request-iframe 0.2.0 → 0.2.2

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 (93) hide show
  1. package/QUICKSTART.CN.md +33 -11
  2. package/QUICKSTART.md +33 -11
  3. package/README.CN.md +157 -44
  4. package/README.md +159 -41
  5. package/cdn/request-iframe.umd.js +4814 -4026
  6. package/cdn/request-iframe.umd.js.map +1 -1
  7. package/cdn/request-iframe.umd.min.js +2 -2
  8. package/cdn/request-iframe.umd.min.js.map +1 -1
  9. package/esm/api/client.js +45 -22
  10. package/esm/api/endpoint.js +30 -13
  11. package/esm/api/server.js +22 -13
  12. package/esm/constants/warn-once.js +7 -1
  13. package/esm/endpoint/index.js +1 -2
  14. package/esm/endpoint/infra/inbox.js +5 -4
  15. package/esm/endpoint/infra/outbox.js +8 -8
  16. package/esm/endpoint/stream/file-auto-resolve.js +9 -8
  17. package/esm/impl/client.js +3 -2
  18. package/esm/impl/response.js +4 -2
  19. package/esm/impl/server.js +8 -6
  20. package/esm/message/channel.js +15 -3
  21. package/esm/message/dispatcher.js +27 -0
  22. package/esm/stream/file-stream.js +311 -72
  23. package/esm/stream/writable-stream.js +21 -4
  24. package/esm/utils/blob.js +17 -0
  25. package/esm/utils/debug-lazy.js +76 -0
  26. package/esm/utils/logger.js +33 -1
  27. package/esm/utils/strict-mode.js +85 -0
  28. package/esm/utils/warn-once.js +30 -0
  29. package/esm/utils/warnings.js +47 -0
  30. package/library/api/client.d.ts.map +1 -1
  31. package/library/api/client.js +45 -22
  32. package/library/api/endpoint.d.ts.map +1 -1
  33. package/library/api/endpoint.js +30 -13
  34. package/library/api/server.d.ts.map +1 -1
  35. package/library/api/server.js +22 -13
  36. package/library/constants/warn-once.d.ts +6 -0
  37. package/library/constants/warn-once.d.ts.map +1 -1
  38. package/library/constants/warn-once.js +7 -1
  39. package/library/endpoint/index.d.ts +0 -1
  40. package/library/endpoint/index.d.ts.map +1 -1
  41. package/library/endpoint/index.js +1 -8
  42. package/library/endpoint/infra/inbox.d.ts.map +1 -1
  43. package/library/endpoint/infra/inbox.js +4 -3
  44. package/library/endpoint/infra/outbox.d.ts +2 -0
  45. package/library/endpoint/infra/outbox.d.ts.map +1 -1
  46. package/library/endpoint/infra/outbox.js +7 -7
  47. package/library/endpoint/stream/file-auto-resolve.d.ts +1 -1
  48. package/library/endpoint/stream/file-auto-resolve.d.ts.map +1 -1
  49. package/library/endpoint/stream/file-auto-resolve.js +8 -8
  50. package/library/impl/client.d.ts +2 -0
  51. package/library/impl/client.d.ts.map +1 -1
  52. package/library/impl/client.js +3 -2
  53. package/library/impl/response.d.ts.map +1 -1
  54. package/library/impl/response.js +4 -2
  55. package/library/impl/server.d.ts.map +1 -1
  56. package/library/impl/server.js +7 -5
  57. package/library/message/channel.d.ts +2 -2
  58. package/library/message/channel.d.ts.map +1 -1
  59. package/library/message/channel.js +15 -3
  60. package/library/message/dispatcher.d.ts.map +1 -1
  61. package/library/message/dispatcher.js +27 -0
  62. package/library/stream/file-stream.d.ts +70 -5
  63. package/library/stream/file-stream.d.ts.map +1 -1
  64. package/library/stream/file-stream.js +310 -70
  65. package/library/stream/types.d.ts +2 -0
  66. package/library/stream/types.d.ts.map +1 -1
  67. package/library/stream/writable-stream.d.ts.map +1 -1
  68. package/library/stream/writable-stream.js +21 -4
  69. package/library/types/index.d.ts +38 -0
  70. package/library/types/index.d.ts.map +1 -1
  71. package/library/utils/blob.d.ts +7 -0
  72. package/library/utils/blob.d.ts.map +1 -1
  73. package/library/utils/blob.js +18 -0
  74. package/library/utils/debug-lazy.d.ts +26 -0
  75. package/library/utils/debug-lazy.d.ts.map +1 -0
  76. package/library/utils/debug-lazy.js +85 -0
  77. package/library/utils/logger.d.ts +20 -0
  78. package/library/utils/logger.d.ts.map +1 -1
  79. package/library/utils/logger.js +34 -1
  80. package/library/utils/strict-mode.d.ts +37 -0
  81. package/library/utils/strict-mode.d.ts.map +1 -0
  82. package/library/utils/strict-mode.js +94 -0
  83. package/library/utils/warn-once.d.ts +9 -0
  84. package/library/utils/warn-once.d.ts.map +1 -0
  85. package/library/utils/warn-once.js +36 -0
  86. package/library/utils/warnings.d.ts +48 -0
  87. package/library/utils/warnings.d.ts.map +1 -0
  88. package/library/utils/warnings.js +54 -0
  89. package/package.json +1 -1
  90. package/esm/endpoint/stream/file-writable.js +0 -105
  91. package/library/endpoint/stream/file-writable.d.ts +0 -33
  92. package/library/endpoint/stream/file-writable.d.ts.map +0 -1
  93. package/library/endpoint/stream/file-writable.js +0 -115
