feat(identity-switch): implement deduplication logic for identity switch events and refactor related handling in App.vue and IdentitiesPage.vue

This commit is contained in:
Ivan
2026-04-23 17:05:53 -05:00
parent 8cda11b3e4
commit 084c1105db
3 changed files with 39 additions and 19 deletions
+33 -15
View File
@@ -704,6 +704,9 @@ export default {
wsReconnectedBanner: false,
wsDisconnectTickTimer: null,
wsReconnectedHideTimer: null,
identitySwitchDedupeHash: null,
identitySwitchDedupeAt: 0,
};
},
computed: {
@@ -842,6 +845,7 @@ export default {
WebSocketConnection.on("disconnected", this.onWsShellDisconnected);
WebSocketConnection.on("connected", this.onWsShellConnected);
GlobalEmitter.on("identity-switching-start", this.onIdentitySwitchingStartShell);
GlobalEmitter.on("identity-switched-apply", this.onIdentitySwitchedApplyShell);
GlobalEmitter.on("sync-propagation-node", this.onSyncPropagationNodeShell);
GlobalEmitter.on("config-updated", this.onConfigUpdatedExternally);
GlobalEmitter.on("keyboard-shortcut", this.onKeyboardShortcutShell);
@@ -878,6 +882,7 @@ export default {
WebSocketConnection.off("disconnected", this.onWsShellDisconnected);
WebSocketConnection.off("connected", this.onWsShellConnected);
GlobalEmitter.off("identity-switching-start", this.onIdentitySwitchingStartShell);
GlobalEmitter.off("identity-switched-apply", this.onIdentitySwitchedApplyShell);
GlobalEmitter.off("sync-propagation-node", this.onSyncPropagationNodeShell);
GlobalEmitter.off("config-updated", this.onConfigUpdatedExternally);
GlobalEmitter.off("keyboard-shortcut", this.onKeyboardShortcutShell);
@@ -990,6 +995,33 @@ export default {
}
}, 45000);
},
onIdentitySwitchedApplyShell(payload) {
this.applyIdentitySwitched(payload).catch(() => {});
},
async applyIdentitySwitched(json) {
const hash = json?.identity_hash;
if (hash == null || hash === "") {
return;
}
const now = Date.now();
if (this.identitySwitchDedupeHash === hash && now - this.identitySwitchDedupeAt < 10000) {
return;
}
this.identitySwitchDedupeHash = hash;
this.identitySwitchDedupeAt = now;
ToastUtils.success(this.$t("identities.switched"));
GlobalState.unreadConversationsCount = 0;
await this.getConfig();
await this.updateRingtonePlayer();
await this.getAppInfo();
this.isSwitchingIdentity = false;
GlobalEmitter.emit("identity-switched", json);
},
onSyncPropagationNodeShell() {
this.syncPropagationNode();
},
@@ -1177,21 +1209,7 @@ export default {
break;
}
case "identity_switched": {
ToastUtils.success(`Switched to identity: ${json.display_name}`);
// reset global state
GlobalState.unreadConversationsCount = 0;
// update local state
await this.getConfig();
await this.updateRingtonePlayer();
await this.getAppInfo();
// hide loading overlay
this.isSwitchingIdentity = false;
// if we are on identities page, we might want to refresh it
GlobalEmitter.emit("identity-switched", json);
await this.applyIdentitySwitched(json);
break;
}
case "rncp.receive.completed": {
@@ -564,8 +564,10 @@ export default {
});
if (response.data.hotswapped) {
// Success is handled by GlobalEmitter "identity-switched" which we listen to
ToastUtils.success(this.$t("identities.switched") || "Identity switched successfully");
GlobalEmitter.emit("identity-switched-apply", {
identity_hash: response.data.identity_hash ?? identity.hash,
display_name: response.data.display_name ?? identity.display_name ?? "",
});
} else {
ToastUtils.info(this.$t("identities.switch_scheduled"));
setTimeout(() => {
+2 -2
View File
@@ -23,8 +23,8 @@ vi.mock("../../meshchatx/src/frontend/js/GlobalEmitter", () => ({
function makeCtx() {
return {
_identitySwitchDedupeHash: null,
_identitySwitchDedupeAt: 0,
identitySwitchDedupeHash: null,
identitySwitchDedupeAt: 0,
isSwitchingIdentity: true,
getConfig: vi.fn().mockResolvedValue(undefined),
updateRingtonePlayer: vi.fn().mockResolvedValue(undefined),