-
Notifications
You must be signed in to change notification settings - Fork 14
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
implement message channel #444
base: main
Are you sure you want to change the base?
Conversation
This pull request is automatically built and testable in CodeSandbox. To see build info of the built libraries, click here or the icon next to each commit SHA. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I really like this MessageChannel
-based solution, it's way better than the global listeners/handlers we had previously! 🚀
If we have channels in place between individual iframeStamper
s and their iframes we can completely disregard the notion "trusted origin" I believe! The channel is opened by a trusted origin, almost by definition.
The ambiguity which existed before ("is the iframe receiving a message from its parent or from another frame?" and "is the parent receiving from its child iframe or from another frame?") doesn't exist anymore because the MessageChannel is privately owned by the iframe stamper:
- script on the parent page or other frames can't write to port1. This means the iframe knows any messages on port2 are from the right sender: the frame which instantiated it 🔒
- other frames can't write to port2 because it's owned by the iframe. This means the iframe stamper is guaranteed that messages on port1 are from the iframe it instantiated, and nobody else 🔒
@@ -123,7 +123,7 @@ export const TurnkeyProvider: React.FC<TurnkeyProviderProps> = ({ | |||
iframeContainer: document.getElementById( | |||
TurnkeyAuthIframeContainerId | |||
), | |||
iframeUrl: "https://auth.turnkey.com", | |||
iframeUrl: config.iframeUrl || "https://auth.turnkey.com", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👏
@@ -91,6 +91,7 @@ export interface TurnkeySDKBrowserConfig { | |||
defaultOrganizationId: string; | |||
rpId?: string; | |||
serverSignUrl?: string; | |||
iframeUrl?: string; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
because you're making a change here you'll need to generate a changeset for this PR so that we have a minor version change + changelog entry for sdk-browser
trustedParentOrigin: string; | ||
messageChannel: MessageChannel; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would be nice to explain what these are with a quick comment? Something like:
// used to template the iframe and ensure that iframe events are received by the right (parent) frame, for security reasons.
this.trustedParentOrigin = window.origin; | ||
// See https://developer.mozilla.org/en-US/docs/Web/HTML/Element/iframe#sandbox | ||
// We do not need any other permission than running scripts for import/export/auth frames. | ||
iframe.setAttribute("sandbox", "allow-scripts allow-same-origin"); | ||
iframe.setAttribute("data-trusted-origin", this.trustedParentOrigin); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is there a need to persist window.origin
on the iframeStamper or can we do iframe.setAttribute("data-trusted-origin", window.origin)
directly?
@@ -132,9 +134,11 @@ export class IframeStamper { | |||
|
|||
let iframe = window.document.createElement("iframe"); | |||
|
|||
this.trustedParentOrigin = window.origin; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A thought I'm having is that this won't necessarily work in react native (what would "window.origin" resolve to?). But I think that's fine because RN apps don't use iframe stampers do they?
Another thing worth checking is TG apps. But again, they probably don't use iframe stampers?
packages/iframe-stamper/src/index.ts
Outdated
} | ||
|
||
/** | ||
* Inserts the iframe on the page and returns a promise resolving to the iframe's public key | ||
*/ | ||
async init(): Promise<string> { | ||
this.container.appendChild(this.iframe); | ||
this.iframe.addEventListener("load", () => { | ||
// Send a message to the iframe to initialize the message channel | ||
this.iframe.contentWindow?.postMessage("init", this.iframeOrigin, [ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why can contentWindow
be null or undefined? Should we throw an error if this.iframe.contentWindow
is falsy?
this.iframe.addEventListener("load", () => { | ||
// Send a message to the iframe to initialize the message channel | ||
this.iframe.contentWindow?.postMessage("init", this.iframeOrigin, [ | ||
this.messageChannel.port2, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
From the MDN docs: "The ownership of these objects [in the transfer array] is given to the destination side and they are no longer usable on the sending side".
So we're essentially giving the iframe the "port2" side of the pipe? Maybe that's worth a comment I'm not sure.
Also: should we do any feature detection at instantiation time to make sure MessageChannel is available?
packages/iframe-stamper/src/index.ts
Outdated
} | ||
|
||
/** | ||
* Inserts the iframe on the page and returns a promise resolving to the iframe's public key | ||
*/ | ||
async init(): Promise<string> { | ||
this.container.appendChild(this.iframe); | ||
this.iframe.addEventListener("load", () => { | ||
// Send a message to the iframe to initialize the message channel | ||
this.iframe.contentWindow?.postMessage("init", this.iframeOrigin, [ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Another comment here. Maybe this message should be called "initChannel" or "INIT_CHANNEL" just so it's clear?
Summary & Motivation
How I Tested These Changes
Did you add a changeset?
If updating one of our packages, you'll likely need to add a changeset to your PR. To do so, run
pnpm changeset
.pnpm changeset
will generate a file where you should write a human friendly message about the changes. Note how this (example) includes the package name (should be auto added by the command) along with the type of semver change (major.minor.patch) (which you should set).These changes will be used at release time to determine what packages to publish and how to bump their version. For more context see this comment.