Skip to content

Commit

Permalink
Only process one iframe load event (#16)
Browse files Browse the repository at this point in the history
In Safari, the `load` event for iframes can fire multiple times, which
causes confusion for the RPC channel. This ensures that we only process
the `load` event once per iframe.
  • Loading branch information
robknight authored Oct 8, 2024
1 parent fe746e7 commit d8addb0
Showing 1 changed file with 50 additions and 46 deletions.
96 changes: 50 additions & 46 deletions packages/app-connector/src/adapters/iframe.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,53 +102,57 @@ export function connect(
iframe.src = normalizedUrl.toString();

return new Promise<ParcnetAPI>((resolve) => {
iframe.addEventListener("load", () => {
// Create a new MessageChannel to communicate with the iframe
const chan = new MessageChannel();

// Create a new RPC client
const client = new ParcnetRPCConnector(
chan.port2,
new DialogControllerImpl(dialog)
);
// Tell the RPC client to start. It will call the function we pass in
// when the connection is ready, at which point we can resolve the
// promise and return the API wrapper to the caller.
// See below for how the other port of the message channel is sent to
// the client.
client.start(() => {
resolve(new ParcnetAPI(client));
});

if (iframe.contentWindow) {
const contentWindow = iframe.contentWindow;
// @todo Blink (and maybe Webkit) will discard messages if there's no
// handler yet, so we need to wait a bit and/or retry until the client is
// ready
// The client takes a few seconds to load, so waiting isn't a bad solution
new Promise<void>((resolve) => {
window.setTimeout(() => resolve(), 1000);
})
.then(() => {
// Send the other port of the message channel to the client
postWindowMessage(
contentWindow,
{
type: InitializationMessageType.PARCNET_CLIENT_CONNECT,
zapp: zapp
},
"*",
// Our RPC client has port2, send port1 to the client
[chan.port1]
);
iframe.addEventListener(
"load",
() => {
// Create a new MessageChannel to communicate with the iframe
const chan = new MessageChannel();

// Create a new RPC client
const client = new ParcnetRPCConnector(
chan.port2,
new DialogControllerImpl(dialog)
);
// Tell the RPC client to start. It will call the function we pass in
// when the connection is ready, at which point we can resolve the
// promise and return the API wrapper to the caller.
// See below for how the other port of the message channel is sent to
// the client.
client.start(() => {
resolve(new ParcnetAPI(client));
});

if (iframe.contentWindow) {
const contentWindow = iframe.contentWindow;
// @todo Blink (and maybe Webkit) will discard messages if there's no
// handler yet, so we need to wait a bit and/or retry until the client is
// ready
// The client takes a few seconds to load, so waiting isn't a bad solution
new Promise<void>((resolve) => {
window.setTimeout(() => resolve(), 1000);
})
.catch((err) => {
console.error("Error sending initialization message", err);
});
} else {
console.error("no iframe content window!");
}
});
.then(() => {
// Send the other port of the message channel to the client
postWindowMessage(
contentWindow,
{
type: InitializationMessageType.PARCNET_CLIENT_CONNECT,
zapp: zapp
},
"*",
// Our RPC client has port2, send port1 to the client
[chan.port1]
);
})
.catch((err) => {
console.error("Error sending initialization message", err);
});
} else {
console.error("no iframe content window!");
}
},
{ once: true }
);
shadow.appendChild(iframe);
});
}
Expand Down

0 comments on commit d8addb0

Please sign in to comment.