package/README.md CHANGED
@@ -125,13 +125,20 @@ Example (using unpkg):
125
125
  ```typescript
126
126
  import { requestIframeClient } from 'request-iframe';
127
127
 
128
- // Get iframe element
129
- const iframe = document.querySelector('iframe')!;
128
+ /** Get iframe element */
129
+ const iframe = document.querySelector('iframe') as HTMLIFrameElement;
130
+
131
+ /** Prefer waiting iframe load so contentWindow is ready */
132
+ await new Promise<void>((resolve) => iframe.addEventListener('load', () => resolve(), { once: true }));
130
133
 
131
- // Create client
132
- const client = requestIframeClient(iframe, { secretKey: 'my-app' });
134
+ /**
135
+ * Create client (safer + less boilerplate default)
136
+ * - strict: true defaults targetOrigin/allowedOrigins to window.location.origin (same-origin only)
137
+ * - For cross-origin, explicitly configure targetOrigin + allowedOrigins/validateOrigin
138
+ */
139
+ const client = requestIframeClient(iframe, { secretKey: 'my-app', strict: true });
133
140
 
134
- // Send request (just like axios)
141
+ /** Send request (just like axios) */
135
142
  const response = await client.send('/api/getUserInfo', { userId: 123 });
136
143
  console.log(response.data); // { name: 'Tom', age: 18 }
137
144
  ```
@@ -141,10 +148,15 @@ console.log(response.data); // { name: 'Tom', age: 18 }
141
148
  ```typescript
142
149
  import { requestIframeServer } from 'request-iframe';
143
150
 
144
- // Create server
145
- const server = requestIframeServer({ secretKey: 'my-app' });
151
+ /**
152
+ * Create server
153
+ * - Strongly recommended to configure allowedOrigins / validateOrigin in production
154
+ * - This snippet assumes a same-origin demo (parent origin === iframe origin)
155
+ * For cross-origin, replace with the real parent origin (e.g. 'https://parent.example.com')
156
+ */
157
+ const server = requestIframeServer({ secretKey: 'my-app', strict: true });
146
158
 
147
- // Register handler (just like express)
159
+ /** Register handler (just like express) */
148
160
  server.on('/api/getUserInfo', (req, res) => {
149
161
  const { userId } = req.body;
150
162
  res.send({ name: 'Tom', age: 18 });
@@ -169,8 +181,8 @@ That's it! 🎉
169
181
  In micro-frontend architecture, the main application needs to communicate with child application iframes:
170
182
 
171
183
  ```typescript
172
- // Main application (parent page)
173
- const client = requestIframeClient(iframe, { secretKey: 'main-app' });
184
+ /** Main application (parent page, same-origin default: strict: true) */
185
+ const client = requestIframeClient(iframe, { secretKey: 'main-app', strict: true });
174
186
 
175
187
  // Get user info from child application
176
188
  const userInfoResponse = await client.send('/api/user/info', {});
@@ -185,8 +197,8 @@ await client.send('/api/data/refresh', { timestamp: Date.now() });
185
197
  When integrating third-party components, isolate via iframe while maintaining communication:
