From 3c16209cb492503471f0c8079e0c9e80427f1367 Mon Sep 17 00:00:00 2001 From: kissel Date: Tue, 11 Jun 2024 12:11:53 +0200 Subject: [PATCH] - require a target window only when posting a message (not when receiving), because that will not cause issues on the Cadenza end and can be handled by the custom app (by unsubscribing all subscribers) - add CadenzaClient#destroy for conveniently unsubscribing all subscribers (cherry picked from commit 9233dd55410c2437a83754457b50033621dcf6ed) --- CHANGELOG.md | 5 +++++ src/cadenza.js | 26 ++++++++++++++++++-------- src/cadenza.test.ts | 12 +++++++++++- 3 files changed, 34 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fa3c5e07..56bc1f41 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,11 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## Unreleased +### Added +- `CadenzaClient#destroy` + +### Fixed +- To avoid errors with active subscriptions when the iframe is not visible, a target window (for postMessage communication) is now required only for _sending_ a message (and not for receiving messages). ## 2.6.0 - 2024-04-12 ### Added diff --git a/src/cadenza.js b/src/cadenza.js index 3a7afcd9..327d7180 100644 --- a/src/cadenza.js +++ b/src/cadenza.js @@ -195,6 +195,10 @@ export class CadenzaClient { return this.#iframeElement; } + get #targetWindow() { + return /** @type {WindowProxy | null} */ this.iframe?.contentWindow; + } + get #requiredIframe() { const iframe = /** @type {HTMLIFrameElement} */ (this.iframe); assert( @@ -683,10 +687,7 @@ export class CadenzaClient { #onMessage = ( /** @type MessageEvent> */ event, ) => { - if ( - event.origin !== this.#origin || - event.source !== this.#requiredIframe.contentWindow - ) { + if (event.origin !== this.#origin || event.source !== this.#targetWindow) { return; } @@ -699,6 +700,16 @@ export class CadenzaClient { }); }; + /** + * Remove all subscriptions. + * + * @see {@link CadenzaClient#on} + */ + destroy() { + this.#subscriptions = []; + window.removeEventListener('message', this.#onMessage); + } + /** * Posts an event to Cadenza and returns a `Promise` for the response. * @@ -735,10 +746,9 @@ export class CadenzaClient { #postEvent(type, detail, transfer) { const cadenzaEvent = { type, detail }; this.#log('postMessage', cadenzaEvent); - const contentWindow = /** @type {WindowProxy} */ ( - this.#requiredIframe.contentWindow - ); - contentWindow.postMessage(cadenzaEvent, { + const targetWindow = this.#targetWindow; + assert(targetWindow != null, 'Cannot find target window'); + (/** @type {WindowProxy} */ (targetWindow)).postMessage(cadenzaEvent, { targetOrigin: this.#origin, transfer, }); diff --git a/src/cadenza.test.ts b/src/cadenza.test.ts index 6d70986c..43490593 100644 --- a/src/cadenza.test.ts +++ b/src/cadenza.test.ts @@ -64,7 +64,7 @@ describe('Given a Cadenza JS client instance', () => { it('Throws when attempting to show an embedding target without an iframe', () => expect(() => cadenza(BASE_URL).show(EMBEDDING_TARGET_ID)).toThrow( - 'present', + 'Required iframe', )); it('Throws when attempting to show an embedding target in an invisible iframe', () => { @@ -219,6 +219,16 @@ describe('Given a Cadenza JS client instance', () => { expect(subscriber).not.toHaveBeenCalled()); }); + describe('When the event is sent again after the instance has been destroyed', () => { + beforeEach(() => { + cad.destroy(); + sendEvent(EVENT.type, EVENT.detail); + }); + + it('Subscribers are not called anymore', () => + expect(subscriber).not.toHaveBeenCalled()); + }); + describe('When the event is sent from another origin', () => { beforeEach(() => sendEvent(EVENT.type, EVENT.detail, 'http://disy.net'));