Source: components/room-upgrade-handler.js

const log = require("./logging").get("RoomUpgradeHandler");
const MatrixRoom = require("../models/rooms/matrix");
const MatrixUser = require("../models/users/matrix");

/**
 * Handles migration of rooms when a room upgrade is performed.
 */
class RoomUpgradeHandler {
    /**
     * @param {RoomUpgradeHandler~Options} opts
     * @param {Bridge} bridge The parent bridge.
     */
    constructor(opts, bridge) {
        if (opts.migrateGhosts !== false) {
            opts.migrateGhosts = opts.migrateGhosts !== false;
        }
        this._opts = opts;
        this._bridge = bridge;
        this._waitingForInvite = new Map(); //newRoomId: oldRoomId
    }

    onTombstone(ev) {
        const movingTo = ev.content.replacement_room;
        log.info(`Got tombstone event for ${ev.room_id} -> ${movingTo}`);
        const joinVia = new MatrixUser(ev.sender).domain;
        // Try to join the new room.
        return this._joinNewRoom(movingTo, [joinVia]).then((couldJoin) => {
            if (couldJoin) {
                return this._onJoinedNewRoom(ev.room_id, movingTo);
            }
            this._waitingForInvite.set(movingTo, ev.room_id);
            return true;
        }).catch((err) => {
            log.error("Couldn't handle room upgrade: ", err);
            return false;
        });
    }

    _joinNewRoom(newRoomId, joinVia=[]) {
        const intent = this._bridge.getIntent();
        return intent.join(newRoomId, [joinVia]).then(() => {
            return true;
        }).catch((ex) => {
            if (ex.errcode === "M_FORBIDDEN") {
                return false;
            }
            throw Error("Failed to handle upgrade");
        })
    }

    onInvite(ev) {
        if (!this._waitingForInvite.has(ev.room_id)) {
            return false;
        }
        const oldRoomId = this._waitingForInvite.get(ev.room_id);
        this._waitingForInvite.delete(ev.room_id);
        log.debug(`Got invite to upgraded room ${ev.room_id}`);
        this._joinNewRoom(ev.room_id).then(() => {
            return this._onJoinedNewRoom(oldRoomId, ev.room_id);
        }).catch((err) => {
            log.error("Couldn't handle room upgrade: ", err);
        });
        return true;
    }

    _onJoinedNewRoom(oldRoomId, newRoomId) {
        log.debug(`Joined ${newRoomId}`);
        const intent = this._bridge.getIntent();
        const asBot = this._bridge.getBot();
        const roomStore = this._bridge.getRoomStore();
        return roomStore.getEntriesByMatrixId(oldRoomId).then((entries) => {
            console.log("Entries:", entries);
            return entries.map((entry) => {
                const newEntry = (
                    this._opts.migrateEntry || this._migrateEntry)(entry, newRoomId);
                if (!newEntry) {
                    return Promise.resolve();
                }
                return roomStore.upsertEntry(newEntry);
            });
        }).catch((ex) => {
            log.warn("Failed to migrate room entries:", ex);
        }).then(() => {
            log.debug(`Migrated entries from ${oldRoomId} to ${newRoomId} successfully.`);
            if (this._opts.onRoomMigrated) {
                this._opts.onRoomMigrated(oldRoomId, newRoomId);
            }

            if (!this._opts.migrateGhosts) {
                return Promise.resolve(false);
            }
            return asBot.getJoinedMembers(oldRoomId);
        }).then((members) => {
            if (members === false) {
                return false;
            }
            const userIds = Object.keys(members).filter((u) => asBot.isRemoteUser(u));
            log.debug(`Migrating ${userIds.length} ghosts`);
            return Promise.all(userIds.map((uId) => {
                const i = this._bridge.getIntent(userId);
                return Promise.all([i.leave(oldRoomId), i.join(newRoomId)]);
            }).concat([
                intent.leave(oldRoomId)
            ]));
        }).catch((ex) => {
            log.warn("Failed to migrate room ghosts:", ex);
        }).then((res) => {
            if (res !== false) {
                log.debug("Migrated all ghosts across");
            }
        })
    }

    _migrateEntry(entry, newRoomId) {
        entry.matrix = new MatrixRoom(newRoomId, {
            name: entry.name,
            topic: entry.topic,
            extras: entry._extras,
        });
        return entry;
    }
}

module.exports = RoomUpgradeHandler;

 /**
  * Options to supply to the {@link RoomUpgradeHandler}.
  * @typedef RoomUpgradeHandler~Options
  * @type {Object}
  * @property {RoomUpgradeHandler~MigrateEntry} migrateEntry Called when
  * the handler wishes to migrate a MatrixRoom entry to a new room_id. If omitted,
  * {@link RoomUpgradeHandler~_migrateEntry} will be used instead.
  * @property {RoomUpgradeHandler~onRoomMigrated} onRoomMigrated This is called
  * when the entries of the room have been migrated, the bridge should do any cleanup it
  * needs of the old room and setup the new room (ex: Joining ghosts to the new room).
  * @property {bool} [consumeEvent=true] Consume tombstone or invite events that
  * are acted on by this handler.
  * @property {bool} [migrateGhosts=true] If given, migrate all ghost users across to
  * the new room.
  */


 /**
 * Invoked when iterating around a rooms entries. Should be used to update entries
 * with a new room id.
 *
 * @callback RoomUpgradeHandler~MigrateEntry
 * @param {RoomBridgeStore~Entry} entry The existing entry.
 * @param {string} newRoomId The new roomId.
 * @return {RoomBridgeStore~Entry} Return the entry to upsert it,
 * or null to ignore it.
 */

 /**
  * Invoked after a room has been upgraded and it's entries updated.
  *
  * @callback RoomUpgradeHandler~onRoomMigrated
  * @param {string} oldRoomId The old roomId.
  * @param {string} newRoomId The new roomId.
  */