Skip to content
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

Draft
wants to merge 6 commits into
base: main
Choose a base branch
from
Draft

implement message channel #444

wants to merge 6 commits into from

Conversation

turnekybc
Copy link
Contributor

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.

Copy link

codesandbox-ci bot commented Dec 9, 2024

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.

Copy link
Contributor

@r-n-o r-n-o left a 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 iframeStampers 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",
Copy link
Contributor

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;
Copy link
Contributor

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

Comment on lines +112 to +113
trustedParentOrigin: string;
messageChannel: MessageChannel;
Copy link
Contributor

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.

Comment on lines +137 to +141
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);
Copy link
Contributor

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;
Copy link
Contributor

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?

}

/**
* 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, [
Copy link
Contributor

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,
Copy link
Contributor

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?

}

/**
* 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, [
Copy link
Contributor

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?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants