From b3fa1038d8417a49b7b70b0b04521945986cbff5 Mon Sep 17 00:00:00 2001 From: Daniel Winter Date: Wed, 4 Mar 2026 13:22:33 +0100 Subject: [PATCH] Fix: Use wildcard origin for postMessage in sendReadyMessage Fixes the SyntaxError that occurred when wildcard patterns (e.g., http://localhost:*, https://*.hygraph.com) were used directly as targetOrigin in postMessage calls. Changes: - Modified sendReadyMessage to use "*" as targetOrigin instead of iterating through allowedOrigins patterns - Security is maintained through origin validation in isOriginAllowed() - Updated tests to reflect the new behavior - Added tests for wildcard origin pattern validation - Bumped version to 1.0.5 The postMessage API only accepts specific origins or "*", not pattern strings with wildcards. Using "*" for the initial ready message is safe because we validate all incoming messages against the wildcard patterns in allowedOrigins. Co-Authored-By: Claude Sonnet 4.5 --- package.json | 2 +- src/core/MessageBridge.test.ts | 71 ++++++++++++++++++++++++++++++++-- src/core/MessageBridge.ts | 30 ++++++-------- src/core/Preview.ts | 2 +- 4 files changed, 81 insertions(+), 24 deletions(-) diff --git a/package.json b/package.json index 04b1061..5e34fa6 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@hygraph/preview-sdk", - "version": "1.0.4", + "version": "1.0.5", "description": "Content preview SDK for seamless real-time content editing in Hygraph CMS", "publishConfig": { "access": "public" diff --git a/src/core/MessageBridge.test.ts b/src/core/MessageBridge.test.ts index fa0f1fd..000b2a6 100644 --- a/src/core/MessageBridge.test.ts +++ b/src/core/MessageBridge.test.ts @@ -53,7 +53,7 @@ describe('MessageBridge', () => { expect(bridge.isConnectedToStudio()).toBe(true); }); - it('broadcasts ready message to all allowed origins', () => { + it('sends ready message to any parent window using wildcard origin', () => { bridge = new MessageBridge({ allowedOrigins, debug: false, @@ -69,9 +69,9 @@ describe('MessageBridge', () => { bridge.sendReadyMessage(readyMessage); - expect(mockBridge.postMessage).toHaveBeenCalledTimes(allowedOrigins.length); - const targets = mockBridge.messages.map(({ targetOrigin }) => targetOrigin); - expect(targets).toEqual(expect.arrayContaining(allowedOrigins)); + // Should send once with wildcard origin to reach any parent + expect(mockBridge.postMessage).toHaveBeenCalledTimes(1); + expect(mockBridge.postMessage).toHaveBeenCalledWith(readyMessage, '*'); }); it('ignores messages from disallowed origins', () => { @@ -120,5 +120,68 @@ describe('MessageBridge', () => { expect(onMessage).toHaveBeenCalledWith(validMessage); }); + + it('accepts messages from origins matching wildcard patterns', () => { + bridge = new MessageBridge({ + allowedOrigins: ['https://*.hygraph.com', 'http://localhost:*'], + debug: false, + onMessage, + }); + + // Test wildcard domain pattern + mockBridge.dispatch( + { + type: 'init', + studioOrigin: 'https://app.hygraph.com', + timestamp: Date.now(), + }, + 'https://app.hygraph.com' + ); + + expect(onMessage).toHaveBeenCalled(); + expect(bridge.isConnectedToStudio()).toBe(true); + onMessage.mockClear(); + + // Test wildcard port pattern + const bridge2 = new MessageBridge({ + allowedOrigins: ['http://localhost:*'], + debug: false, + onMessage, + }); + + mockBridge.dispatch( + { + type: 'init', + studioOrigin: 'http://localhost:3000', + timestamp: Date.now(), + }, + 'http://localhost:3000' + ); + + expect(onMessage).toHaveBeenCalled(); + expect(bridge2.isConnectedToStudio()).toBe(true); + + bridge2.destroy(); + }); + + it('rejects messages from origins not matching wildcard patterns', () => { + bridge = new MessageBridge({ + allowedOrigins: ['https://*.hygraph.com'], + debug: false, + onMessage, + }); + + mockBridge.dispatch( + { + type: 'init', + studioOrigin: 'https://malicious.example.com', + timestamp: Date.now(), + }, + 'https://malicious.example.com' + ); + + expect(onMessage).not.toHaveBeenCalled(); + expect(bridge.isConnectedToStudio()).toBe(false); + }); }); diff --git a/src/core/MessageBridge.ts b/src/core/MessageBridge.ts index 23318b0..d281aac 100644 --- a/src/core/MessageBridge.ts +++ b/src/core/MessageBridge.ts @@ -57,26 +57,20 @@ export class MessageBridge { sendReadyMessage(message: SDKMessage & { type: 'ready' }): boolean { if (this.isDestroyed) return false; - let sentSuccessfully = false; - - // Try sending to each allowed origin - for (const origin of this.config.allowedOrigins) { - try { - window.parent.postMessage(message, origin); - - if (this.config.debug) { - console.log('[MessageBridge] Sent ready message to origin:', origin, 'message:', message); - } - - sentSuccessfully = true; - } catch (error) { - if (this.config.debug) { - console.log('[MessageBridge] Failed to send ready message to origin:', origin, 'error:', error); - } + try { + // Use "*" as targetOrigin to send to any parent window + // Security is maintained by validating incoming messages via isOriginAllowed() + window.parent.postMessage(message, '*'); + + if (this.config.debug) { + console.log('[MessageBridge] Sent ready message to parent window, message:', message); } - } - return sentSuccessfully; + return true; + } catch (error) { + console.error('[MessageBridge] Failed to send ready message to origin: * error:', error); + return false; + } } /** diff --git a/src/core/Preview.ts b/src/core/Preview.ts index 0c9c03d..986edce 100644 --- a/src/core/Preview.ts +++ b/src/core/Preview.ts @@ -88,7 +88,7 @@ export class Preview { * Get current SDK version */ getVersion(): string { - return '2.0.0'; + return '1.0.5'; } /**