@bleedingdev/modern-js-runtime-extensions 3.9.0-ultramodern.4 → 3.9.0-ultramodern.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (155) hide show
  1. package/dist/cjs/contextExtensions.js +74 -0
  2. package/dist/cjs/headRuntime.js +91 -0
  3. package/dist/cjs/helmetContext.js +54 -0
  4. package/dist/cjs/index.js +18 -4
  5. package/dist/cjs/prefetchPolicy.js +52 -0
  6. package/dist/cjs/request-policy/browser.js +139 -0
  7. package/dist/cjs/request-policy/client.js +234 -0
  8. package/dist/cjs/request-policy/node.js +149 -0
  9. package/dist/cjs/request-policy/policyCore.js +266 -0
  10. package/dist/cjs/request-policy/requestContext.js +137 -0
  11. package/dist/cjs/request-policy/traceparent.js +56 -0
  12. package/dist/cjs/request-policy/transport.js +234 -0
  13. package/dist/cjs/request-policy/types.js +57 -0
  14. package/dist/cjs/routeHydration.js +127 -0
  15. package/dist/cjs/routerPrefetchPolicy.js +153 -0
  16. package/dist/cjs/routerProvider.js +105 -0
  17. package/dist/cjs/routerState.js +167 -0
  18. package/dist/cjs/routerStatePlugin.js +86 -0
  19. package/dist/cjs/routerStateTypes.js +18 -0
  20. package/dist/cjs/runtimeContextProjection.js +112 -0
  21. package/dist/cjs/server-config/bffRuntime.js +18 -0
  22. package/dist/cjs/server-config/index.js +18 -0
  23. package/dist/cjs/server-config/serverTelemetry.js +18 -0
  24. package/dist/cjs/shellStream.js +34 -2
  25. package/dist/cjs/workspaceEvents.js +159 -0
  26. package/dist/esm/contextExtensions.mjs +33 -0
  27. package/dist/esm/headRuntime.mjs +43 -0
  28. package/dist/esm/helmetContext.mjs +13 -0
  29. package/dist/esm/index.mjs +2 -0
  30. package/dist/esm/prefetchPolicy.mjs +11 -0
  31. package/dist/esm/request-policy/browser.mjs +11 -0
  32. package/dist/esm/request-policy/client.mjs +195 -0
  33. package/dist/esm/request-policy/policyCore.mjs +171 -0
  34. package/dist/esm/request-policy/requestContext.mjs +75 -0
  35. package/dist/esm/request-policy/traceparent.mjs +18 -0
  36. package/dist/esm/request-policy/transport.mjs +196 -0
  37. package/dist/esm/request-policy/types.mjs +10 -0
  38. package/dist/esm/routeHydration.mjs +77 -0
  39. package/dist/esm/routerPrefetchPolicy.mjs +115 -0
  40. package/dist/esm/routerProvider.mjs +58 -0
  41. package/dist/esm/routerState.mjs +105 -0
  42. package/dist/esm/routerStatePlugin.mjs +48 -0
  43. package/dist/esm/routerStateTypes.mjs +0 -0
  44. package/dist/esm/runtimeContextProjection.mjs +74 -0
  45. package/dist/esm/server-config/bffRuntime.mjs +0 -0
  46. package/dist/esm/server-config/index.mjs +0 -0
  47. package/dist/esm/server-config/serverTelemetry.mjs +0 -0
  48. package/dist/esm/shellStream.mjs +34 -2
  49. package/dist/esm/workspaceEvents.mjs +66 -0
  50. package/dist/esm-node/contextExtensions.mjs +34 -0
  51. package/dist/esm-node/headRuntime.mjs +44 -0
  52. package/dist/esm-node/helmetContext.mjs +14 -0
  53. package/dist/esm-node/index.mjs +2 -0
  54. package/dist/esm-node/prefetchPolicy.mjs +12 -0
  55. package/dist/esm-node/request-policy/browser.mjs +12 -0
  56. package/dist/esm-node/request-policy/client.mjs +196 -0
  57. package/dist/esm-node/request-policy/node.mjs +22 -0
  58. package/dist/esm-node/request-policy/policyCore.mjs +172 -0
  59. package/dist/esm-node/request-policy/requestContext.mjs +76 -0
  60. package/dist/esm-node/request-policy/traceparent.mjs +19 -0
  61. package/dist/esm-node/request-policy/transport.mjs +197 -0
  62. package/dist/esm-node/request-policy/types.mjs +11 -0
  63. package/dist/esm-node/routeHydration.mjs +78 -0
  64. package/dist/esm-node/routerPrefetchPolicy.mjs +116 -0
  65. package/dist/esm-node/routerProvider.mjs +59 -0
  66. package/dist/esm-node/routerState.mjs +106 -0
  67. package/dist/esm-node/routerStatePlugin.mjs +49 -0
  68. package/dist/esm-node/routerStateTypes.mjs +1 -0
  69. package/dist/esm-node/runtimeContextProjection.mjs +75 -0
  70. package/dist/esm-node/server-config/bffRuntime.mjs +1 -0
  71. package/dist/esm-node/server-config/index.mjs +1 -0
  72. package/dist/esm-node/server-config/serverTelemetry.mjs +1 -0
  73. package/dist/esm-node/shellStream.mjs +34 -2
  74. package/dist/esm-node/workspaceEvents.mjs +67 -0
  75. package/dist/types/contextExtensions.d.ts +34 -0
  76. package/dist/types/headRuntime.d.ts +12 -0
  77. package/dist/types/helmetContext.d.ts +10 -0
  78. package/dist/types/index.d.ts +2 -0
  79. package/dist/types/prefetchPolicy.d.ts +4 -0
  80. package/dist/types/request-policy/browser.d.ts +10 -0
  81. package/dist/types/request-policy/client.d.ts +14 -0
  82. package/dist/types/request-policy/node.d.ts +10 -0
  83. package/dist/types/request-policy/policyCore.d.ts +107 -0
  84. package/dist/types/request-policy/requestContext.d.ts +22 -0
  85. package/dist/types/request-policy/traceparent.d.ts +10 -0
  86. package/dist/types/request-policy/transport.d.ts +12 -0
  87. package/dist/types/request-policy/types.d.ts +144 -0
  88. package/dist/types/routeHydration.d.ts +24 -0
  89. package/dist/types/routerPrefetchPolicy.d.ts +28 -0
  90. package/dist/types/routerProvider.d.ts +55 -0
  91. package/dist/types/routerState.d.ts +12 -0
  92. package/dist/types/routerStatePlugin.d.ts +19 -0
  93. package/dist/types/routerStateTypes.d.ts +40 -0
  94. package/dist/types/runtimeContextProjection.d.ts +30 -0
  95. package/dist/types/server-config/bffRuntime.d.ts +122 -0
  96. package/dist/types/server-config/index.d.ts +2 -0
  97. package/dist/types/server-config/serverTelemetry.d.ts +322 -0
  98. package/dist/types/workspaceEvents.d.ts +62 -0
  99. package/package.json +198 -13
  100. package/rslib.config.mts +17 -2
  101. package/rstest.config.mts +33 -2
  102. package/src/contextExtensions.ts +81 -0
  103. package/src/headRuntime.tsx +77 -0
  104. package/src/helmetContext.ts +31 -0
  105. package/src/index.ts +2 -0
  106. package/src/prefetchPolicy.ts +33 -0
  107. package/src/request-policy/browser.ts +22 -0
  108. package/src/request-policy/client.ts +404 -0
  109. package/src/request-policy/node.ts +41 -0
  110. package/src/request-policy/policyCore.ts +424 -0
  111. package/src/request-policy/requestContext.ts +149 -0
  112. package/src/request-policy/traceparent.ts +43 -0
  113. package/src/request-policy/transport.ts +349 -0
  114. package/src/request-policy/types.ts +229 -0
  115. package/src/routeHydration.ts +208 -0
  116. package/src/routerPrefetchPolicy.ts +195 -0
  117. package/src/routerProvider.ts +160 -0
  118. package/src/routerState.ts +208 -0
  119. package/src/routerStatePlugin.ts +86 -0
  120. package/src/routerStateTypes.ts +46 -0
  121. package/src/runtimeContextProjection.ts +105 -0
  122. package/src/server-config/bffRuntime.ts +136 -0
  123. package/src/server-config/index.ts +2 -0
  124. package/src/server-config/serverTelemetry.ts +337 -0
  125. package/src/shellStream.ts +55 -25
  126. package/src/workspaceEvents.ts +324 -0
  127. package/tests/buildIdentity.test.ts +2 -4
  128. package/tests/contextExtensions.test.ts +71 -0
  129. package/tests/headRuntime.test.tsx +369 -0
  130. package/tests/nativeSafeFailureCompatibility.test.ts +117 -0
  131. package/tests/prefetchPolicy.test.ts +51 -0
  132. package/tests/rendererHead.test.ts +1 -72
  133. package/tests/request-policy/browser-policy.test.ts +608 -0
  134. package/tests/request-policy/client-isolation.test.ts +149 -0
  135. package/tests/request-policy/identityBinding.test.ts +75 -0
  136. package/tests/request-policy/node-policy.test.ts +741 -0
  137. package/tests/request-policy/policyCore.test.ts +250 -0
  138. package/tests/request-policy/requestContext.test.ts +80 -0
  139. package/tests/request-policy/requestFactory.outbound-contract.test.ts +691 -0
  140. package/tests/request-policy/traceparent.test.ts +55 -0
  141. package/tests/request-policy/transport-retry.test.ts +370 -0
  142. package/tests/routeHydration.test.ts +180 -0
  143. package/tests/routerPrefetchPolicy.test.ts +102 -0
  144. package/tests/routerProvider.test.ts +29 -0
  145. package/tests/routerState.test.ts +157 -0
  146. package/tests/routerStatePlugin.test.ts +108 -0
  147. package/tests/runtimeContextProjection.test.ts +135 -0
  148. package/tests/shellStream.test.ts +37 -0
  149. package/tests/workspaceEvents.test.ts +391 -0
  150. package/dist/cjs/rscHtmlStream.js +0 -128
  151. package/dist/esm/rscHtmlStream.mjs +0 -86
  152. package/dist/esm-node/rscHtmlStream.mjs +0 -87
  153. package/dist/types/rscHtmlStream.d.ts +0 -4
  154. package/src/rscHtmlStream.ts +0 -142
  155. package/tests/rscHtmlStream.test.ts +0 -153
