From 307bc1d6b6fe78a01dd4cd3cd50d5d464417cce7 Mon Sep 17 00:00:00 2001 From: Andrew Kushnir Date: Tue, 23 Apr 2024 18:15:32 -0700 Subject: [PATCH] refactor(platform-server): event contract script should follow event dispatch script (#55502) This commit fixes an issue where event contract init script was injected into the page before the inlined event dispatch script. That resulted in runtime exceptions, since event contract relies on some code being present on a page already. PR Close #55502 --- packages/platform-server/src/utils.ts | 33 ++++++++++++++----- .../platform-server/test/event_replay_spec.ts | 24 +++++++++----- 2 files changed, 40 insertions(+), 17 deletions(-) diff --git a/packages/platform-server/src/utils.ts b/packages/platform-server/src/utils.ts index 7a1733749b9..a78961a7da8 100644 --- a/packages/platform-server/src/utils.ts +++ b/packages/platform-server/src/utils.ts @@ -9,6 +9,7 @@ import { APP_ID, ApplicationRef, + CSP_NONCE, InjectionToken, PlatformRef, Provider, @@ -19,7 +20,6 @@ import { ɵIS_HYDRATION_DOM_REUSE_ENABLED as IS_HYDRATION_DOM_REUSE_ENABLED, ɵSSR_CONTENT_INTEGRITY_MARKER as SSR_CONTENT_INTEGRITY_MARKER, ɵwhenStable as whenStable, - CSP_NONCE, } from '@angular/core'; import {PlatformState} from './platform_state'; @@ -55,12 +55,20 @@ function createServerPlatform(options: PlatformOptions): PlatformRef { ]); } +/** + * Finds and returns inlined event dispatch script if it exists. + * See the `EVENT_DISPATCH_SCRIPT_ID` const docs for additional info. + */ +function findEventDispatchScript(doc: Document) { + return doc.getElementById(EVENT_DISPATCH_SCRIPT_ID); +} + /** * Removes inlined event dispatch script if it exists. * See the `EVENT_DISPATCH_SCRIPT_ID` const docs for additional info. */ function removeEventDispatchScript(doc: Document) { - doc.getElementById(EVENT_DISPATCH_SCRIPT_ID)?.remove(); + findEventDispatchScript(doc)?.remove(); } /** @@ -99,13 +107,20 @@ function insertEventRecordScript( eventTypesToBeReplayed: Set, nonce: string | null, ): void { - const events = Array.from(eventTypesToBeReplayed); - // This is defined in packages/core/primitives/event-dispatch/contract_binary.ts - const replayScript = `window.__jsaction_bootstrap('ngContracts', document.body, ${JSON.stringify( - appId, - )}, ${JSON.stringify(events)});`; - const script = createScript(doc, replayScript, nonce); - doc.body.insertBefore(script, doc.body.firstChild); + const eventDispatchScript = findEventDispatchScript(doc); + if (eventDispatchScript) { + const events = Array.from(eventTypesToBeReplayed); + // This is defined in packages/core/primitives/event-dispatch/contract_binary.ts + const replayScriptContents = `window.__jsaction_bootstrap('ngContracts', document.body, ${JSON.stringify( + appId, + )}, ${JSON.stringify(events)});`; + + const replayScript = createScript(doc, replayScriptContents, nonce); + + // Insert replay script right after inlined event dispatch script, since it + // relies on `__jsaction_bootstrap` to be defined in the global scope. + eventDispatchScript.after(replayScript); + } } async function _render(platformRef: PlatformRef, applicationRef: ApplicationRef): Promise { diff --git a/packages/platform-server/test/event_replay_spec.ts b/packages/platform-server/test/event_replay_spec.ts index 0d2c3886dc1..24626465c11 100644 --- a/packages/platform-server/test/event_replay_spec.ts +++ b/packages/platform-server/test/event_replay_spec.ts @@ -97,7 +97,7 @@ describe('event replay', () => { selector: 'app', template: `
-
+
`, }) @@ -106,14 +106,12 @@ describe('event replay', () => { onBlur = blurSpy; } - const docContents = ``; + const docContents = `${EVENT_DISPATCH_SCRIPT}`; const html = await ssr(SimpleComponent, {doc: docContents}); const ssrContents = getAppContents(html); - expect( - ssrContents.startsWith( - ``, - ), - ).toBeTrue(); + expect(ssrContents).toContain( + ``, + ); expect(ssrContents).toContain( '
', ); @@ -145,7 +143,9 @@ describe('event replay', () => { onClick() {} } - const doc = ``; + const doc = + `${EVENT_DISPATCH_SCRIPT}` + + ``; const html = await ssr(SimpleComponent, {doc}); expect(getAppContents(html)).toContain( '` + + ``, + ); }); }); });