1111 */
1212
1313import {
14+ createIframeProvider ,
1415 createMessagePortProvider ,
1516 type WireProvider ,
1617} from "./transport.js" ;
@@ -61,7 +62,10 @@ export function isCorrectEnvironment(): boolean {
6162}
6263
6364/**
64- * Origin used as the `targetOrigin` for iframe bootstrap messages.
65+ * Origin used as the `targetOrigin` for outbound `postMessage` frames. Frames
66+ * carry signed payloads and account ids, so this fails closed: when no concrete
67+ * origin can be pinned it returns `null` (rather than falling back to `"*"`) and
68+ * provider construction throws.
6569 */
6670function resolveHostOrigin ( ) : string | null {
6771 if ( typeof document !== "undefined" && document . referrer ) {
@@ -76,8 +80,7 @@ function resolveHostOrigin(): string | null {
7680 return null ;
7781}
7882
79- const HOST_PORT_TIMEOUT_MS = 20_000 ;
80- let iframePortPromise : Promise < MessagePort > | null = null ;
83+ const WEBVIEW_PORT_TIMEOUT_MS = 20_000 ;
8184
8285/**
8386 * Resolve the host-injected `MessagePort`, polling `window.__HOST_API_PORT__`
@@ -90,7 +93,7 @@ let iframePortPromise: Promise<MessagePort> | null = null;
9093 */
9194async function waitForWebviewPort (
9295 signal ?: AbortSignal ,
93- timeoutMs = HOST_PORT_TIMEOUT_MS ,
96+ timeoutMs = WEBVIEW_PORT_TIMEOUT_MS ,
9497) : Promise < MessagePort > {
9598 const start = Date . now ( ) ;
9699 while ( Date . now ( ) - start < timeoutMs ) {
@@ -104,93 +107,18 @@ async function waitForWebviewPort(
104107 ) ;
105108}
106109
107- /**
108- * Resolve the iframe `MessagePort` transferred by `createIframeHost`.
109- */
110- function waitForIframePort (
111- signal ?: AbortSignal ,
112- timeoutMs = HOST_PORT_TIMEOUT_MS ,
113- ) : Promise < MessagePort > {
114- const existing = hostWindow ( ) ?. __HOST_API_PORT__ ;
115- if ( existing ) return Promise . resolve ( existing ) ;
116- if ( iframePortPromise ) return iframePortPromise ;
117-
118- iframePortPromise = new Promise < MessagePort > ( ( resolve , reject ) => {
119- const win = hostWindow ( ) ;
120- if ( ! win ) {
121- reject ( new Error ( "window is unavailable" ) ) ;
122- return ;
123- }
124-
125- const hostOrigin = resolveHostOrigin ( ) ;
126- let done = false ;
127- const cleanup = ( ) : void => {
128- win . removeEventListener ( "message" , onMessage ) ;
129- signal ?. removeEventListener ( "abort" , onAbort ) ;
130- clearTimeout ( timer ) ;
131- } ;
132- const finish = ( result : MessagePort | Error ) : void => {
133- if ( done ) return ;
134- done = true ;
135- cleanup ( ) ;
136- if ( result instanceof Error ) {
137- reject ( result ) ;
138- } else {
139- win . __HOST_API_PORT__ = result ;
140- resolve ( result ) ;
141- }
142- } ;
143- const onAbort = ( ) : void => {
144- finish ( new Error ( "waitForIframePort aborted" ) ) ;
145- } ;
146- const onMessage = ( event : MessageEvent ) : void => {
147- if ( event . source !== win . parent ) return ;
148- if (
149- hostOrigin !== null &&
150- event . origin !== hostOrigin &&
151- event . origin !== "null"
152- ) {
153- return ;
154- }
155- if ( event . data ?. type !== "truapi-init" ) return ;
156- const [ port ] = event . ports ;
157- if ( ! port ) {
158- finish ( new Error ( "truapi-init did not include a MessagePort" ) ) ;
159- return ;
160- }
161- finish ( port ) ;
162- } ;
163- const timer = setTimeout ( ( ) => {
164- finish (
165- new Error ( `Timed out waiting for iframe MessagePort (${ timeoutMs } ms)` ) ,
166- ) ;
167- } , timeoutMs ) ;
168-
169- win . addEventListener ( "message" , onMessage ) ;
170- signal ?. addEventListener ( "abort" , onAbort , { once : true } ) ;
171- win . parent . postMessage ( { type : "truapi-ready" } , hostOrigin ?? "*" ) ;
172- } ) . catch ( ( error : unknown ) => {
173- iframePortPromise = null ;
174- throw error ;
175- } ) ;
176-
177- return iframePortPromise ;
178- }
179-
180110/** Build the {@link WireProvider} matching the detected environment (iframe or webview). */
181111function createSandboxProvider ( ) : WireProvider {
182- const portController = new AbortController ( ) ;
183112 if ( isIframe ( ) ) {
184- const provider = createMessagePortProvider (
185- waitForIframePort ( portController . signal ) ,
186- ) ;
187- const baseDispose = provider . dispose ;
188- provider . dispose = ( ) => {
189- portController . abort ( ) ;
190- baseDispose ?.( ) ;
191- } ;
192- return provider ;
113+ const hostOrigin = resolveHostOrigin ( ) ;
114+ if ( ! hostOrigin ) {
115+ throw new Error (
116+ "TrUAPI iframe provider could not resolve the host origin from document.referrer / ancestorOrigins." ,
117+ ) ;
118+ }
119+ return createIframeProvider ( { target : window . parent , hostOrigin } ) ;
193120 }
121+ const portController = new AbortController ( ) ;
194122 const provider = createMessagePortProvider (
195123 waitForWebviewPort ( portController . signal ) ,
196124 ) ;
@@ -238,21 +166,14 @@ export function getClientSync(): TrUApiClient | null {
238166export function subscribeConnectionStatus (
239167 callback : ( status : ConnectionStatus ) => void ,
240168) : ( ) => void {
241- let emitted = false ;
242- const listener = ( next : ConnectionStatus ) => {
243- emitted = true ;
244- callback ( next ) ;
245- } ;
246- statusListeners . add ( listener ) ;
169+ statusListeners . add ( callback ) ;
170+ callback ( status ) ;
247171
248172 if ( status === "disconnected" ) {
249173 setStatus ( getClientSync ( ) ? "connected" : "disconnected" ) ;
250174 }
251- if ( ! emitted ) {
252- callback ( status ) ;
253- }
254175
255176 return ( ) => {
256- statusListeners . delete ( listener ) ;
177+ statusListeners . delete ( callback ) ;
257178 } ;
258179}
0 commit comments