From 8919ebf712347994ca0dd14640785f4a8473f105 Mon Sep 17 00:00:00 2001 From: Janek Date: Thu, 6 Jul 2023 13:42:40 +0200 Subject: [PATCH] fix: cross origin loading times --- src/adapter/window.ts | 4 ++++ src/link/popup.ts | 28 ++++++++++++++++++++++------ src/shared/constants.ts | 1 + src/types/index.ts | 9 +++++---- 4 files changed, 32 insertions(+), 10 deletions(-) create mode 100644 src/shared/constants.ts diff --git a/src/adapter/window.ts b/src/adapter/window.ts index b74eda4..e24f552 100644 --- a/src/adapter/window.ts +++ b/src/adapter/window.ts @@ -1,6 +1,7 @@ import { AnyProcedure, AnyRouter, TRPCError } from '@trpc/server'; import { Unsubscribable, isObservable } from '@trpc/server/observable'; +import { TRPC_BROWSER_LOADED_EVENT } from '../shared/constants'; import { isTRPCRequestWithId } from '../shared/trpcMessage'; import type { MinimalWindow, TRPCChromeResponse } from '../types'; import { CreateHandlerOptions } from './base'; @@ -22,6 +23,9 @@ export const createWindowHandler = ( return; } + const loadListener = opts.postWindow ?? window.opener ?? window; + loadListener.postMessage(TRPC_BROWSER_LOADED_EVENT, { targetOrigin: postOrigin }); + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const { transformer } = router._def._config; const subscriptions = new Map(); diff --git a/src/link/popup.ts b/src/link/popup.ts index da90364..b3c5927 100644 --- a/src/link/popup.ts +++ b/src/link/popup.ts @@ -1,6 +1,7 @@ import type { TRPCLink } from '@trpc/client'; import type { AnyRouter } from '@trpc/server'; +import { TRPC_BROWSER_LOADED_EVENT } from '../shared/constants'; import type { MinimalPopupWindow, MinimalWindow, TRPCChromeMessage } from '../types'; import { createBaseLink } from './internal/base'; @@ -18,17 +19,32 @@ export const popupLink = (opts: PopupLinkOptions): TR const closeHandlerSet = new Set<() => void>(); let popupWindow: MinimalPopupWindow | null = null; - async function getPopup() { + async function getPopup(loadListenWindow: MinimalWindow) { if (!popupWindow || popupWindow.closed) { popupWindow = opts.createPopup(); - // wait til window is loaded await Promise.race([ + // wait til window is loaded (same origin) new Promise((resolve) => { - popupWindow?.addEventListener?.('load', resolve); + try { + popupWindow?.addEventListener?.('load', resolve); + } catch { + // if this throws, it's a cross-origin popup and should stay pending (never resolve) + } + }), + // this is needed for cross-origin popups as they don't have a load event + new Promise((resolve) => { + loadListenWindow.addEventListener('message', (event) => { + if (event.data === TRPC_BROWSER_LOADED_EVENT) { + resolve(); + } + }); }), - // expect the popup to load within 2.5s, this is needed for cross-origin popups as they don't have a load event + // expect the popup to load after 15s max, in case non of the above events fire new Promise((resolve) => { - setTimeout(resolve, 2500); + console.warn( + 'Could not detect if popup loading succeeded after 15s timeout, continuing anyway', + ); + setTimeout(resolve, 15000); }), ]); @@ -59,7 +75,7 @@ export const popupLink = (opts: PopupLinkOptions): TR return createBaseLink({ async postMessage(message) { - const popup = await getPopup(); + const popup = await getPopup(opts.listenWindow); return popup.postMessage(message, { targetOrigin: opts.postOrigin, }); diff --git a/src/shared/constants.ts b/src/shared/constants.ts new file mode 100644 index 0000000..2ef2dc2 --- /dev/null +++ b/src/shared/constants.ts @@ -0,0 +1 @@ +export const TRPC_BROWSER_LOADED_EVENT = 'TRPC_BROWSER::POPUP_LOADED'; diff --git a/src/types/index.ts b/src/types/index.ts index bc9bbb7..4c2843d 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -10,6 +10,7 @@ export type TRPCChromeRequest = { }; export type TRPCChromeSuccessResponse = { + // eslint-disable-next-line @typescript-eslint/no-explicit-any trpc: TRPCResultMessage; }; @@ -22,10 +23,10 @@ export type TRPCChromeResponse = TRPCChromeSuccessResponse | TRPCChromeErrorResp export type TRPCChromeMessage = TRPCChromeRequest | TRPCChromeResponse; export type RelayedTRPCMessage = TRPCChromeMessage & { relayed?: true }; -export type MinimalWindow = Pick< - Window, - 'postMessage' | 'addEventListener' | 'removeEventListener' ->; +export interface MinimalWindow + extends Pick { + opener?: MinimalWindow; +} export type MinimalPopupWindow = Pick & // addEventListener/removeEventListener are only available on the same origin