From eb5e0108e7271b5debfa8a5d71ef37bd64a8a9ed Mon Sep 17 00:00:00 2001 From: Ben Newman Date: Wed, 13 Aug 2025 06:09:58 -0700 Subject: [PATCH 1/2] feat(message-core)!: update AvMessage for new nav --- packages/message-core/src/AvMessage.js | 22 ++- packages/message-core/src/AvMessage.test.js | 180 +++++++++++++++++--- 2 files changed, 174 insertions(+), 28 deletions(-) diff --git a/packages/message-core/src/AvMessage.js b/packages/message-core/src/AvMessage.js index a97d18d7a..ccacbc429 100644 --- a/packages/message-core/src/AvMessage.js +++ b/packages/message-core/src/AvMessage.js @@ -102,16 +102,19 @@ class AvMessage { } /** - * Attempts to get origin from top window + * Gets the parent origin from referrer * @private * @returns {string|null} */ - getOriginFromTop() { - try { - return window.top.location.origin; - } catch { - return null; + getParentOrigin() { + if (document.referrer) { + try { + return new URL(document.referrer).origin; + } catch { + return null; + } } + return null; } /** @@ -133,10 +136,11 @@ class AvMessage { * @returns {string} */ domain() { - const topOrigin = this.getOriginFromTop(); + const parentOrigin = this.getParentOrigin(); - if (topOrigin) { - return window.location.origin; + if (parentOrigin) { + // Use parent origin if available + return parentOrigin; } if (window.location.origin) { diff --git a/packages/message-core/src/AvMessage.test.js b/packages/message-core/src/AvMessage.test.js index 695216e0d..7db9815ae 100644 --- a/packages/message-core/src/AvMessage.test.js +++ b/packages/message-core/src/AvMessage.test.js @@ -265,37 +265,177 @@ describe('AvMessage', () => { }); describe('domain()', () => { + const originalReferrer = document.referrer; + + afterEach(() => { + Object.defineProperty(document, 'referrer', { + value: originalReferrer, + writable: true, + }); + }); + test('should return location.origin if exists', () => { expect(avMessage.domain()).toBe(TEST_URL); }); - test('domain should return apps.availity.com when window.location.origin is essentials.availity.com window.top.location.origin is inaccessible', () => { - window.top.location = new DOMException('Permission denied to access property "origin" on cross-origin object'); - window.location = new URL('https://qa-essentials.availity.com'); + describe('NEW BEHAVIOR: Uses document.referrer when available', () => { + test('should return parent origin from document.referrer when iframe is on essentials and parent is on essentials', () => { + // Simulate iframe scenario with referrer + Object.defineProperty(document, 'referrer', { + value: 'https://qa-essentials.availity.com/parent-page', + writable: true, + }); + window.location = new URL('https://qa-essentials.availity.com/iframe'); - expect(avMessage.domain()).toEqual('https://qa-apps.availity.com'); - }); + expect(avMessage.domain()).toEqual('https://qa-essentials.availity.com'); + }); - test('domain should return essentials.availity.com when window.location.origin is apps.availity.com window.top.location.origin is inaccessible', () => { - window.top.location = new DOMException('Permission denied to access property "origin" on cross-origin object'); - window.location = new URL('https://qa-apps.availity.com'); + test('should return parent origin from document.referrer when iframe is on apps and parent is on apps', () => { + Object.defineProperty(document, 'referrer', { + value: 'https://qa-apps.availity.com/parent-page', + writable: true, + }); + window.location = new URL('https://qa-apps.availity.com/iframe'); - expect(avMessage.domain()).toEqual('https://qa-essentials.availity.com'); - }); + expect(avMessage.domain()).toEqual('https://qa-apps.availity.com'); + }); - test('domain should return essentials.availity.com when window.location.origin is essentials.availity.com', () => { - window.location = new URL('https://qa-essentials.availity.com'); - window.top.location = new URL('https://qa-essentials.availity.com'); + test('should return parent origin from document.referrer when iframe is on apps but parent is on essentials', () => { + Object.defineProperty(document, 'referrer', { + value: 'https://qa-essentials.availity.com/parent-page', + writable: true, + }); + window.location = new URL('https://qa-apps.availity.com/iframe'); + + expect(avMessage.domain()).toEqual('https://qa-essentials.availity.com'); + }); + test('should return parent origin from document.referrer when iframe is on essentials but parent is on apps', () => { + Object.defineProperty(document, 'referrer', { + value: 'https://qa-apps.availity.com/parent-page', + writable: true, + }); + window.location = new URL('https://qa-essentials.availity.com/iframe'); + + expect(avMessage.domain()).toEqual('https://qa-apps.availity.com'); + }); + }); + + describe('FALLBACK BEHAVIOR: Domain swapping when no referrer', () => { + beforeEach(() => { + Object.defineProperty(document, 'referrer', { + value: '', + writable: true, + }); + }); + + test('should swap essentials to apps when no referrer available', () => { + window.location = new URL('https://qa-essentials.availity.com'); + expect(avMessage.domain()).toEqual('https://qa-apps.availity.com'); + }); + + test('should swap apps to essentials when no referrer available', () => { + window.location = new URL('https://qa-apps.availity.com'); + expect(avMessage.domain()).toEqual('https://qa-essentials.availity.com'); + }); + }); + + describe('EDGE CASES', () => { + test('should handle invalid referrer URL gracefully', () => { + Object.defineProperty(document, 'referrer', { + value: 'invalid-url', + writable: true, + }); + window.location = new URL('https://qa-essentials.availity.com'); + + // Should fall back to domain swapping + expect(avMessage.domain()).toEqual('https://qa-apps.availity.com'); + }); + + test('should return * when no location info available', () => { + Object.defineProperty(document, 'referrer', { + value: '', + writable: true, + }); + Object.defineProperty(window, 'location', { + value: {}, + writable: true, + }); + + expect(avMessage.domain()).toEqual('*'); + }); + }); - expect(avMessage.domain()).toEqual('https://qa-essentials.availity.com'); + describe('PROBLEM SCENARIO: Before fix would cause postMessage errors', () => { + test('OLD BEHAVIOR would have caused postMessage error: essentials iframe -> essentials parent', () => { + // This scenario would have failed before the fix: + // - Iframe on essentials domain + // - Parent also on essentials domain + // - Old code would swap essentials -> apps + // - postMessage would fail with origin mismatch + + Object.defineProperty(document, 'referrer', { + value: 'https://qa-essentials.availity.com/parent', + writable: true, + }); + window.location = new URL('https://qa-essentials.availity.com/iframe'); + + const domain = avMessage.domain(); + + // NEW: Returns correct parent origin (essentials) + expect(domain).toEqual('https://qa-essentials.availity.com'); + + // OLD: Would have returned swapped domain (apps) causing postMessage to fail + // expect(domain).toEqual('https://qa-apps.availity.com'); // This would fail postMessage + }); + }); }); - test('domain should return apps.availity.com when window.location.origin is apps.availity.com', () => { - window.location = new URL('https://qa-apps.availity.com'); - window.top.location = new URL('https://qa-apps.availity.com'); + describe('getParentOrigin()', () => { + const originalReferrer = document.referrer; + + afterEach(() => { + Object.defineProperty(document, 'referrer', { + value: originalReferrer, + writable: true, + }); + }); + + test('should return origin from document.referrer when available', () => { + Object.defineProperty(document, 'referrer', { + value: 'https://qa-essentials.availity.com/some/path?param=value', + writable: true, + }); + + expect(avMessage.getParentOrigin()).toEqual('https://qa-essentials.availity.com'); + }); + + test('should return null when document.referrer is empty', () => { + Object.defineProperty(document, 'referrer', { + value: '', + writable: true, + }); + + expect(avMessage.getParentOrigin()).toBeNull(); + }); + + test('should return null when document.referrer is invalid URL', () => { + Object.defineProperty(document, 'referrer', { + value: 'not-a-valid-url', + writable: true, + }); - expect(avMessage.domain()).toEqual('https://qa-apps.availity.com'); + expect(avMessage.getParentOrigin()).toBeNull(); + }); + + test('should handle different protocols correctly', () => { + Object.defineProperty(document, 'referrer', { + value: 'http://qa-apps.availity.com/path', + writable: true, + }); + + expect(avMessage.getParentOrigin()).toEqual('http://qa-apps.availity.com'); + }); }); }); @@ -325,6 +465,8 @@ describe('AvMessage', () => { beforeEach(() => { avMessage.domain = jest.fn(() => testDomain); + avMessage.isEnabled = true; + mockTarget.postMessage.mockClear(); }); test('should return when not enabled', () => { @@ -359,4 +501,4 @@ describe('AvMessage', () => { expect(mockTarget.postMessage).toHaveBeenCalledWith(JSON.stringify(testMessage), testDomain); }); }); -}); + From 9d78cceb2faa14fa2eb0c9fdea3c0efbd99d1d87 Mon Sep 17 00:00:00 2001 From: Ben Newman Date: Wed, 13 Aug 2025 06:23:04 -0700 Subject: [PATCH 2/2] fix(messge-core): use origin over referrer --- packages/message-core/src/AvMessage.js | 26 ++--- packages/message-core/src/AvMessage.test.js | 123 +++++++------------- 2 files changed, 53 insertions(+), 96 deletions(-) diff --git a/packages/message-core/src/AvMessage.js b/packages/message-core/src/AvMessage.js index ccacbc429..7980377c0 100644 --- a/packages/message-core/src/AvMessage.js +++ b/packages/message-core/src/AvMessage.js @@ -102,19 +102,16 @@ class AvMessage { } /** - * Gets the parent origin from referrer + * Attempts to get origin from top window * @private * @returns {string|null} */ - getParentOrigin() { - if (document.referrer) { - try { - return new URL(document.referrer).origin; - } catch { - return null; - } + getOriginFromTop() { + try { + return window.top.location.origin; + } catch { + return null; } - return null; } /** @@ -136,16 +133,16 @@ class AvMessage { * @returns {string} */ domain() { - const parentOrigin = this.getParentOrigin(); + const topOrigin = this.getOriginFromTop(); - if (parentOrigin) { - // Use parent origin if available - return parentOrigin; + if (topOrigin) { + // If we can access top origin, use it directly (same domain scenario) + return topOrigin; } + // Cross-domain scenario - fall back to domain swapping if (window.location.origin) { const url = window.location.origin; - return this.swapDomain(url); } @@ -153,7 +150,6 @@ class AvMessage { const url = `${window.location.protocol}//${window.location.hostname}${ window.location.port ? `:${window.location.port}` : '' }`; - return this.swapDomain(url); } diff --git a/packages/message-core/src/AvMessage.test.js b/packages/message-core/src/AvMessage.test.js index 7db9815ae..09ac65fcd 100644 --- a/packages/message-core/src/AvMessage.test.js +++ b/packages/message-core/src/AvMessage.test.js @@ -278,90 +278,65 @@ describe('AvMessage', () => { expect(avMessage.domain()).toBe(TEST_URL); }); - describe('NEW BEHAVIOR: Uses document.referrer when available', () => { - test('should return parent origin from document.referrer when iframe is on essentials and parent is on essentials', () => { - // Simulate iframe scenario with referrer - Object.defineProperty(document, 'referrer', { - value: 'https://qa-essentials.availity.com/parent-page', - writable: true, - }); + describe('NEW BEHAVIOR: Uses window.top.location.origin when accessible', () => { + test('should return top origin when iframe and parent are on same domain (essentials)', () => { + // Simulate same-domain scenario where window.top.location.origin is accessible window.location = new URL('https://qa-essentials.availity.com/iframe'); - - expect(avMessage.domain()).toEqual('https://qa-essentials.availity.com'); - }); - - test('should return parent origin from document.referrer when iframe is on apps and parent is on apps', () => { - Object.defineProperty(document, 'referrer', { - value: 'https://qa-apps.availity.com/parent-page', + Object.defineProperty(window, 'top', { + value: { location: { origin: 'https://qa-essentials.availity.com' } }, writable: true, }); - window.location = new URL('https://qa-apps.availity.com/iframe'); - - expect(avMessage.domain()).toEqual('https://qa-apps.availity.com'); - }); - - test('should return parent origin from document.referrer when iframe is on apps but parent is on essentials', () => { - Object.defineProperty(document, 'referrer', { - value: 'https://qa-essentials.availity.com/parent-page', - writable: true, - }); - window.location = new URL('https://qa-apps.availity.com/iframe'); expect(avMessage.domain()).toEqual('https://qa-essentials.availity.com'); }); - test('should return parent origin from document.referrer when iframe is on essentials but parent is on apps', () => { - Object.defineProperty(document, 'referrer', { - value: 'https://qa-apps.availity.com/parent-page', + test('should return top origin when iframe and parent are on same domain (apps)', () => { + window.location = new URL('https://qa-apps.availity.com/iframe'); + Object.defineProperty(window, 'top', { + value: { location: { origin: 'https://qa-apps.availity.com' } }, writable: true, }); - window.location = new URL('https://qa-essentials.availity.com/iframe'); expect(avMessage.domain()).toEqual('https://qa-apps.availity.com'); }); }); - describe('FALLBACK BEHAVIOR: Domain swapping when no referrer', () => { + describe('FALLBACK BEHAVIOR: Domain swapping when cross-domain', () => { beforeEach(() => { - Object.defineProperty(document, 'referrer', { - value: '', + // Simulate cross-domain scenario where window.top.location.origin throws + Object.defineProperty(window, 'top', { + value: { + get location() { + throw new DOMException('Permission denied'); + } + }, writable: true, }); }); - test('should swap essentials to apps when no referrer available', () => { + test('should swap essentials to apps when cross-domain', () => { window.location = new URL('https://qa-essentials.availity.com'); expect(avMessage.domain()).toEqual('https://qa-apps.availity.com'); }); - test('should swap apps to essentials when no referrer available', () => { + test('should swap apps to essentials when cross-domain', () => { window.location = new URL('https://qa-apps.availity.com'); expect(avMessage.domain()).toEqual('https://qa-essentials.availity.com'); }); }); describe('EDGE CASES', () => { - test('should handle invalid referrer URL gracefully', () => { - Object.defineProperty(document, 'referrer', { - value: 'invalid-url', - writable: true, - }); + test('should fall back to domain swapping when top origin access fails', () => { window.location = new URL('https://qa-essentials.availity.com'); - - // Should fall back to domain swapping + // top.location access already set to throw in beforeEach expect(avMessage.domain()).toEqual('https://qa-apps.availity.com'); }); test('should return * when no location info available', () => { - Object.defineProperty(document, 'referrer', { - value: '', - writable: true, - }); Object.defineProperty(window, 'location', { value: {}, writable: true, }); - expect(avMessage.domain()).toEqual('*'); }); }); @@ -371,18 +346,18 @@ describe('AvMessage', () => { // This scenario would have failed before the fix: // - Iframe on essentials domain // - Parent also on essentials domain - // - Old code would swap essentials -> apps + // - Old code would use window.location.origin and swap essentials -> apps // - postMessage would fail with origin mismatch - Object.defineProperty(document, 'referrer', { - value: 'https://qa-essentials.availity.com/parent', + window.location = new URL('https://qa-essentials.availity.com/iframe'); + Object.defineProperty(window, 'top', { + value: { location: { origin: 'https://qa-essentials.availity.com' } }, writable: true, }); - window.location = new URL('https://qa-essentials.availity.com/iframe'); const domain = avMessage.domain(); - // NEW: Returns correct parent origin (essentials) + // NEW: Returns correct top origin (essentials) when accessible expect(domain).toEqual('https://qa-essentials.availity.com'); // OLD: Would have returned swapped domain (apps) causing postMessage to fail @@ -391,50 +366,36 @@ describe('AvMessage', () => { }); }); - describe('getParentOrigin()', () => { - const originalReferrer = document.referrer; - - afterEach(() => { - Object.defineProperty(document, 'referrer', { - value: originalReferrer, + describe('getOriginFromTop()', () => { + test('should return origin from window.top.location when accessible', () => { + Object.defineProperty(window, 'top', { + value: { location: { origin: 'https://qa-essentials.availity.com' } }, writable: true, }); - }); - test('should return origin from document.referrer when available', () => { - Object.defineProperty(document, 'referrer', { - value: 'https://qa-essentials.availity.com/some/path?param=value', - writable: true, - }); - - expect(avMessage.getParentOrigin()).toEqual('https://qa-essentials.availity.com'); - }); - - test('should return null when document.referrer is empty', () => { - Object.defineProperty(document, 'referrer', { - value: '', - writable: true, - }); - - expect(avMessage.getParentOrigin()).toBeNull(); + expect(avMessage.getOriginFromTop()).toEqual('https://qa-essentials.availity.com'); }); - test('should return null when document.referrer is invalid URL', () => { - Object.defineProperty(document, 'referrer', { - value: 'not-a-valid-url', + test('should return null when window.top.location access throws', () => { + Object.defineProperty(window, 'top', { + value: { + get location() { + throw new DOMException('Permission denied'); + } + }, writable: true, }); - expect(avMessage.getParentOrigin()).toBeNull(); + expect(avMessage.getOriginFromTop()).toBeNull(); }); test('should handle different protocols correctly', () => { - Object.defineProperty(document, 'referrer', { - value: 'http://qa-apps.availity.com/path', + Object.defineProperty(window, 'top', { + value: { location: { origin: 'http://qa-apps.availity.com' } }, writable: true, }); - expect(avMessage.getParentOrigin()).toEqual('http://qa-apps.availity.com'); + expect(avMessage.getOriginFromTop()).toEqual('http://qa-apps.availity.com'); }); }); });