@@ -0,0 +1,391 @@
1
+ import { spawnSync } from 'node:child_process';
2
+ import { mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
3
+ import { createRequire } from 'node:module';
4
+ import { tmpdir } from 'node:os';
5
+ import path from 'node:path';
6
+ import { transformSync } from 'esbuild';
7
+ import * as contracts from '../src/workspaceEvents';
8
+
9
+ const cases = [
10
+ {
11
+ name: 'ultramodern:navigate',
12
+ valid: { to: '/dashboard', replace: false, state: { from: 'shell' } },
13
+ invalid: [
14
+ {},
15
+ { to: '' },
16
+ { to: ' ' },
17
+ { to: 42 },
18
+ { to: '/', replace: 'false' },
19
+ { to: '/', state: [] },
20
+ { to: '/', state: null },
21
+ ],
22
+ validate: contracts.isUltramodernNavigatePayload,
23
+ dispatch: contracts.dispatchUltramodernNavigate,
24
+ on: contracts.onUltramodernNavigate,
25
+ },
26
+ {
27
+ name: 'ultramodern:route-settled',
28
+ valid: { pathname: '/cs', locale: 'cs', title: 'Home' },
29
+ invalid: [
30
+ {},
31
+ { pathname: ' ' },
32
+ { pathname: '/', locale: 'de' },
33
+ { pathname: '/', locale: null },
34
+ { pathname: '/', title: '' },
35
+ { pathname: '/', title: 42 },
36
+ ],
37
+ validate: contracts.isUltramodernRouteSettledPayload,
38
+ dispatch: contracts.dispatchUltramodernRouteSettled,
39
+ on: contracts.onUltramodernRouteSettled,
40
+ },
41
+ {
42
+ name: 'ultramodern:remote-ready',
43
+ valid: { appId: 'catalog', build: 'a', surface: 'ui', version: '1' },
44
+ invalid: [
45
+ {},
46
+ { appId: 42 },
47
+ { appId: ' ' },
48
+ { appId: 'catalog', build: '' },
49
+ { appId: 'catalog', surface: null },
50
+ { appId: 'catalog', version: 1 },
51
+ ],
52
+ validate: contracts.isUltramodernRemoteReadyPayload,
53
+ dispatch: contracts.dispatchUltramodernRemoteReady,
54
+ on: contracts.onUltramodernRemoteReady,
55
+ },
56
+ {
57
+ name: 'ultramodern:performance-signal',
58
+ valid: {
59
+ signalId: 'bfcache',
60
+ status: 'pass',
61
+ durationMs: 0,
62
+ detail: { source: 'browser' },
63
+ },
64
+ invalid: [
65
+ {},
66
+ { signalId: 'unknown', status: 'pass' },
67
+ { signalId: 'bfcache', status: 'unknown' },
68
+ ...[-1, Number.NaN, Number.POSITIVE_INFINITY, '1', null].map(
69
+ durationMs => ({ signalId: 'bfcache', status: 'pass', durationMs }),
70
+ ),
71
+ { signalId: 'bfcache', status: 'pass', detail: [] },
72
+ { signalId: 'bfcache', status: 'pass', detail: null },
73
+ ],
74
+ validate: contracts.isUltramodernPerformanceSignalPayload,
75
+ dispatch: contracts.dispatchUltramodernPerformanceSignal,
76
+ on: contracts.onUltramodernPerformanceSignal,
77
+ },
78
+ ] as const;
79
+
80
+ describe('workspace event contracts', () => {
81
+ for (const { name, valid, invalid, validate, dispatch, on } of cases) {
82
+ it(`${name} preserves schema validation and error data`, () => {
83
+ expect(validate(valid)).toBe(true);
84
+ expect(
85
+ contracts.assertUltramodernWorkspaceEventPayload(name, valid),
86
+ ).toBe(valid);
87
+ for (const payload of [null, undefined, [], false, 'text', ...invalid]) {
88
+ expect(validate(payload)).toBe(false);
89
+ expect(
90
+ contracts.isUltramodernWorkspaceEventPayload(name, payload),
91
+ ).toBe(false);
92
+ let error: unknown;
93
+ try {
94
+ contracts.assertUltramodernWorkspaceEventPayload(name, payload);
95
+ } catch (caught) {
96
+ error = caught;
97
+ }
98
+ expect(error).toBeInstanceOf(
99
+ contracts.UltramodernWorkspaceEventValidationError,
100
+ );
101
+ expect(error).toMatchObject({
102
+ name: 'UltramodernWorkspaceEventValidationError',
103
+ message: `Invalid payload for UltraModern workspace event "${name}"`,
104
+ eventName: name,
105
+ payload,
106
+ });
107
+ // The existing public error is a data class, not an Error subclass.
108
+ expect(error).not.toBeInstanceOf(Error);
109
+ }
110
+ });
111
+
112
+ it(`${name} uses native dispatch, preserves payload identity and cleans up`, () => {
113
+ const target = new EventTarget();
114
+ const received: unknown[] = [];
115
+ const handler = (payload: unknown, event: CustomEvent<unknown>) => {
116
+ expect(event).toBeInstanceOf(CustomEvent);
117
+ expect(event.type).toBe(name);
118
+ expect(event.bubbles).toBe(true);
119
+ expect(event.composed).toBe(true);
120
+ expect(event.cancelable).toBe(false);
121
+ expect(event.target).toBe(target);
122
+ expect(event.detail).toBe(payload);
123
+ received.push(payload);
124
+ };
125
+ const unsubscribe = on(target, handler);
126
+ // Each table row correlates the convenience dispatch with its own schema.
127
+ const dispatchPayload = dispatch as (
128
+ target: EventTarget,
129
+ payload: typeof valid,
130
+ ) => boolean;
131
+ expect(dispatchPayload(target, valid)).toBe(true);
132
+ expect(received).toEqual([valid]);
133
+ expect(received[0]).toBe(valid);
134
+ unsubscribe();
135
+ unsubscribe();
136
+ dispatchPayload(target, valid);
137
+ expect(received).toEqual([valid]);
138
+ });
139
+ }
140
+
141
+ it('retains permissive records, unknown fields and undefined optional fields', () => {
142
+ const payload = Object.assign(Object.create(null), {
143
+ to: ' /raw ',
144
+ state: new Date(0),
145
+ replace: undefined,
146
+ extension: 'consumer-owned',
147
+ });
148
+ expect(
149
+ contracts.assertUltramodernWorkspaceEventPayload(
150
+ 'ultramodern:navigate',
151
+ payload,
152
+ ),
153
+ ).toBe(payload);
154
+ expect(payload.to).toBe(' /raw ');
155
+ for (const locale of ['en', 'cs', undefined]) {
156
+ expect(
157
+ contracts.isUltramodernRouteSettledPayload({
158
+ pathname: '/',
159
+ locale,
160
+ title: undefined,
161
+ }),
162
+ ).toBe(true);
163
+ }
164
+ expect(
165
+ contracts.isUltramodernRemoteReadyPayload({
166
+ appId: 'catalog',
167
+ build: undefined,
168
+ surface: undefined,
169
+ version: undefined,
170
+ }),
171
+ ).toBe(true);
172
+ for (const signalId of [
173
+ 'bfcache',
174
+ 'core-web-vitals-rum',
175
+ 'duplicate-prefetch-warmup',
176
+ 'cache-policy-sanity',
177
+ 'save-data-behavior',
178
+ 'cloudflare-ssr-cache-hints',
179
+ ]) {
180
+ for (const status of ['pass', 'warn', 'fail']) {
181
+ expect(
182
+ contracts.isUltramodernPerformanceSignalPayload({
183
+ signalId,
184
+ status,
185
+ durationMs: undefined,
186
+ detail: undefined,
187
+ }),
188
+ ).toBe(true);
189
+ }
190
+ }
191
+ });
192
+
193
+ it('rejects invalid dispatch before notifying listeners', () => {
194
+ const target = new EventTarget();
195
+ const handler = rstest.fn();
196
+ const unsubscribe = contracts.onUltramodernNavigate(target, handler);
197
+ expect(() =>
198
+ contracts.dispatchUltramodernNavigate(target, { to: '' }),
199
+ ).toThrow();
200
+ expect(handler).not.toHaveBeenCalled();
201
+ unsubscribe();
202
+ });
203
+
204
+ it('validates incoming event details and removes only its own listener', () => {
205
+ const target = new EventTarget();
206
+ const add = rstest.spyOn(target, 'addEventListener');
207
+ const remove = rstest.spyOn(target, 'removeEventListener');
208
+ const handler = rstest.fn();
209
+ const first = contracts.onUltramodernNavigate(target, handler);
210
+ const listener = add.mock.calls[0]?.[1] as EventListener;
211
+ for (const event of [
212
+ new Event('ultramodern:navigate'),
213
+ new CustomEvent('ultramodern:navigate', { detail: { to: '' } }),
214
+ ]) {
215
+ // Native dispatch reports listener errors asynchronously; invoke the
216
+ // registered listener directly to inspect the precise validation failure.
217
+ expect(() => listener(event)).toThrow();
218
+ }
219
+ expect(handler).not.toHaveBeenCalled();
220
+ const second = contracts.onUltramodernNavigate(target, handler);
221
+ first();
222
+ expect(remove).toHaveBeenCalledWith('ultramodern:navigate', listener);
223
+ contracts.dispatchUltramodernNavigate(target, { to: '/still-subscribed' });
224
+ expect(handler).toHaveBeenCalledTimes(1);
225
+ second();
226
+ add.mockRestore();
227
+ remove.mockRestore();
228
+ });
229
+
230
+ it('keeps event-name payload inference and compile-time rejection', () => {
231
+ const temporaryRoot = mkdtempSync(
232
+ path.join(tmpdir(), 'workspace-events-types-'),
233
+ );
234
+ try {
235
+ const fixture = path.join(temporaryRoot, 'consumer.ts');
236
+ writeFileSync(
237
+ fixture,
238
+ `
239
+ import * as c from ${JSON.stringify(path.resolve(__dirname, '../src/workspaceEvents'))};
240
+ const target = new EventTarget();
241
+ const value: unknown = { to: '/' };
242
+ if (c.isUltramodernWorkspaceEventPayload(c.ultramodernWorkspaceEventNames.navigate, value)) {
243
+ const destination: string = value.to;
244
+ // @ts-expect-error Navigate payload has no pathname
245
+ value.pathname;
246
+ }
247
+ const event: CustomEvent<c.UltramodernRemoteReadyPayload> =
248
+ c.createUltramodernWorkspaceEvent('ultramodern:remote-ready', { appId: 'catalog' });
249
+ const cleanup: () => void = c.onUltramodernPerformanceSignal(target, (payload, event) => {
250
+ const status: c.UltramodernPerformanceReadinessSignalStatus = payload.status;
251
+ const signal: c.UltramodernPerformanceReadinessSignalId = event.detail.signalId;
252
+ });
253
+ const dispatched: boolean = c.dispatchUltramodernRouteSettled(target, { pathname: '/', locale: 'en' });
254
+ const asserted: c.UltramodernNavigatePayload = c.assertUltramodernWorkspaceEventPayload('ultramodern:navigate', value);
255
+ // @ts-expect-error Event names are closed
256
+ c.createUltramodernWorkspaceEvent('consumer:unknown', {});
257
+ // @ts-expect-error Wrong payload for the chosen event
258
+ c.dispatchUltramodernWorkspaceEvent(target, 'ultramodern:navigate', { appId: 'catalog' });
259
+ // @ts-expect-error Unsupported locale
260
+ c.dispatchUltramodernRouteSettled(target, { pathname: '/', locale: 'de' });
261
+ // @ts-expect-error Unsupported signal status
262
+ c.dispatchUltramodernPerformanceSignal(target, { signalId: 'bfcache', status: 'ok' });
263
+ `,
264
+ );
265
+ const requireFromPackage = createRequire(
266
+ path.resolve(__dirname, '../package.json'),
267
+ );
268
+ const compilerPackage = requireFromPackage.resolve(
269
+ 'typescript/package.json',
270
+ );
271
+ const metadata = JSON.parse(readFileSync(compilerPackage, 'utf8'));
272
+ const compiler = path.resolve(
273
+ path.dirname(compilerPackage),
274
+ metadata.bin.tsc,
275
+ );
276
+ const config = path.join(temporaryRoot, 'tsconfig.json');
277
+ writeFileSync(
278
+ config,
279
+ JSON.stringify({
280
+ compilerOptions: {
281
+ noEmit: true,
282
+ strict: true,
283
+ skipLibCheck: true,
284
+ types: [],
285
+ target: 'ES2022',
286
+ module: 'NodeNext',
287
+ moduleResolution: 'NodeNext',
288
+ },
289
+ files: [fixture],
290
+ }),
291
+ );
292
+ const checked = spawnSync(
293
+ process.execPath,
294
+ [compiler, '--project', config, '--pretty', 'false'],
295
+ {
296
+ cwd: temporaryRoot,
297
+ encoding: 'utf8',
298
+ },
299
+ );
300
+ expect(checked.error).toBeUndefined();
301
+ expect({
302
+ status: checked.status,
303
+ output: checked.stdout + checked.stderr,
304
+ }).toEqual({ status: 0, output: '' });
305
+ } finally {
306
+ rmSync(temporaryRoot, { recursive: true, force: true });
307
+ }
308
+ });
309
+
310
+ it('supports native browser targets, shadow propagation and foreign realm events', async () => {
311
+ const requireFromBrowserFixture = createRequire(
312
+ path.resolve(
313
+ __dirname,
314
+ '../../../../tests/integration/rstest/basic-app-rstest-browser/package.json',
315
+ ),
316
+ );
317
+ const { chromium } = requireFromBrowserFixture('playwright');
318
+ const source = readFileSync(
319
+ path.resolve(__dirname, '../src/workspaceEvents.ts'),
320
+ 'utf8',
321
+ );
322
+ const compiled = transformSync(source, {
323
+ loader: 'ts',
324
+ format: 'cjs',
325
+ }).code;
326
+ const browser = await chromium.launch({ headless: true });
327
+ try {
328
+ const page = await browser.newPage();
329
+ const result = await page.evaluate((compiled: string) => {
330
+ const module = { exports: {} };
331
+ new Function('module', 'exports', compiled)(module, module.exports);
332
+ const api = module.exports as typeof contracts;
333
+ const host = document.createElement('div');
334
+ document.body.append(host);
335
+ const inner = document.createElement('button');
336
+ host.attachShadow({ mode: 'open' }).append(inner);
337
+ const payload = { to: '/shadow' };
338
+ const seen: unknown[] = [];
339
+ const off = api.onUltramodernNavigate(document, (detail, event) => {
340
+ seen.push({
341
+ same: detail === payload,
342
+ target: event.target === host,
343
+ });
344
+ });
345
+ api.dispatchUltramodernNavigate(inner, payload);
346
+ off();
347
+ api.dispatchUltramodernNavigate(inner, payload);
348
+ const iframe = document.createElement('iframe');
349
+ document.body.append(iframe);
350
+ const foreignWindow = iframe.contentWindow as Window &
351
+ typeof globalThis;
352
+ const foreignEvent = new foreignWindow.CustomEvent(
353
+ 'ultramodern:navigate',
354
+ {
355
+ detail: payload,
356
+ },
357
+ );
358
+ let foreignReceived = false;
359
+ const offForeign = api.onUltramodernNavigate(
360
+ document,
361
+ (detail, event) => {
362
+ foreignReceived = detail === payload && event === foreignEvent;
363
+ },
364
+ );
365
+ document.dispatchEvent(foreignEvent);
366
+ offForeign();
367
+ const foreignTarget = new foreignWindow.EventTarget();
368
+ let foreignTargetReceived = false;
369
+ const offTarget = api.onUltramodernNavigate(foreignTarget, detail => {
370
+ foreignTargetReceived = detail === payload;
371
+ });
372
+ api.dispatchUltramodernNavigate(foreignTarget, payload);
373
+ offTarget();
374
+ return {
375
+ seen,
376
+ foreignReceived,
377
+ foreignTargetReceived,
378
+ distinctRealm: !(foreignEvent instanceof CustomEvent),
379
+ };
380
+ }, compiled);
381
+ expect(result).toEqual({
382
+ seen: [{ same: true, target: true }],
383
+ foreignReceived: true,
384
+ foreignTargetReceived: true,
385
+ distinctRealm: true,
386
+ });
387
+ } finally {
388
+ await browser.close();
389
+ }
390
+ });
391
+ });
@@ -1,128 +0,0 @@
1
- "use strict";
2
- var __webpack_require__ = {};
3
- (()=>{
4
- __webpack_require__.d = (exports1, getters, values)=>{
5
- var define = (defs, kind)=>{
6
- for(var key in defs)if (__webpack_require__.o(defs, key) && !__webpack_require__.o(exports1, key)) Object.defineProperty(exports1, key, {
7
- enumerable: true,
8
- [kind]: defs[key]
9
- });
10
- };
11
- define(getters, "get");
12
- define(values, "value");
13
- };
14
- })();
15
- (()=>{
16
- __webpack_require__.o = (obj, prop)=>Object.prototype.hasOwnProperty.call(obj, prop);
17
- })();
18
- (()=>{
19
- __webpack_require__.r = (exports1)=>{
20
- if ("u" > typeof Symbol && Symbol.toStringTag) Object.defineProperty(exports1, Symbol.toStringTag, {
21
- value: 'Module'
22
- });
23
- Object.defineProperty(exports1, '__esModule', {
24
- value: true
25
- });
26
- };
27
- })();
28
- var __webpack_exports__ = {};
29
- __webpack_require__.r(__webpack_exports__);
30
- const encoder = new TextEncoder();
31
- const closingTagsPattern = /<\/body>\s*<\/html>\s*$/i;
32
- const closingTagsPrefixPattern = /(?:<\/body>\s*(?:<\/html>\s*|<\/h(?:t(?:m(?:l>?)?)?)?|<\/?|<)?|<\/b(?:o(?:d(?:y>?)?)?)?|<\/?|<[^>]*)$/i;
33
- const RSC_HTML_CLOSING_TAIL_LIMIT = 8192;
34
- function injectRSCPayload(rscStream, { injectClosingTags = true } = {}) {
35
- const decoder = new TextDecoder();
36
- let retainedTail = '';
37
- let committedClosingBody = false;
38
- let committedHtmlClose = false;
39
- let committedProbe = '';
40
- let flightPromise;
41
- const startFlight = (controller)=>{
42
- if (!flightPromise) {
43
- flightPromise = writeRSCStream(rscStream, controller);
44
- flightPromise.catch((error)=>controller.error(error));
45
- }
46
- return flightPromise;
47
- };
48
- const enqueueHtml = (value, controller)=>{
49
- if (value) controller.enqueue(encoder.encode(value));
50
- };
51
- const processHtml = async (value, controller)=>{
52
- if (!value) return;
53
- if (committedClosingBody) {
54
- const probe = committedProbe + value;
55
- committedHtmlClose ||= /<\/html>/i.test(probe);
56
- committedProbe = probe.slice(-6);
57
- enqueueHtml(value, controller);
58
- return;
59
- }
60
- const html = retainedTail + value;
61
- retainedTail = '';
62
- const suffix = html.match(closingTagsPrefixPattern)?.[0] ?? '';
63
- const prefix = suffix ? html.slice(0, -suffix.length) : html;
64
- enqueueHtml(prefix, controller);
65
- if (!suffix) return void startFlight(controller);
66
- if (suffix.length <= RSC_HTML_CLOSING_TAIL_LIMIT) {
67
- retainedTail = suffix;
68
- return;
69
- }
70
- await startFlight(controller);
71
- if (/<\/body>/i.test(suffix)) {
72
- committedClosingBody = true;
73
- committedHtmlClose = /<\/html>/i.test(suffix);
74
- committedProbe = suffix.slice(-6);
75
- }
76
- enqueueHtml(suffix, controller);
77
- };
78
- return new TransformStream({
79
- async transform (chunk, controller) {
80
- await processHtml(decoder.decode(chunk, {
81
- stream: true
82
- }), controller);
83
- },
84
- async flush (controller) {
85
- await processHtml(decoder.decode(), controller);
86
- await startFlight(controller);
87
- const retainedClosingTags = closingTagsPattern.test(retainedTail);
88
- enqueueHtml(retainedTail, controller);
89
- if (!injectClosingTags) return;
90
- if (committedClosingBody) {
91
- if (!committedHtmlClose) enqueueHtml('</html>', controller);
92
- } else if (!retainedClosingTags) enqueueHtml('</body></html>', controller);
93
- }
94
- });
95
- }
96
- async function writeRSCStream(rscStream, controller) {
97
- const decoder = new TextDecoder('utf-8', {
98
- fatal: true
99
- });
100
- for await (const chunk of rscStream)try {
101
- writeChunk(JSON.stringify(decoder.decode(chunk, {
102
- stream: true
103
- })), controller);
104
- } catch {
105
- const base64 = JSON.stringify(btoa(String.fromCodePoint(...chunk)));
106
- writeChunk(`Uint8Array.from(atob(${base64}), m => m.codePointAt(0))`, controller);
107
- }
108
- const remaining = decoder.decode();
109
- if (remaining) writeChunk(JSON.stringify(remaining), controller);
110
- }
111
- function writeChunk(chunk, controller) {
112
- const script = `(self.__FLIGHT_DATA||=[]).push(${chunk})`;
113
- controller.enqueue(encoder.encode(`<script>${script.replace(/<!--/g, '<\\!--').replace(/<\/(script)/gi, '</\\$1')}</script>`));
114
- }
115
- __webpack_require__.d(__webpack_exports__, {
116
- injectRSCPayload: ()=>injectRSCPayload
117
- }, {
118
- RSC_HTML_CLOSING_TAIL_LIMIT: RSC_HTML_CLOSING_TAIL_LIMIT
119
- });
120
- exports.RSC_HTML_CLOSING_TAIL_LIMIT = __webpack_exports__.RSC_HTML_CLOSING_TAIL_LIMIT;
121
- exports.injectRSCPayload = __webpack_exports__.injectRSCPayload;
122
- for(var __rspack_i in __webpack_exports__)if (-1 === [
123
- "RSC_HTML_CLOSING_TAIL_LIMIT",
124
- "injectRSCPayload"
125
- ].indexOf(__rspack_i)) exports[__rspack_i] = __webpack_exports__[__rspack_i];
126
- Object.defineProperty(exports, '__esModule', {
127
- value: true
128
- });
@@ -1,86 +0,0 @@
1
- const encoder = new TextEncoder();
2
- const closingTagsPattern = /<\/body>\s*<\/html>\s*$/i;
3
- const closingTagsPrefixPattern = /(?:<\/body>\s*(?:<\/html>\s*|<\/h(?:t(?:m(?:l>?)?)?)?|<\/?|<)?|<\/b(?:o(?:d(?:y>?)?)?)?|<\/?|<[^>]*)$/i;
4
- const RSC_HTML_CLOSING_TAIL_LIMIT = 8192;
5
- function injectRSCPayload(rscStream, { injectClosingTags = true } = {}) {
6
- const decoder = new TextDecoder();
7
- let retainedTail = '';
8
- let committedClosingBody = false;
9
- let committedHtmlClose = false;
10
- let committedProbe = '';
11
- let flightPromise;
12
- const startFlight = (controller)=>{
13
- if (!flightPromise) {
14
- flightPromise = writeRSCStream(rscStream, controller);
15
- flightPromise.catch((error)=>controller.error(error));
16
- }
17
- return flightPromise;
18
- };
19
- const enqueueHtml = (value, controller)=>{
20
- if (value) controller.enqueue(encoder.encode(value));
21
- };
22
- const processHtml = async (value, controller)=>{
23
- if (!value) return;
24
- if (committedClosingBody) {
25
- const probe = committedProbe + value;
26
- committedHtmlClose ||= /<\/html>/i.test(probe);
27
- committedProbe = probe.slice(-6);
28
- enqueueHtml(value, controller);
29
- return;
30
- }
31
- const html = retainedTail + value;
32
- retainedTail = '';
33
- const suffix = html.match(closingTagsPrefixPattern)?.[0] ?? '';
34
- const prefix = suffix ? html.slice(0, -suffix.length) : html;
35
- enqueueHtml(prefix, controller);
36
- if (!suffix) return void startFlight(controller);
37
- if (suffix.length <= RSC_HTML_CLOSING_TAIL_LIMIT) {
38
- retainedTail = suffix;
39
- return;
40
- }
41
- await startFlight(controller);
42
- if (/<\/body>/i.test(suffix)) {
43
- committedClosingBody = true;
44
- committedHtmlClose = /<\/html>/i.test(suffix);
45
- committedProbe = suffix.slice(-6);
46
- }
47
- enqueueHtml(suffix, controller);
48
- };
49
- return new TransformStream({
50
- async transform (chunk, controller) {
51
- await processHtml(decoder.decode(chunk, {
52
- stream: true
53
- }), controller);
54
- },
55
- async flush (controller) {
56
- await processHtml(decoder.decode(), controller);
57
- await startFlight(controller);
58
- const retainedClosingTags = closingTagsPattern.test(retainedTail);
59
- enqueueHtml(retainedTail, controller);
60
- if (!injectClosingTags) return;
61
- if (committedClosingBody) {
62
- if (!committedHtmlClose) enqueueHtml('</html>', controller);
63
- } else if (!retainedClosingTags) enqueueHtml('</body></html>', controller);
64
- }
65
- });
66
- }
67
- async function writeRSCStream(rscStream, controller) {
68
- const decoder = new TextDecoder('utf-8', {
69
- fatal: true
70
- });
71
- for await (const chunk of rscStream)try {
72
- writeChunk(JSON.stringify(decoder.decode(chunk, {
73
- stream: true
74
- })), controller);
75
- } catch {
76
- const base64 = JSON.stringify(btoa(String.fromCodePoint(...chunk)));
77
- writeChunk(`Uint8Array.from(atob(${base64}), m => m.codePointAt(0))`, controller);
78
- }
79
- const remaining = decoder.decode();
80
- if (remaining) writeChunk(JSON.stringify(remaining), controller);
81
- }
82
- function writeChunk(chunk, controller) {
83
- const script = `(self.__FLIGHT_DATA||=[]).push(${chunk})`;
84
- controller.enqueue(encoder.encode(`<script>${script.replace(/<!--/g, '<\\!--').replace(/<\/(script)/gi, '</\\$1')}</script>`));
85
- }
86
- export { RSC_HTML_CLOSING_TAIL_LIMIT, injectRSCPayload };
@@ -1,87 +0,0 @@
1
- import "node:module";
2
- const encoder = new TextEncoder();
3
- const closingTagsPattern = /<\/body>\s*<\/html>\s*$/i;
4
- const closingTagsPrefixPattern = /(?:<\/body>\s*(?:<\/html>\s*|<\/h(?:t(?:m(?:l>?)?)?)?|<\/?|<)?|<\/b(?:o(?:d(?:y>?)?)?)?|<\/?|<[^>]*)$/i;
5
- const RSC_HTML_CLOSING_TAIL_LIMIT = 8192;
6
- function injectRSCPayload(rscStream, { injectClosingTags = true } = {}) {
7
- const decoder = new TextDecoder();
8
- let retainedTail = '';
9
- let committedClosingBody = false;
10
- let committedHtmlClose = false;
11
- let committedProbe = '';
12
- let flightPromise;
13
- const startFlight = (controller)=>{
14
- if (!flightPromise) {
15
- flightPromise = writeRSCStream(rscStream, controller);
16
- flightPromise.catch((error)=>controller.error(error));
17
- }
18
- return flightPromise;
19
- };
20
- const enqueueHtml = (value, controller)=>{
21
- if (value) controller.enqueue(encoder.encode(value));
22
- };
23
- const processHtml = async (value, controller)=>{
24
- if (!value) return;
25
- if (committedClosingBody) {
26
- const probe = committedProbe + value;
27
- committedHtmlClose ||= /<\/html>/i.test(probe);
28
- committedProbe = probe.slice(-6);
29
- enqueueHtml(value, controller);
30
- return;
31
- }
32
- const html = retainedTail + value;
33
- retainedTail = '';
34
- const suffix = html.match(closingTagsPrefixPattern)?.[0] ?? '';
35
- const prefix = suffix ? html.slice(0, -suffix.length) : html;
36
- enqueueHtml(prefix, controller);
37
- if (!suffix) return void startFlight(controller);
38
- if (suffix.length <= RSC_HTML_CLOSING_TAIL_LIMIT) {
39
- retainedTail = suffix;
40
- return;
41
- }
42
- await startFlight(controller);
43
- if (/<\/body>/i.test(suffix)) {
44
- committedClosingBody = true;
45
- committedHtmlClose = /<\/html>/i.test(suffix);
46
- committedProbe = suffix.slice(-6);
47
- }
48
- enqueueHtml(suffix, controller);
49
- };
50
- return new TransformStream({
51
- async transform (chunk, controller) {
52
- await processHtml(decoder.decode(chunk, {
53
- stream: true
54
- }), controller);
55
- },
56
- async flush (controller) {
57
- await processHtml(decoder.decode(), controller);
58
- await startFlight(controller);
59
- const retainedClosingTags = closingTagsPattern.test(retainedTail);
60
- enqueueHtml(retainedTail, controller);
61
- if (!injectClosingTags) return;
62
- if (committedClosingBody) {
63
- if (!committedHtmlClose) enqueueHtml('</html>', controller);
64
- } else if (!retainedClosingTags) enqueueHtml('</body></html>', controller);
65
- }
66
- });
67
- }
68
- async function writeRSCStream(rscStream, controller) {
69
- const decoder = new TextDecoder('utf-8', {
70
- fatal: true
71
- });
72
- for await (const chunk of rscStream)try {
73
- writeChunk(JSON.stringify(decoder.decode(chunk, {
74
- stream: true
75
- })), controller);
76
- } catch {
77
- const base64 = JSON.stringify(btoa(String.fromCodePoint(...chunk)));
78
- writeChunk(`Uint8Array.from(atob(${base64}), m => m.codePointAt(0))`, controller);
79
- }
80
- const remaining = decoder.decode();
81
- if (remaining) writeChunk(JSON.stringify(remaining), controller);
82
- }
83
- function writeChunk(chunk, controller) {
84
- const script = `(self.__FLIGHT_DATA||=[]).push(${chunk})`;
85
- controller.enqueue(encoder.encode(`<script>${script.replace(/<!--/g, '<\\!--').replace(/<\/(script)/gi, '</\\$1')}</script>`));
86
- }
87
- export { RSC_HTML_CLOSING_TAIL_LIMIT, injectRSCPayload };