fix(call): send theme changes only once the call frame is ready (postMessage origin error) #478

Merged
jared merged 1 commits from fix/call-theme-before-ready into lotus 2026-10-28 20:52:06 -04:00
+26 -3
View File
@@ -133,6 +133,15 @@ export class CallEmbed {
private themeKind: ElementCallThemeKind = 'dark';
// The theme the frame was loaded with (its URL `theme` param), and whether
// the widget API is ready. Until it is, the iframe may still be on its
// initial about:blank (our own origin), so a send to the call's origin is
// refused ("target origin … does not match the recipient window's origin")
// and the request hangs to a timeout. Theme changes wait for ready.
private loadedThemeKind: ElementCallThemeKind = 'dark';
private widgetReady = false;
// Watchdog: detects an iframe that never reaches a usable state.
private loadWatchdog?: ReturnType<typeof setTimeout>;
@@ -360,6 +369,7 @@ export class CallEmbed {
this.iframe = iframe;
this.container = container;
this.themeKind = themeKind;
this.loadedThemeKind = themeKind;
const controlState = initialControlState ?? new CallControlState(true, false, true);
this.control = new CallControl(controlState, call, iframe);
@@ -375,6 +385,13 @@ export class CallEmbed {
// happy path (onCallJoined) clears it too; these cover earlier signals so a
// user sitting in the lobby/prescreen isn't flagged as an error.
this.disposables.push(this.onReady(() => this.settleLoad()));
this.disposables.push(
this.onReady(() => {
this.widgetReady = true;
// A theme change made while the frame was loading.
if (this.themeKind !== this.loadedThemeKind) this.sendTheme();
}),
);
this.disposables.push(this.onCapabilitiesNotified(() => this.settleLoad()));
this.disposables.push(this.onPreparingError(() => this.settleLoad('iframe')));
@@ -399,12 +416,18 @@ export class CallEmbed {
return this.room.roomId;
}
public setTheme(theme: ElementCallThemeKind) {
public setTheme(theme: ElementCallThemeKind): Promise<unknown> {
this.themeKind = theme;
// Before ready, the URL theme applies and the change is sent on ready.
if (!this.widgetReady) return Promise.resolve();
return this.sendTheme();
}
private sendTheme(): Promise<unknown> {
return this.call.transport
.send(WidgetApiToWidgetAction.ThemeChange, { name: theme })
.send(WidgetApiToWidgetAction.ThemeChange, { name: this.themeKind })
.catch(() => {
// Widget transport not ready yet; theme URL param is the fallback
// The frame went away mid-send; nothing to do.
});
}