From a2b4c77a31d3303f00c08009684ad631b484ed96 Mon Sep 17 00:00:00 2001 From: Brian Cleary Date: Mon, 9 Dec 2024 16:23:28 -0500 Subject: [PATCH 1/8] implement message channel --- packages/iframe-stamper/src/index.ts | 312 +++++++----------- packages/sdk-browser/package.json | 2 +- packages/sdk-browser/src/__types__/base.ts | 1 + .../sdk-react/src/contexts/TurnkeyContext.tsx | 2 +- packages/sdk-server/package.json | 2 +- 5 files changed, 115 insertions(+), 204 deletions(-) diff --git a/packages/iframe-stamper/src/index.ts b/packages/iframe-stamper/src/index.ts index 666bcde19..3447211bd 100644 --- a/packages/iframe-stamper/src/index.ts +++ b/packages/iframe-stamper/src/index.ts @@ -109,6 +109,8 @@ export class IframeStamper { iframe: HTMLIFrameElement; iframeOrigin: string; iframePublicKey: string | null; + trustedParentOrigin: string; + messageChannel: MessageChannel; /** * Creates a new iframe stamper. This function _does not_ insert the iframe in the DOM. @@ -132,9 +134,11 @@ export class IframeStamper { let iframe = window.document.createElement("iframe"); + 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) iframe.id = config.iframeElementId; iframe.src = config.iframeUrl; @@ -145,6 +149,8 @@ export class IframeStamper { // This is populated once the iframe is ready. Call `.init()` to kick off DOM insertion! this.iframePublicKey = null; + + this.messageChannel = new MessageChannel(); } /** @@ -152,22 +158,18 @@ export class IframeStamper { */ async init(): Promise { 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, [this.messageChannel.port2]); + }) + return new Promise((resolve, _reject) => { - window.addEventListener( - "message", - (event) => { - if (event.origin !== this.iframeOrigin) { - // There might be other things going on in the window, for example: react dev tools, other extensions, etc. - // Instead of erroring out - return; - } - if (event.data?.type === IframeEventType.PublicKeyReady) { - this.iframePublicKey = event.data["value"]; - resolve(event.data["value"]); - } - }, - false - ); + this.messageChannel.port1.onmessage = (event) => { + if (event.data?.type === IframeEventType.PublicKeyReady) { + this.iframePublicKey = event.data["value"]; + resolve(event.data["value"]); + } + } }); } @@ -193,31 +195,18 @@ export class IframeStamper { */ async injectCredentialBundle(bundle: string): Promise { return new Promise((resolve, reject) => { - this.iframe.contentWindow?.postMessage( - { - type: IframeEventType.InjectCredentialBundle, - value: bundle, - }, - "*" - ); - - window.addEventListener( - "message", - (event) => { - if (event.origin !== this.iframeOrigin) { - // There might be other things going on in the window, for example: react dev tools, other extensions, etc. - // Instead of erroring out we simply return. Not our event! - return; - } - if (event.data?.type === IframeEventType.BundleInjected) { - resolve(event.data["value"]); - } - if (event.data?.type === IframeEventType.Error) { - reject(event.data["value"]); - } - }, - false - ); + this.messageChannel.port1.postMessage({ + type: IframeEventType.InjectCredentialBundle, + value: bundle, + }) + this.messageChannel.port1.onmessage = (event) => { + if (event.data?.type === IframeEventType.BundleInjected) { + resolve(event.data["value"]); + } + if (event.data?.type === IframeEventType.Error) { + reject(event.data["value"]); + } + } }); } @@ -240,27 +229,18 @@ export class IframeStamper { keyFormat, organizationId, }, - "*" + this.iframeOrigin ); return new Promise((resolve, reject) => { - window.addEventListener( - "message", - (event) => { - if (event.origin !== this.iframeOrigin) { - // There might be other things going on in the window, for example: react dev tools, other extensions, etc. - // Instead of erroring out we simply return. Not our event! - return; - } - if (event.data?.type === IframeEventType.BundleInjected) { - resolve(event.data["value"]); - } - if (event.data?.type === IframeEventType.Error) { - reject(event.data["value"]); - } - }, - false - ); + this.messageChannel.port1.onmessage = (event) => { + if (event.data?.type === IframeEventType.BundleInjected) { + resolve(event.data["value"]); + } + if (event.data?.type === IframeEventType.Error) { + reject(event.data["value"]); + } + } }); } @@ -274,33 +254,21 @@ export class IframeStamper { bundle: string, organizationId: string ): Promise { - this.iframe.contentWindow?.postMessage( - { - type: IframeEventType.InjectWalletExportBundle, - value: bundle, - organizationId, - }, - "*" - ); + this.messageChannel.port1.postMessage({ + type: IframeEventType.InjectWalletExportBundle, + value: bundle, + organizationId, + }) return new Promise((resolve, reject) => { - window.addEventListener( - "message", - (event) => { - if (event.origin !== this.iframeOrigin) { - // There might be other things going on in the window, for example: react dev tools, other extensions, etc. - // Instead of erroring out we simply return. Not our event! - return; - } - if (event.data?.type === IframeEventType.BundleInjected) { - resolve(event.data["value"]); - } - if (event.data?.type === IframeEventType.Error) { - reject(event.data["value"]); - } - }, - false - ); + this.messageChannel.port1.onmessage = (event) => { + if (event.data?.type === IframeEventType.BundleInjected) { + resolve(event.data["value"]); + } + if (event.data?.type === IframeEventType.Error) { + reject(event.data["value"]); + } + } }); } @@ -313,34 +281,24 @@ export class IframeStamper { organizationId: string, userId: string ): Promise { - this.iframe.contentWindow?.postMessage( + this.messageChannel.port1.postMessage( { type: IframeEventType.InjectImportBundle, value: bundle, organizationId, userId, }, - "*" ); return new Promise((resolve, reject) => { - window.addEventListener( - "message", - (event) => { - if (event.origin !== this.iframeOrigin) { - // There might be other things going on in the window, for example: react dev tools, other extensions, etc. - // Instead of erroring out we simply return. Not our event! - return; - } - if (event.data?.type === IframeEventType.BundleInjected) { - resolve(event.data["value"]); - } - if (event.data?.type === IframeEventType.Error) { - reject(event.data["value"]); - } - }, - false - ); + this.messageChannel.port1.onmessage = (event) => { + if (event.data?.type === IframeEventType.BundleInjected) { + resolve(event.data["value"]); + } + if (event.data?.type === IframeEventType.Error) { + reject(event.data["value"]); + } + } }); } @@ -351,31 +309,21 @@ export class IframeStamper { * This is used during the wallet import flow. */ async extractWalletEncryptedBundle(): Promise { - this.iframe.contentWindow?.postMessage( + this.messageChannel.port1.postMessage( { type: IframeEventType.ExtractWalletEncryptedBundle, }, - "*" ); return new Promise((resolve, reject) => { - window.addEventListener( - "message", - (event) => { - if (event.origin !== this.iframeOrigin) { - // There might be other things going on in the window, for example: react dev tools, other extensions, etc. - // Instead of erroring out we simply return. Not our event! - return; - } - if (event.data?.type === IframeEventType.EncryptedBundleExtracted) { - resolve(event.data["value"]); - } - if (event.data?.type === IframeEventType.Error) { - reject(event.data["value"]); - } - }, - false - ); + this.messageChannel.port1.onmessage = (event) => { + if (event.data?.type === IframeEventType.EncryptedBundleExtracted) { + resolve(event.data["value"]); + } + if (event.data?.type === IframeEventType.Error) { + reject(event.data["value"]); + } + } }); } @@ -387,32 +335,20 @@ export class IframeStamper { * This is used during the private key import flow. */ async extractKeyEncryptedBundle(keyFormat?: KeyFormat): Promise { - this.iframe.contentWindow?.postMessage( - { - type: IframeEventType.ExtractKeyEncryptedBundle, - keyFormat: keyFormat, - }, - "*" - ); + this.messageChannel.port1.postMessage({ + type: IframeEventType.ExtractKeyEncryptedBundle, + keyFormat: keyFormat, + }); return new Promise((resolve, reject) => { - window.addEventListener( - "message", - (event) => { - if (event.origin !== this.iframeOrigin) { - // There might be other things going on in the window, for example: react dev tools, other extensions, etc. - // Instead of erroring out we simply return. Not our event! - return; - } - if (event.data?.type === IframeEventType.EncryptedBundleExtracted) { - resolve(event.data["value"]); - } - if (event.data?.type === IframeEventType.Error) { - reject(event.data["value"]); - } - }, - false - ); + this.messageChannel.port1.onmessage = (event) => { + if (event.data?.type === IframeEventType.EncryptedBundleExtracted) { + resolve(event.data["value"]); + } + if (event.data?.type === IframeEventType.Error) { + reject(event.data["value"]); + } + } }); } @@ -422,33 +358,21 @@ export class IframeStamper { */ async applySettings(settings: TIframeSettings): Promise { const settingsStr = JSON.stringify(settings); - this.iframe.contentWindow?.postMessage( - { - type: IframeEventType.ApplySettings, - value: settingsStr, - }, - "*" - ); + this.messageChannel.port1.postMessage({ + type: IframeEventType.ApplySettings, + value: settingsStr, + }) return new Promise((resolve, reject) => { - window.addEventListener( - "message", - (event) => { - if (event.origin !== this.iframeOrigin) { - // There might be other things going on in the window, for example: react dev tools, other extensions, etc. - // Instead of erroring out we simply return. Not our event! - return; - } - if (event.data?.type === IframeEventType.SettingsApplied) { - resolve(event.data["value"]); - } - if (event.data?.type === IframeEventType.Error) { - reject(event.data["value"]); - } - }, - false - ); - }); + this.messageChannel.port1.onmessage = (event) => { + if (event.data?.type === IframeEventType.SettingsApplied) { + resolve(event.data["value"]); + } + if (event.data?.type === IframeEventType.Error) { + reject(event.data["value"]); + } + } + }) } /** @@ -461,37 +385,23 @@ export class IframeStamper { ); } - const iframeOrigin = this.iframeOrigin; - - this.iframe.contentWindow?.postMessage( - { - type: IframeEventType.StampRequest, - value: payload, - }, - "*" - ); - - return new Promise(function (resolve, reject) { - window.addEventListener( - "message", - (event) => { - if (event.origin !== iframeOrigin) { - // There might be other things going on in the window, for example: react dev tools, other extensions, etc. - // Instead of erroring out we simply return. Not our event! - return; - } - if (event.data?.type === IframeEventType.Stamp) { - resolve({ - stampHeaderName: stampHeaderName, - stampHeaderValue: event.data["value"], - }); - } - if (event.data?.type === IframeEventType.Error) { - reject(event.data["value"]); - } - }, - false - ); + this.messageChannel.port1.postMessage({ + type: IframeEventType.StampRequest, + value: payload, + }) + + return new Promise((resolve, reject) => { + this.messageChannel.port1.onmessage = (event) => { + if (event.data?.type === IframeEventType.Stamp) { + resolve({ + stampHeaderName: stampHeaderName, + stampHeaderValue: event.data["value"], + }); + } + if (event.data?.type === IframeEventType.Error) { + reject(event.data["value"]); + } + } }); } } diff --git a/packages/sdk-browser/package.json b/packages/sdk-browser/package.json index 3477dc887..6526fb360 100644 --- a/packages/sdk-browser/package.json +++ b/packages/sdk-browser/package.json @@ -13,7 +13,7 @@ }, "types": "./dist/index.d.ts", "license": "Apache-2.0", - "description": "Javascript Browser SDK", + "description": "JavaScript Browser SDK", "keywords": [ "Turnkey" ], diff --git a/packages/sdk-browser/src/__types__/base.ts b/packages/sdk-browser/src/__types__/base.ts index 54f33ae39..e7ee2db74 100644 --- a/packages/sdk-browser/src/__types__/base.ts +++ b/packages/sdk-browser/src/__types__/base.ts @@ -91,6 +91,7 @@ export interface TurnkeySDKBrowserConfig { defaultOrganizationId: string; rpId?: string; serverSignUrl?: string; + iframeUrl?: string; } export type queryOverrideParams = { diff --git a/packages/sdk-react/src/contexts/TurnkeyContext.tsx b/packages/sdk-react/src/contexts/TurnkeyContext.tsx index e185a7d85..15565fc41 100644 --- a/packages/sdk-react/src/contexts/TurnkeyContext.tsx +++ b/packages/sdk-react/src/contexts/TurnkeyContext.tsx @@ -123,7 +123,7 @@ export const TurnkeyProvider: React.FC = ({ iframeContainer: document.getElementById( TurnkeyAuthIframeContainerId ), - iframeUrl: "https://auth.turnkey.com", + iframeUrl: config.iframeUrl || "https://auth.turnkey.com", iframeElementId: TurnkeyAuthIframeElementId, }); setAuthIframeClient(newAuthIframeClient); diff --git a/packages/sdk-server/package.json b/packages/sdk-server/package.json index c2b0ee236..b58642748 100644 --- a/packages/sdk-server/package.json +++ b/packages/sdk-server/package.json @@ -13,7 +13,7 @@ }, "types": "./dist/index.d.ts", "license": "Apache-2.0", - "description": "Javascript Server SDK", + "description": "JavaScript Server SDK", "keywords": [ "Turnkey" ], From 12f2cd3f6068cd8e1018eb2612aec291ab7e9b5d Mon Sep 17 00:00:00 2001 From: Brian Cleary Date: Mon, 9 Dec 2024 16:38:42 -0500 Subject: [PATCH 2/8] prettier --- packages/iframe-stamper/src/index.ts | 64 ++++++++++++++-------------- 1 file changed, 31 insertions(+), 33 deletions(-) diff --git a/packages/iframe-stamper/src/index.ts b/packages/iframe-stamper/src/index.ts index 3447211bd..496da3fbc 100644 --- a/packages/iframe-stamper/src/index.ts +++ b/packages/iframe-stamper/src/index.ts @@ -138,7 +138,7 @@ export class IframeStamper { // 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) + iframe.setAttribute("data-trusted-origin", this.trustedParentOrigin); iframe.id = config.iframeElementId; iframe.src = config.iframeUrl; @@ -160,16 +160,18 @@ export class IframeStamper { 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, [this.messageChannel.port2]); - }) - + this.iframe.contentWindow?.postMessage("init", this.iframeOrigin, [ + this.messageChannel.port2, + ]); + }); + return new Promise((resolve, _reject) => { this.messageChannel.port1.onmessage = (event) => { if (event.data?.type === IframeEventType.PublicKeyReady) { this.iframePublicKey = event.data["value"]; resolve(event.data["value"]); } - } + }; }); } @@ -198,7 +200,7 @@ export class IframeStamper { this.messageChannel.port1.postMessage({ type: IframeEventType.InjectCredentialBundle, value: bundle, - }) + }); this.messageChannel.port1.onmessage = (event) => { if (event.data?.type === IframeEventType.BundleInjected) { resolve(event.data["value"]); @@ -206,7 +208,7 @@ export class IframeStamper { if (event.data?.type === IframeEventType.Error) { reject(event.data["value"]); } - } + }; }); } @@ -233,14 +235,14 @@ export class IframeStamper { ); return new Promise((resolve, reject) => { - this.messageChannel.port1.onmessage = (event) => { + this.messageChannel.port1.onmessage = (event) => { if (event.data?.type === IframeEventType.BundleInjected) { resolve(event.data["value"]); } if (event.data?.type === IframeEventType.Error) { reject(event.data["value"]); } - } + }; }); } @@ -258,7 +260,7 @@ export class IframeStamper { type: IframeEventType.InjectWalletExportBundle, value: bundle, organizationId, - }) + }); return new Promise((resolve, reject) => { this.messageChannel.port1.onmessage = (event) => { @@ -268,7 +270,7 @@ export class IframeStamper { if (event.data?.type === IframeEventType.Error) { reject(event.data["value"]); } - } + }; }); } @@ -281,14 +283,12 @@ export class IframeStamper { organizationId: string, userId: string ): Promise { - this.messageChannel.port1.postMessage( - { - type: IframeEventType.InjectImportBundle, - value: bundle, - organizationId, - userId, - }, - ); + this.messageChannel.port1.postMessage({ + type: IframeEventType.InjectImportBundle, + value: bundle, + organizationId, + userId, + }); return new Promise((resolve, reject) => { this.messageChannel.port1.onmessage = (event) => { @@ -298,7 +298,7 @@ export class IframeStamper { if (event.data?.type === IframeEventType.Error) { reject(event.data["value"]); } - } + }; }); } @@ -309,11 +309,9 @@ export class IframeStamper { * This is used during the wallet import flow. */ async extractWalletEncryptedBundle(): Promise { - this.messageChannel.port1.postMessage( - { - type: IframeEventType.ExtractWalletEncryptedBundle, - }, - ); + this.messageChannel.port1.postMessage({ + type: IframeEventType.ExtractWalletEncryptedBundle, + }); return new Promise((resolve, reject) => { this.messageChannel.port1.onmessage = (event) => { @@ -323,7 +321,7 @@ export class IframeStamper { if (event.data?.type === IframeEventType.Error) { reject(event.data["value"]); } - } + }; }); } @@ -348,7 +346,7 @@ export class IframeStamper { if (event.data?.type === IframeEventType.Error) { reject(event.data["value"]); } - } + }; }); } @@ -361,7 +359,7 @@ export class IframeStamper { this.messageChannel.port1.postMessage({ type: IframeEventType.ApplySettings, value: settingsStr, - }) + }); return new Promise((resolve, reject) => { this.messageChannel.port1.onmessage = (event) => { @@ -371,8 +369,8 @@ export class IframeStamper { if (event.data?.type === IframeEventType.Error) { reject(event.data["value"]); } - } - }) + }; + }); } /** @@ -388,8 +386,8 @@ export class IframeStamper { this.messageChannel.port1.postMessage({ type: IframeEventType.StampRequest, value: payload, - }) - + }); + return new Promise((resolve, reject) => { this.messageChannel.port1.onmessage = (event) => { if (event.data?.type === IframeEventType.Stamp) { @@ -401,7 +399,7 @@ export class IframeStamper { if (event.data?.type === IframeEventType.Error) { reject(event.data["value"]); } - } + }; }); } } From a2c45af3a5e70d19719ab326252134240cdf5b54 Mon Sep 17 00:00:00 2001 From: Brian Cleary Date: Tue, 17 Dec 2024 12:02:21 -0500 Subject: [PATCH 3/8] refactor message handlers --- packages/iframe-stamper/src/index.ts | 135 ++++++++++----------------- 1 file changed, 49 insertions(+), 86 deletions(-) diff --git a/packages/iframe-stamper/src/index.ts b/packages/iframe-stamper/src/index.ts index 496da3fbc..648c8cb48 100644 --- a/packages/iframe-stamper/src/index.ts +++ b/packages/iframe-stamper/src/index.ts @@ -47,6 +47,9 @@ export enum IframeEventType { // Event sent by the iframe to communicate the result of a stamp operation. // Value: signed payload Stamp = "STAMP", + // Event sent by the parent to establish secure communication via MessageChannel API. + // Value: MessageChannel port + TurnkeyInitMessageChannel = "TURNKEY_INIT_MESSAGE_CHANNEL", // Event sent by the iframe to communicate an error // Value: serialized error Error = "ERROR", @@ -121,6 +124,10 @@ export class IframeStamper { throw new Error("Cannot initialize iframe in non-browser environment"); } + if (typeof MessageChannel === "undefined") { + throw new Error("Cannot initialize iframe without MessageChannel support"); + } + if (!config.iframeContainer) { throw new Error("Iframe container cannot be found"); } @@ -159,8 +166,9 @@ export class IframeStamper { async init(): Promise { this.container.appendChild(this.iframe); this.iframe.addEventListener("load", () => { + console.log('sdk init() iframe DOMContentLoaded callback'); // Send a message to the iframe to initialize the message channel - this.iframe.contentWindow?.postMessage("init", this.iframeOrigin, [ + this.iframe.contentWindow?.postMessage({ type: IframeEventType.TurnkeyInitMessageChannel }, this.iframeOrigin, [ this.messageChannel.port2, ]); }); @@ -179,6 +187,8 @@ export class IframeStamper { * Removes the iframe from the DOM */ clear() { + this.messageChannel.port1.close(); + this.messageChannel.port2.close(); this.iframe.remove(); } @@ -189,6 +199,33 @@ export class IframeStamper { return this.iframePublicKey; } + /** + * Adds a message handler to the iframe's message channel + */ + addMessageHandler() : Promise { + return new Promise((resolve, reject) => { + this.messageChannel.port1.onmessage = (event) => { + this.onMessageHandler(event, resolve, reject); + }; + }); + } + + onMessageHandler(event: MessageEvent, resolve: any, reject: any): void { + switch (event.data?.type) { + case IframeEventType.Stamp: + resolve({ + stampHeaderName: stampHeaderName, + stampHeaderValue: event.data["value"], + }); + break; + case IframeEventType.Error: + reject(event.data["value"]); + break; + default: + resolve(event.data["value"]); + } + } + /** * Function to inject a new credential into the iframe * The bundle should be encrypted to the iframe's initial public key @@ -202,12 +239,7 @@ export class IframeStamper { value: bundle, }); this.messageChannel.port1.onmessage = (event) => { - if (event.data?.type === IframeEventType.BundleInjected) { - resolve(event.data["value"]); - } - if (event.data?.type === IframeEventType.Error) { - reject(event.data["value"]); - } + this.onMessageHandler(event, resolve, reject); }; }); } @@ -224,26 +256,14 @@ export class IframeStamper { organizationId: string, keyFormat?: KeyFormat ): Promise { - this.iframe.contentWindow?.postMessage( - { - type: IframeEventType.InjectKeyExportBundle, + this.messageChannel.port1.postMessage({ + type: IframeEventType.InjectKeyExportBundle, value: bundle, keyFormat, organizationId, - }, - this.iframeOrigin - ); - - return new Promise((resolve, reject) => { - this.messageChannel.port1.onmessage = (event) => { - if (event.data?.type === IframeEventType.BundleInjected) { - resolve(event.data["value"]); - } - if (event.data?.type === IframeEventType.Error) { - reject(event.data["value"]); - } - }; }); + + return this.addMessageHandler(); } /** @@ -262,16 +282,7 @@ export class IframeStamper { organizationId, }); - return new Promise((resolve, reject) => { - this.messageChannel.port1.onmessage = (event) => { - if (event.data?.type === IframeEventType.BundleInjected) { - resolve(event.data["value"]); - } - if (event.data?.type === IframeEventType.Error) { - reject(event.data["value"]); - } - }; - }); + return this.addMessageHandler(); } /** @@ -290,16 +301,7 @@ export class IframeStamper { userId, }); - return new Promise((resolve, reject) => { - this.messageChannel.port1.onmessage = (event) => { - if (event.data?.type === IframeEventType.BundleInjected) { - resolve(event.data["value"]); - } - if (event.data?.type === IframeEventType.Error) { - reject(event.data["value"]); - } - }; - }); + return this.addMessageHandler(); } /** @@ -313,16 +315,7 @@ export class IframeStamper { type: IframeEventType.ExtractWalletEncryptedBundle, }); - return new Promise((resolve, reject) => { - this.messageChannel.port1.onmessage = (event) => { - if (event.data?.type === IframeEventType.EncryptedBundleExtracted) { - resolve(event.data["value"]); - } - if (event.data?.type === IframeEventType.Error) { - reject(event.data["value"]); - } - }; - }); + return this.addMessageHandler(); } /** @@ -338,16 +331,7 @@ export class IframeStamper { keyFormat: keyFormat, }); - return new Promise((resolve, reject) => { - this.messageChannel.port1.onmessage = (event) => { - if (event.data?.type === IframeEventType.EncryptedBundleExtracted) { - resolve(event.data["value"]); - } - if (event.data?.type === IframeEventType.Error) { - reject(event.data["value"]); - } - }; - }); + return this.addMessageHandler(); } /** @@ -361,16 +345,7 @@ export class IframeStamper { value: settingsStr, }); - return new Promise((resolve, reject) => { - this.messageChannel.port1.onmessage = (event) => { - if (event.data?.type === IframeEventType.SettingsApplied) { - resolve(event.data["value"]); - } - if (event.data?.type === IframeEventType.Error) { - reject(event.data["value"]); - } - }; - }); + return this.addMessageHandler(); } /** @@ -388,18 +363,6 @@ export class IframeStamper { value: payload, }); - return new Promise((resolve, reject) => { - this.messageChannel.port1.onmessage = (event) => { - if (event.data?.type === IframeEventType.Stamp) { - resolve({ - stampHeaderName: stampHeaderName, - stampHeaderValue: event.data["value"], - }); - } - if (event.data?.type === IframeEventType.Error) { - reject(event.data["value"]); - } - }; - }); + return this.addMessageHandler(); } } From c9d54e6cb37b9099e9c3b204717d7899667b7dfe Mon Sep 17 00:00:00 2001 From: Brian Cleary Date: Wed, 18 Dec 2024 12:33:19 -0500 Subject: [PATCH 4/8] prettier updates --- packages/iframe-stamper/src/index.ts | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/packages/iframe-stamper/src/index.ts b/packages/iframe-stamper/src/index.ts index 648c8cb48..f388ea8ab 100644 --- a/packages/iframe-stamper/src/index.ts +++ b/packages/iframe-stamper/src/index.ts @@ -125,7 +125,9 @@ export class IframeStamper { } if (typeof MessageChannel === "undefined") { - throw new Error("Cannot initialize iframe without MessageChannel support"); + throw new Error( + "Cannot initialize iframe without MessageChannel support" + ); } if (!config.iframeContainer) { @@ -166,11 +168,13 @@ export class IframeStamper { async init(): Promise { this.container.appendChild(this.iframe); this.iframe.addEventListener("load", () => { - console.log('sdk init() iframe DOMContentLoaded callback'); + console.log("sdk init() iframe DOMContentLoaded callback"); // Send a message to the iframe to initialize the message channel - this.iframe.contentWindow?.postMessage({ type: IframeEventType.TurnkeyInitMessageChannel }, this.iframeOrigin, [ - this.messageChannel.port2, - ]); + this.iframe.contentWindow?.postMessage( + { type: IframeEventType.TurnkeyInitMessageChannel }, + this.iframeOrigin, + [this.messageChannel.port2] + ); }); return new Promise((resolve, _reject) => { @@ -202,7 +206,7 @@ export class IframeStamper { /** * Adds a message handler to the iframe's message channel */ - addMessageHandler() : Promise { + addMessageHandler(): Promise { return new Promise((resolve, reject) => { this.messageChannel.port1.onmessage = (event) => { this.onMessageHandler(event, resolve, reject); @@ -258,9 +262,9 @@ export class IframeStamper { ): Promise { this.messageChannel.port1.postMessage({ type: IframeEventType.InjectKeyExportBundle, - value: bundle, - keyFormat, - organizationId, + value: bundle, + keyFormat, + organizationId, }); return this.addMessageHandler(); From 16907cd248d3c3dee6f7c4576785c5dd490b7ba1 Mon Sep 17 00:00:00 2001 From: Brian Cleary Date: Wed, 18 Dec 2024 12:40:37 -0500 Subject: [PATCH 5/8] remove data-attribute --- packages/iframe-stamper/src/index.ts | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/packages/iframe-stamper/src/index.ts b/packages/iframe-stamper/src/index.ts index f388ea8ab..1a7d10aed 100644 --- a/packages/iframe-stamper/src/index.ts +++ b/packages/iframe-stamper/src/index.ts @@ -112,7 +112,6 @@ export class IframeStamper { iframe: HTMLIFrameElement; iframeOrigin: string; iframePublicKey: string | null; - trustedParentOrigin: string; messageChannel: MessageChannel; /** @@ -143,11 +142,9 @@ export class IframeStamper { let iframe = window.document.createElement("iframe"); - 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); + iframe.setAttribute("sandbox", "allow-scripts allow-same-origin"); iframe.id = config.iframeElementId; iframe.src = config.iframeUrl; @@ -191,8 +188,8 @@ export class IframeStamper { * Removes the iframe from the DOM */ clear() { - this.messageChannel.port1.close(); - this.messageChannel.port2.close(); + this.messageChannel?.port1?.close(); + this.messageChannel?.port2?.close(); this.iframe.remove(); } From 35270743ac13e8214b6afab37bccfe6b22c023bf Mon Sep 17 00:00:00 2001 From: Brian Cleary Date: Wed, 18 Dec 2024 12:54:33 -0500 Subject: [PATCH 6/8] add comments to code --- packages/iframe-stamper/src/index.ts | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/packages/iframe-stamper/src/index.ts b/packages/iframe-stamper/src/index.ts index 1a7d10aed..c8f5ca318 100644 --- a/packages/iframe-stamper/src/index.ts +++ b/packages/iframe-stamper/src/index.ts @@ -144,7 +144,7 @@ export class IframeStamper { // 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("sandbox", "allow-scripts allow-same-origin"); iframe.id = config.iframeElementId; iframe.src = config.iframeUrl; @@ -156,6 +156,10 @@ export class IframeStamper { // This is populated once the iframe is ready. Call `.init()` to kick off DOM insertion! this.iframePublicKey = null; + /** + * The MessageChannel API is used to establish secure communication between the parent page and the iframe. + * See https://developer.mozilla.org/en-US/docs/Web/API/MessageChannel + */ this.messageChannel = new MessageChannel(); } @@ -164,9 +168,13 @@ export class IframeStamper { */ async init(): Promise { this.container.appendChild(this.iframe); + /** + * Once the iframe is loaded, we send a message to the iframe to hand over the + * MessageChannel's second port, port2, and establish the secure communication channel. + * The iframe will use this port to send messages back to the parent page. + * See https://developer.mozilla.org/en-US/docs/Web/API/MessagePort/postMessage#transfer + */ this.iframe.addEventListener("load", () => { - console.log("sdk init() iframe DOMContentLoaded callback"); - // Send a message to the iframe to initialize the message channel this.iframe.contentWindow?.postMessage( { type: IframeEventType.TurnkeyInitMessageChannel }, this.iframeOrigin, From fad7c37c3f66dd743f02cff7609976616e396e6d Mon Sep 17 00:00:00 2001 From: Brian Cleary Date: Wed, 18 Dec 2024 12:58:35 -0500 Subject: [PATCH 7/8] add changeset --- .changeset/tasty-singers-tan.md | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 .changeset/tasty-singers-tan.md diff --git a/.changeset/tasty-singers-tan.md b/.changeset/tasty-singers-tan.md new file mode 100644 index 000000000..0e9056d90 --- /dev/null +++ b/.changeset/tasty-singers-tan.md @@ -0,0 +1,10 @@ +--- +"@turnkey/iframe-stamper": minor +"@turnkey/sdk-browser": patch +"@turnkey/sdk-server": patch +--- + +@turnkey/iframe-stamper - Implemented MessageChannel API for secure communication between the parent and iframe. + +@turnkey/sdk-browser - fixed spelling in package.json +@turnkey/sdk-server - fixed spelling in package.json From 4139ebb3267c4f2ba081e327e87f0fe64c62b04a Mon Sep 17 00:00:00 2001 From: Brian Cleary Date: Wed, 18 Dec 2024 13:19:10 -0500 Subject: [PATCH 8/8] prettier updates --- packages/iframe-stamper/src/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/iframe-stamper/src/index.ts b/packages/iframe-stamper/src/index.ts index c8f5ca318..fead60c9c 100644 --- a/packages/iframe-stamper/src/index.ts +++ b/packages/iframe-stamper/src/index.ts @@ -169,7 +169,7 @@ export class IframeStamper { async init(): Promise { this.container.appendChild(this.iframe); /** - * Once the iframe is loaded, we send a message to the iframe to hand over the + * Once the iframe is loaded, we send a message to the iframe to hand over the * MessageChannel's second port, port2, and establish the secure communication channel. * The iframe will use this port to send messages back to the parent page. * See https://developer.mozilla.org/en-US/docs/Web/API/MessagePort/postMessage#transfer