diff --git a/desktop/main.go b/desktop/main.go index f953eca..fb78b82 100644 --- a/desktop/main.go +++ b/desktop/main.go @@ -81,6 +81,10 @@ func (c *CommandHandler) Init() { Data: marshaledPayload, }) } + c.App.EmitEvent("hicli_event", &hicli.JSONCommand{ + Command: "init_complete", + RequestID: 0, + }) log.Info().Int("room_count", roomCount).Msg("Sent initial rooms to client") }() } diff --git a/pkg/gomuks/websocket.go b/pkg/gomuks/websocket.go index 6d9f528..c3d35d9 100644 --- a/pkg/gomuks/websocket.go +++ b/pkg/gomuks/websocket.go @@ -259,5 +259,13 @@ func (gmx *Gomuks) sendInitialData(ctx context.Context, conn *websocket.Conn) { return } } + err := writeCmd(ctx, conn, &hicli.JSONCommand{ + Command: "init_complete", + RequestID: 0, + }) + if err != nil { + log.Err(err).Msg("Failed to send initial rooms done event to client") + return + } log.Info().Int("room_count", roomCount).Msg("Sent initial rooms to client") } diff --git a/web/src/api/client.ts b/web/src/api/client.ts index 07bf47c..1db38b4 100644 --- a/web/src/api/client.ts +++ b/web/src/api/client.ts @@ -34,6 +34,7 @@ import type { export default class Client { readonly state = new CachedEventDispatcher() readonly syncStatus = new NonNullCachedEventDispatcher({ type: "waiting", error_count: 0 }) + readonly initComplete = new NonNullCachedEventDispatcher(false) readonly store = new StateStore() #stateRequests: RoomStateGUID[] = [] #stateRequestQueued = false @@ -105,6 +106,8 @@ export default class Client { this.state.emit(ev.data) } else if (ev.command === "sync_status") { this.syncStatus.emit(ev.data) + } else if (ev.command === "init_complete") { + this.initComplete.emit(true) } else if (ev.command === "sync_complete") { this.store.applySync(ev.data) } else if (ev.command === "events_decrypted") { @@ -317,9 +320,16 @@ export default class Client { } } - async logout() { - await this.rpc.logout() + clearState() { + this.initComplete.emit(false) + this.syncStatus.emit({ type: "waiting", error_count: 0 }) + this.state.clearCache() localStorage.clear() this.store.clear() } + + async logout() { + await this.rpc.logout() + this.clearState() + } } diff --git a/web/src/api/types/hievents.ts b/web/src/api/types/hievents.ts index 69952f7..96603b4 100644 --- a/web/src/api/types/hievents.ts +++ b/web/src/api/types/hievents.ts @@ -118,6 +118,10 @@ export interface SyncStatusEvent extends RPCCommand { command: "sync_status" } +export interface InitCompleteEvent extends RPCCommand { + command: "init_complete" +} + export type RPCEvent = ClientStateEvent | SyncStatusEvent | @@ -125,4 +129,5 @@ export type RPCEvent = SendCompleteEvent | EventsDecryptedEvent | SyncCompleteEvent | - ImageAuthTokenEvent + ImageAuthTokenEvent | + InitCompleteEvent diff --git a/web/src/ui/MainScreen.tsx b/web/src/ui/MainScreen.tsx index c0071cf..676ba41 100644 --- a/web/src/ui/MainScreen.tsx +++ b/web/src/ui/MainScreen.tsx @@ -159,6 +159,7 @@ const handleURLHash = (client: Client, context: ContextFields) => { console.error("Invalid matrix URI", decodedURI) return } + console.log("Handling URI", uri) const newURL = new URL(location.href) newURL.hash = "" if (uri.identifier.startsWith("@")) { @@ -206,9 +207,20 @@ const MainScreen = () => { context.setRightPanel(evt.state?.right_panel ?? null, false) } window.addEventListener("popstate", listener) - listener({ state: history.state } as PopStateEvent) - handleURLHash(client, context) - return () => window.removeEventListener("popstate", listener) + const initHandle = () => { + listener({ state: history.state } as PopStateEvent) + handleURLHash(client, context) + } + let cancel = () => {} + if (client.initComplete.current) { + initHandle() + } else { + cancel = client.initComplete.once(initHandle) + } + return () => { + window.removeEventListener("popstate", listener) + cancel() + } }, [context, client]) useEffect(() => context.keybindings.listen(), [context]) useInsertionEffect(() => { diff --git a/web/src/util/eventdispatcher.ts b/web/src/util/eventdispatcher.ts index 9530b0c..4ebf0b1 100644 --- a/web/src/util/eventdispatcher.ts +++ b/web/src/util/eventdispatcher.ts @@ -33,6 +33,16 @@ export class EventDispatcher { return this.#listen(listener) } + once(listener: (data: T) => void): () => void { + let unsub: (() => void) | undefined = undefined + const wrapped = (data: T) => { + unsub?.() + listener(data) + } + unsub = this.#listen(wrapped) + return unsub + } + #listen(listener: (data: T) => void): () => void { this.#listeners.push(listener) return () => { @@ -72,6 +82,10 @@ export class CachedEventDispatcher extends EventDispatcher { } return unlisten } + + clearCache() { + this.current = null + } } export class NonNullCachedEventDispatcher extends CachedEventDispatcher { @@ -81,4 +95,8 @@ export class NonNullCachedEventDispatcher extends CachedEventDispatcher { super(cache) this.current = cache } + + clearCache() { + throw new Error("Cannot clear cache of NonNullCachedEventDispatcher") + } }