186
198
 
187
199
  ```typescript
188
- // Parent page
189
- const client = requestIframeClient(thirdPartyIframe, { secretKey: 'widget' });
200
+ /** Parent page (same-origin default: strict: true) */
201
+ const client = requestIframeClient(thirdPartyIframe, { secretKey: 'widget', strict: true });
190
202
 
191
203
  // Configure component
192
204
  await client.send('/config', {
@@ -194,14 +206,16 @@ await client.send('/config', {
194
206
  language: 'en-US'
195
207
  });
196
208
 
197
- // Listen to component events (via reverse communication)
198
- const server = requestIframeServer({ secretKey: 'widget' });
209
+ /** Listen to component events (via reverse communication) */
210
+ const server = requestIframeServer({ secretKey: 'widget', strict: true });
199
211
  server.on('/event', (req, res) => {
200
212
  console.log('Component event:', req.body);
201
213
  res.send({ received: true });
202
214
  });
203
215
  ```
204
216
 
217
+ > If the third-party iframe is **cross-origin**, explicitly configure `targetOrigin` and `allowedOrigins/validateOrigin` (see the security section).
218
+
205
219
  ### Popup / New Window (Window Communication)
206
220
 
207
221
  `request-iframe` also works with a `Window` target (not only an iframe).
@@ -209,19 +223,25 @@ server.on('/event', (req, res) => {
209
223
  **Important**: you must have a real `Window` reference (e.g. returned by `window.open()`, or available via `window.opener` / `event.source`). You cannot send to an arbitrary browser tab by URL.
210
224
 
211
225
  ```typescript
212
- // Parent page: open a new tab/window
226
+ /** Parent page: open a new tab/window */
213
227
  const child = window.open('https://child.example.com/page.html', '_blank');
214
228
  if (!child) throw new Error('Popup blocked');
215
229
 
216
- // Parent -> child
230
+ /** Parent -> child */
231
+ const targetOrigin = 'https://child.example.com';
217
232
  const client = requestIframeClient(child, {
218
233
  secretKey: 'popup-demo',
219
- targetOrigin: 'https://child.example.com' // strongly recommended (avoid '*')
234
+ targetOrigin, // strongly recommended (avoid '*')
235
+ allowedOrigins: [targetOrigin]
220
236
  });
221
237
  await client.send('/api/ping', { from: 'parent' });
222
238
 
223
- // Child page: create server
224
- const server = requestIframeServer({ secretKey: 'popup-demo' });
239
+ /**
240
+ * Child page: create server
241
+ * Note: allowedOrigins should be the real parent origin (example value below).
242
+ */
243
+ const parentOrigin = 'https://parent.example.com';
244
+ const server = requestIframeServer({ secretKey: 'popup-demo', allowedOrigins: [parentOrigin] });
225
245
  server.on('/api/ping', (req, res) => res.send({ ok: true, echo: req.body }));
226
246
  ```
227
247
 
@@ -230,8 +250,12 @@ server.on('/api/ping', (req, res) => res.send({ ok: true, echo: req.body }));
230
250
  When iframe and parent page are on different origins, use request-iframe to securely fetch data:
231
251
 
232
252
  ```typescript
233
- // Inside iframe (different origin)
234
- const server = requestIframeServer({ secretKey: 'data-api' });
253
+ /**
254
+ * Inside iframe (different origin)
255
+ * Note: allowedOrigins should be the real parent origin (example value below).
256
+ */
257
+ const parentOrigin = 'https://parent.example.com';
258
+ const server = requestIframeServer({ secretKey: 'data-api', allowedOrigins: [parentOrigin] });
235
259
 
236
260
  server.on('/api/data', async (req, res) => {
237
261
  // Fetch data from same-origin API (iframe can access same-origin resources)
@@ -239,8 +263,9 @@ server.on('/api/data', async (req, res) => {
239
263
  res.send(data);
240
264
  });
241
265
 
242
- // Parent page (cross-origin)
243
- const client = requestIframeClient(iframe, { secretKey: 'data-api' });
266
+ /** Parent page (cross-origin) */
267
+ const targetOrigin = new URL(iframe.src).origin;
268
+ const client = requestIframeClient(iframe, { secretKey: 'data-api', targetOrigin, allowedOrigins: [targetOrigin] });
244
269
  const response = await client.send('/api/data', {});
245
270
  const data = response.data; // Successfully fetch cross-origin data
246
271
  ```
@@ -740,6 +765,17 @@ import {
740
765
  isIframeFileReadableStream
741
766
  } from 'request-iframe';
742
767
 
768
+ /**
769
+ * How to choose (data stream vs file/byte stream)
770
+ *
771
+ * - IframeWritableStream / IframeReadableStream:
772
+ * For application-level data (objects/strings), relies on structured clone.
773
+ * - IframeFileWritableStream / IframeFileReadableStream:
774
+ * For byte sequences (files/binary/UTF-8 text files). Chunks represent bytes.
775
+ * - Text file recommended: IframeFileWritableStream.fromText(...) + fileStream.readAsText()
776
+ * - Binary recommended: yield Uint8Array/ArrayBuffer (transferables when possible)
777
+ */
778
+
743
779
  // Server side: Send data stream using iterator
744
780
  server.on('/api/stream', async (req, res) => {
745
781
  const stream = new IframeWritableStream({
@@ -762,6 +798,19 @@ server.on('/api/stream', async (req, res) => {
762
798
  await res.sendStream(stream);
763
799
  });
764
800
 
801
+ // Server side: Option 2 (recommended) - create a file stream from Blob/File via from()
802
+ server.on('/api/fileStream2', async (req, res) => {
803
+ const blob = new Blob([/* file bytes */], { type: 'application/octet-stream' });
804
+ const stream = await IframeFileWritableStream.from({
805
+ content: blob,
806
+ fileName: 'large-file.bin',
807
+ mimeType: 'application/octet-stream',
808
+ chunked: true,
809
+ chunkSize: 256 * 1024
810
+ });
811
+ await res.sendStream(stream);
812
+ });
813
+
765
814
  // Client side: Receive stream data
766
815
  const response = await client.send('/api/stream', {}, { streamTimeout: 10000 });
767
816
 
@@ -795,6 +844,28 @@ if (isIframeReadableStream(response.stream)) {
795
844
  }
796
845
  ```
797
846
 
847
+ #### Text file convenience
848
+
849
+ ```typescript
850
+ // Server side: send a UTF-8 text file
851
+ server.on('/api/textFile', async (req, res) => {
852
+ const stream = await IframeFileWritableStream.fromText({
853
+ text: 'hello',
854
+ fileName: 'hello.txt',
855
+ chunked: true,
856
+ chunkSize: 64 * 1024
857
+ });
858
+ await res.sendStream(stream);
859
+ });
860
+
861
+ // Client side: read as UTF-8 text
862
+ const resp = await client.send('/api/textFile', {});
863
+ if (isIframeFileReadableStream(resp.stream)) {
864
+ const text = await resp.stream.readAsText();
865
+ console.log(text);
866
+ }
867
+ ```
868
+
798
869
  #### Client → Server (Client sends stream to server)
799
870
 
800
871
  ```typescript
@@ -849,11 +920,11 @@ server.on('/api/uploadStream', async (req, res) => {
849
920
  | Type | Description |
850
921
  |------|-------------|
851
922
  | `IframeWritableStream` | Writer/producer stream: **created by whichever side is sending the stream** (server→client response stream, or client→server request stream) |
852
- | `IframeFileWritableStream` | File writer/producer stream (base64-encodes internally) |
923
+ | `IframeFileWritableStream` | File writer/producer stream (supports `Uint8Array`/`ArrayBuffer` chunks; uses transferables when possible) |
853
924
  | `IframeReadableStream` | Reader/consumer stream for receiving regular data (regardless of which side sent it) |
854
- | `IframeFileReadableStream` | File reader/consumer stream (base64-decodes internally) |
925
+ | `IframeFileReadableStream` | File reader/consumer stream (supports binary chunks) |
855
926
 
856
- > **Note**: File streams are base64-encoded internally. Base64 introduces ~33% size overhead and can be memory/CPU heavy for very large files. For large files, prefer **chunked** file streams (`chunked: true`) and keep chunk sizes moderate (e.g. 256KB–1MB).
927
+ > **Note**: File stream chunks represent **bytes**. This version transfers file chunks as binary (`ArrayBuffer`/`Uint8Array`) and will use transferables when possible to reduce copying. If you manually yield a `string` chunk, it will be encoded into bytes using **UTF-8**; for binary data, yield `Uint8Array/ArrayBuffer` directly. For large files, prefer **chunked** file streams (`chunked: true`) and keep chunk sizes moderate (e.g. 256KB–1MB).
857
928
 
858
929
  **Stream timeouts:**
859
930
  - `options.streamTimeout` (request option): client-side stream idle timeout while consuming `response.stream` (data/file streams). When triggered, the client performs a heartbeat check (by default `client.isConnect()`); if not alive, the stream fails as disconnected.
@@ -964,6 +1035,7 @@ Create a Client instance.
964
1035
  | `target` | `HTMLIFrameElement \| Window` | Target iframe element or window object |
965
1036
  | `options.secretKey` | `string` | Message isolation identifier (optional) |
966
1037
  | `options.trace` | `boolean \| 'trace' \| 'info' \| 'warn' \| 'error' \| 'silent'` | Trace/log level (optional). Default logs are warn/error only |
1038
+ | `options.strict` | `boolean` | Strict mode (recommended for same-origin defaults). If you did not explicitly configure `targetOrigin/allowedOrigins/validateOrigin`, defaults are constrained to `window.location.origin` (same-origin only). **Note: strict is NOT a cross-origin security configuration**; for cross-origin you must explicitly set `targetOrigin` + `allowedOrigins/validateOrigin`. |
967
1039
  | `options.targetOrigin` | `string` | Override postMessage targetOrigin for sending (optional). If `target` is a `Window`, default is `*`. |
968
1040
  | `options.ackTimeout` | `number` | Global default ACK acknowledgment timeout (ms), default 1000 |
969
1041
  | `options.timeout` | `number` | Global default request timeout (ms), default 5000 |
@@ -978,7 +1050,8 @@ Create a Client instance.
978
1050
  **Notes about `target: Window`:**
979
1051
  - **You must have a `Window` reference** (e.g. from `window.open()`, `window.opener`, or `MessageEvent.source`).
980
1052
  - You **cannot** communicate with an arbitrary browser tab by URL.
981
- - For security, prefer setting a strict `targetOrigin` and configure `allowedOrigins` / `validateOrigin`.
1053
+ - For security, prefer setting a strict `targetOrigin` (avoid `*`) and configure `allowedOrigins` / `validateOrigin`.
1054
+ - `strict: true` only constrains defaults to the current origin (same-origin only); it does **not** automatically make a cross-origin setup secure.
982
1055
 
983
1056
  **Production configuration template:**
984
1057
 
@@ -1049,6 +1122,7 @@ Create a Server instance.
1049
1122
  |-----------|------|-------------|
1050
1123
  | `options.secretKey` | `string` | Message isolation identifier (optional) |
1051
1124
  | `options.trace` | `boolean \| 'trace' \| 'info' \| 'warn' \| 'error' \| 'silent'` | Trace/log level (optional). Default logs are warn/error only |
1125
+ | `options.strict` | `boolean` | Strict mode (recommended for same-origin defaults). If you did not explicitly configure `allowedOrigins/validateOrigin`, it defaults to `allowedOrigins: [window.location.origin]` (same-origin only). **Note: strict is NOT a cross-origin security configuration**; for cross-origin you must explicitly configure allowlists/validators. |
1052
1126
  | `options.ackTimeout` | `number` | Wait for client acknowledgment timeout (ms), default 1000 |
1053
1127
  | `options.maxConcurrentRequestsPerClient` | `number` | Max concurrent in-flight requests per client (per origin + creatorId). Default Infinity |
1054
1128
  | `options.allowedOrigins` | `string \| RegExp \| Array<string \| RegExp>` | Allowlist for incoming message origins (optional, recommended for production) |
@@ -1660,13 +1734,35 @@ try {
1660
1734
  `secretKey` is used for message isolation. When there are multiple iframes or multiple request-iframe instances on a page, using different `secretKey` values can prevent message cross-talk:
1661
1735
 
1662
1736
  ```typescript
1663
- // Communication for iframe A
1664
- const clientA = requestIframeClient(iframeA, { secretKey: 'app-a' });
1665
- const serverA = requestIframeServer({ secretKey: 'app-a' });
1737
+ /**
1738
+ * Communication for iframe A
1739
+ * - Parent page should allowlist iframe A origin
1740
+ * - Inside iframe A should allowlist parent origin
1741
+ */
1742
+ const iframeASrc = iframeA.getAttribute('src');
1743
+ if (!iframeASrc) throw new Error('iframeA src is empty');
1744
+ const iframeAOrigin = new URL(iframeASrc, window.location.href).origin;
1745
+ const clientA = requestIframeClient(iframeA, {
1746
+ secretKey: 'app-a',
1747
+ targetOrigin: iframeAOrigin,
1748
+ allowedOrigins: [iframeAOrigin]
1749
+ });
1750
+ const parentOrigin = 'https://parent.com';
1751
+ const serverA = requestIframeServer({ secretKey: 'app-a', allowedOrigins: [parentOrigin] });
1666
1752
 
1667
- // Communication for iframe B
1668
- const clientB = requestIframeClient(iframeB, { secretKey: 'app-b' });
1669
- const serverB = requestIframeServer({ secretKey: 'app-b' });
1753
+ /**
1754
+ * Communication for iframe B
1755
+ * - Same pattern as iframe A
1756
+ */
1757
+ const iframeBSrc = iframeB.getAttribute('src');
1758
+ if (!iframeBSrc) throw new Error('iframeB src is empty');
1759
+ const iframeBOrigin = new URL(iframeBSrc, window.location.href).origin;
1760
+ const clientB = requestIframeClient(iframeB, {
1761
+ secretKey: 'app-b',
1762
+ targetOrigin: iframeBOrigin,
1763
+ allowedOrigins: [iframeBOrigin]
1764
+ });
1765
+ const serverB = requestIframeServer({ secretKey: 'app-b', allowedOrigins: [parentOrigin] });
1670
1766
  ```
1671
1767
 
1672
1768
  ### 2. Why is ACK acknowledgment needed?
@@ -1681,14 +1777,28 @@ ACK mechanism is similar to TCP handshake, used for:
1681
1777
  `postMessage` itself supports cross-origin communication, request-iframe handles it automatically:
1682
1778
 
1683
1779
  ```typescript
1684
- // Parent page (https://parent.com)
1685
- const client = requestIframeClient(iframe);
1780
+ /**
1781
+ * Parent page (https://parent.com)
1782
+ * - targetOrigin/allowedOrigins should be the iframe origin
1783
+ */
1784
+ const iframeSrc = iframe.getAttribute('src');
1785
+ if (!iframeSrc) throw new Error('iframe src is empty');
1786
+ const childOrigin = new URL(iframeSrc, window.location.href).origin;
1787
+ const client = requestIframeClient(iframe, {
1788
+ secretKey: 'my-app',
1789
+ targetOrigin: childOrigin,
1790
+ allowedOrigins: [childOrigin]
1791
+ });
1686
1792
 
1687
- // Inside iframe (https://child.com)
1688
- const server = requestIframeServer();
1793
+ /**
1794
+ * Inside iframe (https://child.com)
1795
+ * - allowedOrigins should be the real parent origin
1796
+ */
1797
+ const parentOrigin = 'https://parent.com';
1798
+ const server = requestIframeServer({ secretKey: 'my-app', allowedOrigins: [parentOrigin] });
1689
1799
  ```
1690
1800
 
1691
- Just ensure both sides use the same `secretKey`.
1801
+ Just ensure both sides use the same `secretKey`, and configure `targetOrigin` / `allowedOrigins` correctly.
1692
1802
 
1693
1803
  ### 4. Can Server actively push messages?
1694
1804
 
@@ -1699,9 +1809,17 @@ For bidirectional communication, you have 2 options:
1699
1809
  - Use `requestIframeEndpoint()` on both sides (recommended) so each side has **send + handle** in one object
1700
1810
 
1701
1811
  ```typescript
1702
- // Inside iframe
1703
- const server = requestIframeServer({ secretKey: 'my-app' });
1704
- const client = requestIframeClient(window.parent, { secretKey: 'my-app-reverse' });
1812
+ /**
1813
+ * Inside iframe
1814
+ * - For Window targets, always set a strict targetOrigin and allowlist it.
1815
+ */
1816
+ const parentOrigin = 'https://parent.com';
1817
+ const server = requestIframeServer({ secretKey: 'my-app', allowedOrigins: [parentOrigin] });
1818
+ const client = requestIframeClient(window.parent, {
1819
+ secretKey: 'my-app-reverse',
1820
+ targetOrigin: parentOrigin,
1821
+ allowedOrigins: [parentOrigin]
1822
+ });
1705
1823
 
1706
1824
  // Actively send message to parent page
1707
1825
  await client.send('/notify', { event: 'data-changed' });