Skip to content

Commit 20fc397

Browse files
committed
Restore Call.start to a single, robust event loop
1 parent 12bda78 commit 20fc397

File tree

1 file changed

+39
-37
lines changed

1 file changed

+39
-37
lines changed

src/models/Call.ts

Lines changed: 39 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -222,46 +222,48 @@ export abstract class Call extends TypedEventEmitter<CallEvent, CallEventHandler
222222
const startTime = performance.now();
223223
let messaging: WidgetMessaging | undefined = messagingStore.getMessagingForUid(this.widgetUid);
224224
// The widget might still be initializing, so wait for it in an async
225-
// event loop. We need the messaging to be both present and started, so
226-
// we register listeners for both cases. Note that due to React strict
227-
// mode, the messaging could even be aborted and replaced by an entirely
228-
// new messaging while we are waiting here!
229-
while (!messaging) {
230-
logger.info(`No messaging for ${this.widgetUid}`);
231-
await new Promise<void>((resolve, reject) => {
232-
// We don't have `messaging` either so wait for the
233-
const onStoreMessaging = (uid: string, m: WidgetMessaging): void => {
234-
if (uid === this.widgetUid) {
235-
messagingStore.off(WidgetMessagingStoreEvent.StoreMessaging, onStoreMessaging);
236-
messaging = m;
237-
resolve();
238-
}
239-
};
240-
messagingStore.on(WidgetMessagingStoreEvent.StoreMessaging, onStoreMessaging);
241-
242-
setTimeout(
243-
() => {
244-
messagingStore.off(WidgetMessagingStoreEvent.StoreMessaging, onStoreMessaging);
245-
reject(new Error(`Messaging for call in ${this.roomId} not present; timed out`));
246-
},
247-
startTime + TIMEOUT_MS - performance.now(),
248-
);
249-
});
250-
}
225+
// event loop. We need the messaging to be both present and started
226+
// (have a connected widget API), so register listeners for both cases.
227+
while (!messaging?.widgetApi) {
228+
if (messaging) logger.debug(`Messaging present but not yet started for ${this.widgetUid}`);
229+
else logger.debug(`No messaging yet for ${this.widgetUid}`);
230+
const recheck = Promise.withResolvers<void>();
231+
const currentMessaging = messaging;
232+
233+
// Maybe the messaging is present but not yet started. In this case,
234+
// check again for a widget API as soon as it starts.
235+
const onStart = (): void => recheck.resolve();
236+
currentMessaging?.on(WidgetMessagingEvent.Start, onStart);
237+
238+
// Maybe the messaging is not present at all. It's also entirely
239+
// possible (as shown in React strict mode) that the messaging could
240+
// be abandoned and replaced by an entirely new messaging object
241+
// while we were waiting for the original one to start. We need to
242+
// react to store updates in either case.
243+
const onStoreMessaging = (uid: string, m: WidgetMessaging): void => {
244+
if (uid === this.widgetUid) {
245+
messagingStore.off(WidgetMessagingStoreEvent.StoreMessaging, onStoreMessaging);
246+
messaging = m; // Check the new messaging object on the next iteration of the loop
247+
recheck.resolve();
248+
}
249+
};
250+
messagingStore.on(WidgetMessagingStoreEvent.StoreMessaging, onStoreMessaging);
251251

252-
while (!messaging.widgetApi) {
253-
const foundMessaging = messaging;
254-
logger.info(`No widgetApi for ${this.widgetUid}`);
255-
await new Promise<void>((resolve, reject) => {
256-
// We have messasing but are waiting for the widgetApi
257-
foundMessaging.once(WidgetMessagingEvent.Start, (): void => resolve());
252+
// Race both of the above recheck signals against a timeout.
253+
const timeout = setTimeout(
254+
() => recheck.reject(new Error(`Widget for call in ${this.roomId} not started; timed out`)),
255+
TIMEOUT_MS - (performance.now() - startTime),
256+
);
258257

259-
setTimeout(
260-
() => reject(new Error(`Messaging for call in ${this.roomId} did not start; timed out`)),
261-
startTime + TIMEOUT_MS - performance.now(),
262-
);
263-
});
258+
try {
259+
await recheck.promise;
260+
} finally {
261+
currentMessaging?.off(WidgetMessagingEvent.Start, onStart);
262+
messagingStore.off(WidgetMessagingStoreEvent.StoreMessaging, onStoreMessaging);
263+
clearTimeout(timeout);
264+
}
264265
}
266+
265267
logger.debug(`Widget ${this.widgetUid} now ready`);
266268
return (this.widgetApi = messaging.widgetApi);
267269
}

0 commit comments

Comments
 (0)