kkrpc 0.0.9 → 0.0.10

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 (103) hide show
  1. package/dist/__tests__/bun.worker.test.d.ts +2 -0
  2. package/dist/__tests__/bun.worker.test.d.ts.map +1 -0
  3. package/dist/__tests__/http.test.d.ts +2 -0
  4. package/dist/__tests__/http.test.d.ts.map +1 -0
  5. package/dist/__tests__/scripts/api.d.ts +19 -0
  6. package/dist/__tests__/scripts/api.d.ts.map +1 -0
  7. package/dist/__tests__/scripts/deno-api.d.ts +2 -0
  8. package/dist/__tests__/scripts/deno-api.d.ts.map +1 -0
  9. package/dist/__tests__/scripts/node-api.d.ts +2 -0
  10. package/dist/__tests__/scripts/node-api.d.ts.map +1 -0
  11. package/dist/__tests__/scripts/worker.d.ts +2 -0
  12. package/dist/__tests__/scripts/worker.d.ts.map +1 -0
  13. package/dist/__tests__/serialization.test.d.ts +2 -0
  14. package/dist/__tests__/serialization.test.d.ts.map +1 -0
  15. package/dist/__tests__/stdio-rpc.test.d.ts +2 -0
  16. package/dist/__tests__/stdio-rpc.test.d.ts.map +1 -0
  17. package/dist/__tests__/websocket.test.d.ts +2 -0
  18. package/dist/__tests__/websocket.test.d.ts.map +1 -0
  19. package/dist/adapters/deno.cjs +38 -0
  20. package/dist/adapters/deno.js +36 -0
  21. package/dist/adapters/http.cjs +104 -0
  22. package/dist/adapters/http.js +101 -0
  23. package/dist/adapters/iframe.cjs +163 -0
  24. package/dist/adapters/iframe.js +160 -0
  25. package/dist/adapters/node.cjs +66 -0
  26. package/dist/adapters/node.js +64 -0
  27. package/dist/adapters/websocket.cjs +110 -0
  28. package/dist/adapters/websocket.js +107 -0
  29. package/dist/adapters/worker.cjs +101 -0
  30. package/dist/adapters/worker.js +98 -0
  31. package/dist/browser-mod.cjs +21 -0
  32. package/dist/browser-mod.d.ts +7 -0
  33. package/dist/browser-mod.d.ts.map +1 -0
  34. package/dist/browser-mod.js +6 -0
  35. package/dist/channel.cjs +229 -0
  36. package/dist/channel.js +227 -0
  37. package/dist/http.cjs +21 -0
  38. package/dist/http.d.ts +12 -0
  39. package/dist/http.d.ts.map +1 -0
  40. package/dist/http.js +15 -0
  41. package/dist/mod.cjs +28 -0
  42. package/dist/mod.d.ts +10 -0
  43. package/dist/mod.d.ts.map +1 -0
  44. package/dist/mod.js +9 -0
  45. package/dist/scripts/prepare.d.ts +2 -0
  46. package/dist/scripts/prepare.d.ts.map +1 -0
  47. package/dist/scripts/test.d.ts +2 -0
  48. package/dist/scripts/test.d.ts.map +1 -0
  49. package/dist/serialization.cjs +41 -0
  50. package/dist/serialization.js +36 -0
  51. package/dist/src/adapters/deno.d.ts +17 -0
  52. package/dist/src/adapters/deno.d.ts.map +1 -0
  53. package/dist/src/adapters/http.d.ts +32 -0
  54. package/dist/src/adapters/http.d.ts.map +1 -0
  55. package/dist/src/adapters/iframe.d.ts +57 -0
  56. package/dist/src/adapters/iframe.d.ts.map +1 -0
  57. package/dist/src/adapters/node.d.ts +22 -0
  58. package/dist/src/adapters/node.d.ts.map +1 -0
  59. package/dist/src/adapters/websocket.d.ts +38 -0
  60. package/dist/src/adapters/websocket.d.ts.map +1 -0
  61. package/dist/src/adapters/worker.d.ts +25 -0
  62. package/dist/src/adapters/worker.d.ts.map +1 -0
  63. package/dist/src/channel.d.ts +41 -0
  64. package/dist/src/channel.d.ts.map +1 -0
  65. package/{src/interface.ts → dist/src/interface.d.ts} +7 -8
  66. package/dist/src/interface.d.ts.map +1 -0
  67. package/dist/src/serialization.d.ts +19 -0
  68. package/dist/src/serialization.d.ts.map +1 -0
  69. package/dist/src/utils.d.ts +2 -0
  70. package/dist/src/utils.d.ts.map +1 -0
  71. package/dist/utils.cjs +10 -0
  72. package/dist/utils.js +8 -0
  73. package/package.json +4 -1
  74. package/.turbo/turbo-build.log +0 -15
  75. package/.turbo/turbo-test.log +0 -57
  76. package/__deno_tests__/deno-web-worker.test.ts +0 -31
  77. package/__tests__/bun.worker.test.ts +0 -24
  78. package/__tests__/http.test.ts +0 -100
  79. package/__tests__/scripts/api.ts +0 -47
  80. package/__tests__/scripts/deno-api.ts +0 -5
  81. package/__tests__/scripts/node-api.ts +0 -5
  82. package/__tests__/scripts/worker.ts +0 -10
  83. package/__tests__/serialization.test.ts +0 -31
  84. package/__tests__/stdio-rpc.test.ts +0 -90
  85. package/__tests__/websocket.test.ts +0 -111
  86. package/browser-mod.ts +0 -6
  87. package/deno.json +0 -21
  88. package/http.ts +0 -24
  89. package/mod.ts +0 -9
  90. package/rollup.config.js +0 -33
  91. package/scripts/prepare.ts +0 -12
  92. package/scripts/test.ts +0 -11
  93. package/src/adapters/deno.ts +0 -38
  94. package/src/adapters/http.ts +0 -115
  95. package/src/adapters/iframe.ts +0 -180
  96. package/src/adapters/node.ts +0 -69
  97. package/src/adapters/websocket.ts +0 -127
  98. package/src/adapters/worker.ts +0 -116
  99. package/src/channel.ts +0 -267
  100. package/src/serialization.ts +0 -51
  101. package/src/utils.ts +0 -6
  102. package/tsconfig.json +0 -31
  103. package/typedoc.json +0 -12
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=bun.worker.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bun.worker.test.d.ts","sourceRoot":"","sources":["../../__tests__/bun.worker.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=http.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"http.test.d.ts","sourceRoot":"","sources":["../../__tests__/http.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,19 @@
1
+ export interface API {
2
+ echo(message: string): Promise<string>;
3
+ add(a: number, b: number): Promise<number>;
4
+ subtract(a: number, b: number): Promise<number>;
5
+ addCallback(a: number, b: number, callback: (result: number) => void): void;
6
+ math: {
7
+ grade1: {
8
+ add(a: number, b: number, callback?: (result: number) => void): Promise<number>;
9
+ };
10
+ grade2: {
11
+ multiply(a: number, b: number, callback?: (result: number) => void): Promise<number>;
12
+ };
13
+ grade3: {
14
+ divide(a: number, b: number, callback?: (result: number) => void): Promise<number>;
15
+ };
16
+ };
17
+ }
18
+ export declare const apiMethods: API;
19
+ //# sourceMappingURL=api.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../../../__tests__/scripts/api.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,GAAG;IACnB,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAA;IACtC,GAAG,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAA;IAC1C,QAAQ,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAA;IAC/C,WAAW,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,GAAG,IAAI,CAAA;IAC3E,IAAI,EAAE;QACL,MAAM,EAAE;YACP,GAAG,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,GAAG,OAAO,CAAC,MAAM,CAAC,CAAA;SAC/E,CAAA;QACD,MAAM,EAAE;YACP,QAAQ,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,GAAG,OAAO,CAAC,MAAM,CAAC,CAAA;SACpF,CAAA;QACD,MAAM,EAAE;YACP,MAAM,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,GAAG,OAAO,CAAC,MAAM,CAAC,CAAA;SAClF,CAAA;KACD,CAAA;CACD;AAGD,eAAO,MAAM,UAAU,EAAE,GA2BxB,CAAA"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=deno-api.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"deno-api.d.ts","sourceRoot":"","sources":["../../../__tests__/scripts/deno-api.ts"],"names":[],"mappings":""}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=node-api.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"node-api.d.ts","sourceRoot":"","sources":["../../../__tests__/scripts/node-api.ts"],"names":[],"mappings":""}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=worker.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"worker.d.ts","sourceRoot":"","sources":["../../../__tests__/scripts/worker.ts"],"names":[],"mappings":""}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=serialization.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"serialization.test.d.ts","sourceRoot":"","sources":["../../__tests__/serialization.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=stdio-rpc.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stdio-rpc.test.d.ts","sourceRoot":"","sources":["../../__tests__/stdio-rpc.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=websocket.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"websocket.test.d.ts","sourceRoot":"","sources":["../../__tests__/websocket.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,38 @@
1
+ 'use strict';
2
+
3
+ var node_buffer = require('node:buffer');
4
+
5
+ /**
6
+ * Stdio implementation for Deno
7
+ * Deno doesn't have `process` object, and have a completely different stdio API,
8
+ * This implementation wrap Deno's `Deno.stdin` and `Deno.stdout` to follow StdioInterface
9
+ */
10
+ class DenoIo {
11
+ readStream;
12
+ writeStream;
13
+ reader;
14
+ name = "deno-io";
15
+ constructor(readStream, writeStream) {
16
+ this.readStream = readStream;
17
+ this.writeStream = writeStream;
18
+ this.reader = this.readStream.getReader();
19
+ // const writer = this.writeStream.getWriter()
20
+ // const encoder = new TextEncoder()
21
+ // writer.write(encoder.encode("hello"))
22
+ }
23
+ async read() {
24
+ const { value, done } = await this.reader.read();
25
+ if (done) {
26
+ return null; // End of input
27
+ }
28
+ return node_buffer.Buffer.from(value);
29
+ }
30
+ write(data) {
31
+ const encoder = new TextEncoder();
32
+ const encodedData = encoder.encode(data + "\n");
33
+ Deno.stdout.writeSync(encodedData);
34
+ return Promise.resolve();
35
+ }
36
+ }
37
+
38
+ exports.DenoIo = DenoIo;
@@ -0,0 +1,36 @@
1
+ import { Buffer } from 'node:buffer';
2
+
3
+ /**
4
+ * Stdio implementation for Deno
5
+ * Deno doesn't have `process` object, and have a completely different stdio API,
6
+ * This implementation wrap Deno's `Deno.stdin` and `Deno.stdout` to follow StdioInterface
7
+ */
8
+ class DenoIo {
9
+ readStream;
10
+ writeStream;
11
+ reader;
12
+ name = "deno-io";
13
+ constructor(readStream, writeStream) {
14
+ this.readStream = readStream;
15
+ this.writeStream = writeStream;
16
+ this.reader = this.readStream.getReader();
17
+ // const writer = this.writeStream.getWriter()
18
+ // const encoder = new TextEncoder()
19
+ // writer.write(encoder.encode("hello"))
20
+ }
21
+ async read() {
22
+ const { value, done } = await this.reader.read();
23
+ if (done) {
24
+ return null; // End of input
25
+ }
26
+ return Buffer.from(value);
27
+ }
28
+ write(data) {
29
+ const encoder = new TextEncoder();
30
+ const encodedData = encoder.encode(data + "\n");
31
+ Deno.stdout.writeSync(encodedData);
32
+ return Promise.resolve();
33
+ }
34
+ }
35
+
36
+ export { DenoIo };
@@ -0,0 +1,104 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * HTTP Client implementation of IoInterface
5
+ */
6
+ class HTTPClientIO {
7
+ options;
8
+ name = "http-client-io";
9
+ messageQueue = [];
10
+ resolveRead = null;
11
+ constructor(options) {
12
+ this.options = options;
13
+ }
14
+ async read() {
15
+ if (this.messageQueue.length > 0) {
16
+ return this.messageQueue.shift() ?? null;
17
+ }
18
+ return new Promise((resolve) => {
19
+ this.resolveRead = resolve;
20
+ });
21
+ }
22
+ async write(data) {
23
+ try {
24
+ const response = await fetch(this.options.url, {
25
+ method: "POST",
26
+ headers: {
27
+ "Content-Type": "application/json",
28
+ ...this.options.headers
29
+ },
30
+ body: data
31
+ });
32
+ if (!response.ok) {
33
+ throw new Error(`HTTP error! status: ${response.status}`);
34
+ }
35
+ const responseText = await response.text();
36
+ if (this.resolveRead) {
37
+ this.resolveRead(responseText);
38
+ this.resolveRead = null;
39
+ }
40
+ else {
41
+ this.messageQueue.push(responseText);
42
+ }
43
+ }
44
+ catch (error) {
45
+ console.error("HTTP request failed:", error);
46
+ if (this.resolveRead) {
47
+ this.resolveRead(null);
48
+ this.resolveRead = null;
49
+ }
50
+ }
51
+ }
52
+ }
53
+ /**
54
+ * HTTP Server implementation of IoInterface
55
+ */
56
+ class HTTPServerIO {
57
+ name = "http-server-io";
58
+ messageQueue = [];
59
+ resolveRead = null;
60
+ pendingResponses = new Map();
61
+ constructor() { }
62
+ async read() {
63
+ if (this.messageQueue.length > 0) {
64
+ return this.messageQueue.shift() ?? null;
65
+ }
66
+ return new Promise((resolve) => {
67
+ this.resolveRead = resolve;
68
+ });
69
+ }
70
+ async write(data) {
71
+ // Parse the response to get the request ID
72
+ const response = JSON.parse(data);
73
+ const requestId = response.id;
74
+ const resolveResponse = this.pendingResponses.get(requestId);
75
+ if (resolveResponse) {
76
+ resolveResponse(data);
77
+ this.pendingResponses.delete(requestId);
78
+ }
79
+ }
80
+ async handleRequest(reqData) {
81
+ try {
82
+ // Parse the request to get its ID
83
+ const requestData = JSON.parse(reqData);
84
+ const requestId = requestData.id;
85
+ if (this.resolveRead) {
86
+ this.resolveRead(reqData);
87
+ this.resolveRead = null;
88
+ }
89
+ else {
90
+ this.messageQueue.push(reqData);
91
+ }
92
+ return new Promise((resolve) => {
93
+ this.pendingResponses.set(requestId, resolve);
94
+ });
95
+ }
96
+ catch (error) {
97
+ console.error("RPC processing error:", error);
98
+ throw new Error("Internal server error");
99
+ }
100
+ }
101
+ }
102
+
103
+ exports.HTTPClientIO = HTTPClientIO;
104
+ exports.HTTPServerIO = HTTPServerIO;
@@ -0,0 +1,101 @@
1
+ /**
2
+ * HTTP Client implementation of IoInterface
3
+ */
4
+ class HTTPClientIO {
5
+ options;
6
+ name = "http-client-io";
7
+ messageQueue = [];
8
+ resolveRead = null;
9
+ constructor(options) {
10
+ this.options = options;
11
+ }
12
+ async read() {
13
+ if (this.messageQueue.length > 0) {
14
+ return this.messageQueue.shift() ?? null;
15
+ }
16
+ return new Promise((resolve) => {
17
+ this.resolveRead = resolve;
18
+ });
19
+ }
20
+ async write(data) {
21
+ try {
22
+ const response = await fetch(this.options.url, {
23
+ method: "POST",
24
+ headers: {
25
+ "Content-Type": "application/json",
26
+ ...this.options.headers
27
+ },
28
+ body: data
29
+ });
30
+ if (!response.ok) {
31
+ throw new Error(`HTTP error! status: ${response.status}`);
32
+ }
33
+ const responseText = await response.text();
34
+ if (this.resolveRead) {
35
+ this.resolveRead(responseText);
36
+ this.resolveRead = null;
37
+ }
38
+ else {
39
+ this.messageQueue.push(responseText);
40
+ }
41
+ }
42
+ catch (error) {
43
+ console.error("HTTP request failed:", error);
44
+ if (this.resolveRead) {
45
+ this.resolveRead(null);
46
+ this.resolveRead = null;
47
+ }
48
+ }
49
+ }
50
+ }
51
+ /**
52
+ * HTTP Server implementation of IoInterface
53
+ */
54
+ class HTTPServerIO {
55
+ name = "http-server-io";
56
+ messageQueue = [];
57
+ resolveRead = null;
58
+ pendingResponses = new Map();
59
+ constructor() { }
60
+ async read() {
61
+ if (this.messageQueue.length > 0) {
62
+ return this.messageQueue.shift() ?? null;
63
+ }
64
+ return new Promise((resolve) => {
65
+ this.resolveRead = resolve;
66
+ });
67
+ }
68
+ async write(data) {
69
+ // Parse the response to get the request ID
70
+ const response = JSON.parse(data);
71
+ const requestId = response.id;
72
+ const resolveResponse = this.pendingResponses.get(requestId);
73
+ if (resolveResponse) {
74
+ resolveResponse(data);
75
+ this.pendingResponses.delete(requestId);
76
+ }
77
+ }
78
+ async handleRequest(reqData) {
79
+ try {
80
+ // Parse the request to get its ID
81
+ const requestData = JSON.parse(reqData);
82
+ const requestId = requestData.id;
83
+ if (this.resolveRead) {
84
+ this.resolveRead(reqData);
85
+ this.resolveRead = null;
86
+ }
87
+ else {
88
+ this.messageQueue.push(reqData);
89
+ }
90
+ return new Promise((resolve) => {
91
+ this.pendingResponses.set(requestId, resolve);
92
+ });
93
+ }
94
+ catch (error) {
95
+ console.error("RPC processing error:", error);
96
+ throw new Error("Internal server error");
97
+ }
98
+ }
99
+ }
100
+
101
+ export { HTTPClientIO, HTTPServerIO };
@@ -0,0 +1,163 @@
1
+ 'use strict';
2
+
3
+ const DESTROY_SIGNAL = "__DESTROY__";
4
+ const PORT_INIT_SIGNAL = "__PORT_INIT__";
5
+ /**
6
+ * This design relies on built-in `MessageChannel`, and requires a pairing process to establish the port.
7
+ * The `PORT_INIT_SIGNAL` is designed to be initiated by the child frame, parent window will wait for the signal and establish the port.
8
+ *
9
+ * If `PORT_INIT_SIGNAL` is started by the parent window, there has to be a delay (with `setTimeout`) to wait for the child frame to listen to the signal.
10
+ * Parent window can easily listen to iframe onload event, but there is no way to know when child JS is ready to listen to the message without
11
+ * letting child `postMessage` a signal first.
12
+ *
13
+ * It's much easier to make sure parent window is ready (listening) before iframe is loaded, so `MessageChannel` is designed to be created from iframe's side.
14
+ *
15
+ * It's a good practice to call `destroy()` on either side of the channel to close `MessageChannel` and release resources.
16
+ */
17
+ class IframeParentIO {
18
+ targetWindow;
19
+ name = "iframe-parent-io";
20
+ messageQueue = [];
21
+ resolveRead = null;
22
+ port = null;
23
+ /**
24
+ * @example
25
+ * ```ts
26
+ * const io = new IframeParentIO(iframeRef.contentWindow);
27
+ * const rpc = new RPCChannel(io, {
28
+ * expose: {
29
+ * add: (a: number, b: number) => Promise.resolve(a + b),
30
+ * },
31
+ * });
32
+ * ```
33
+ */
34
+ constructor(targetWindow) {
35
+ this.targetWindow = targetWindow;
36
+ this.port = null;
37
+ window.addEventListener("message", (event) => {
38
+ if (event.source !== this.targetWindow)
39
+ return;
40
+ if (event.data === PORT_INIT_SIGNAL && event.ports.length > 0) {
41
+ this.port = event.ports[0];
42
+ this.port.onmessage = this.handleMessage;
43
+ while (this.messageQueue.length > 0) {
44
+ const message = this.messageQueue.shift();
45
+ if (message)
46
+ this.port.postMessage(message);
47
+ }
48
+ }
49
+ });
50
+ }
51
+ handleMessage = (event) => {
52
+ const message = event.data;
53
+ // Handle destroy signal
54
+ if (message === DESTROY_SIGNAL) {
55
+ this.destroy();
56
+ return;
57
+ }
58
+ if (this.resolveRead) {
59
+ // If there's a pending read, resolve it immediately
60
+ this.resolveRead(message);
61
+ this.resolveRead = null;
62
+ }
63
+ else {
64
+ // Otherwise, queue the message
65
+ this.messageQueue.push(message);
66
+ }
67
+ };
68
+ async read() {
69
+ // If there are queued messages, return the first one
70
+ if (this.messageQueue.length > 0) {
71
+ return this.messageQueue.shift() ?? null;
72
+ }
73
+ // Otherwise, wait for the next message
74
+ return new Promise((resolve) => {
75
+ this.resolveRead = resolve;
76
+ });
77
+ }
78
+ async write(data) {
79
+ if (!this.port) {
80
+ this.messageQueue.push(data);
81
+ return;
82
+ }
83
+ this.port.postMessage(data);
84
+ }
85
+ destroy() {
86
+ if (this.port) {
87
+ this.port.postMessage(DESTROY_SIGNAL);
88
+ this.port.close();
89
+ }
90
+ }
91
+ signalDestroy() {
92
+ if (this.port) {
93
+ this.port.postMessage(DESTROY_SIGNAL);
94
+ }
95
+ }
96
+ }
97
+ // Child frame version
98
+ class IframeChildIO {
99
+ name = "iframe-child-io";
100
+ messageQueue = [];
101
+ resolveRead = null;
102
+ port = null;
103
+ pendingMessages = [];
104
+ initialized;
105
+ channel;
106
+ constructor() {
107
+ this.channel = new MessageChannel();
108
+ this.port = this.channel.port1;
109
+ this.port.onmessage = this.handleMessage;
110
+ window.parent.postMessage(PORT_INIT_SIGNAL, "*", [this.channel.port2]);
111
+ this.initialized = Promise.resolve();
112
+ }
113
+ handleMessage = (event) => {
114
+ const message = event.data;
115
+ // Handle destroy signal
116
+ if (message === DESTROY_SIGNAL) {
117
+ this.destroy();
118
+ return;
119
+ }
120
+ if (this.resolveRead) {
121
+ this.resolveRead(message);
122
+ this.resolveRead = null;
123
+ }
124
+ else {
125
+ this.messageQueue.push(message);
126
+ }
127
+ };
128
+ async read() {
129
+ await this.initialized;
130
+ if (this.messageQueue.length > 0) {
131
+ return this.messageQueue.shift() ?? null;
132
+ }
133
+ return new Promise((resolve) => {
134
+ this.resolveRead = resolve;
135
+ });
136
+ }
137
+ async write(data) {
138
+ await this.initialized;
139
+ if (this.port) {
140
+ this.port.postMessage(data);
141
+ }
142
+ else {
143
+ this.pendingMessages.push(data);
144
+ }
145
+ }
146
+ destroy() {
147
+ if (this.port) {
148
+ this.port.postMessage(DESTROY_SIGNAL);
149
+ this.port.close();
150
+ }
151
+ }
152
+ signalDestroy() {
153
+ if (this.port) {
154
+ this.port.postMessage(DESTROY_SIGNAL);
155
+ }
156
+ else {
157
+ this.pendingMessages.push(DESTROY_SIGNAL);
158
+ }
159
+ }
160
+ }
161
+
162
+ exports.IframeChildIO = IframeChildIO;
163
+ exports.IframeParentIO = IframeParentIO;
@@ -0,0 +1,160 @@
1
+ const DESTROY_SIGNAL = "__DESTROY__";
2
+ const PORT_INIT_SIGNAL = "__PORT_INIT__";
3
+ /**
4
+ * This design relies on built-in `MessageChannel`, and requires a pairing process to establish the port.
5
+ * The `PORT_INIT_SIGNAL` is designed to be initiated by the child frame, parent window will wait for the signal and establish the port.
6
+ *
7
+ * If `PORT_INIT_SIGNAL` is started by the parent window, there has to be a delay (with `setTimeout`) to wait for the child frame to listen to the signal.
8
+ * Parent window can easily listen to iframe onload event, but there is no way to know when child JS is ready to listen to the message without
9
+ * letting child `postMessage` a signal first.
10
+ *
11
+ * It's much easier to make sure parent window is ready (listening) before iframe is loaded, so `MessageChannel` is designed to be created from iframe's side.
12
+ *
13
+ * It's a good practice to call `destroy()` on either side of the channel to close `MessageChannel` and release resources.
14
+ */
15
+ class IframeParentIO {
16
+ targetWindow;
17
+ name = "iframe-parent-io";
18
+ messageQueue = [];
19
+ resolveRead = null;
20
+ port = null;
21
+ /**
22
+ * @example
23
+ * ```ts
24
+ * const io = new IframeParentIO(iframeRef.contentWindow);
25
+ * const rpc = new RPCChannel(io, {
26
+ * expose: {
27
+ * add: (a: number, b: number) => Promise.resolve(a + b),
28
+ * },
29
+ * });
30
+ * ```
31
+ */
32
+ constructor(targetWindow) {
33
+ this.targetWindow = targetWindow;
34
+ this.port = null;
35
+ window.addEventListener("message", (event) => {
36
+ if (event.source !== this.targetWindow)
37
+ return;
38
+ if (event.data === PORT_INIT_SIGNAL && event.ports.length > 0) {
39
+ this.port = event.ports[0];
40
+ this.port.onmessage = this.handleMessage;
41
+ while (this.messageQueue.length > 0) {
42
+ const message = this.messageQueue.shift();
43
+ if (message)
44
+ this.port.postMessage(message);
45
+ }
46
+ }
47
+ });
48
+ }
49
+ handleMessage = (event) => {
50
+ const message = event.data;
51
+ // Handle destroy signal
52
+ if (message === DESTROY_SIGNAL) {
53
+ this.destroy();
54
+ return;
55
+ }
56
+ if (this.resolveRead) {
57
+ // If there's a pending read, resolve it immediately
58
+ this.resolveRead(message);
59
+ this.resolveRead = null;
60
+ }
61
+ else {
62
+ // Otherwise, queue the message
63
+ this.messageQueue.push(message);
64
+ }
65
+ };
66
+ async read() {
67
+ // If there are queued messages, return the first one
68
+ if (this.messageQueue.length > 0) {
69
+ return this.messageQueue.shift() ?? null;
70
+ }
71
+ // Otherwise, wait for the next message
72
+ return new Promise((resolve) => {
73
+ this.resolveRead = resolve;
74
+ });
75
+ }
76
+ async write(data) {
77
+ if (!this.port) {
78
+ this.messageQueue.push(data);
79
+ return;
80
+ }
81
+ this.port.postMessage(data);
82
+ }
83
+ destroy() {
84
+ if (this.port) {
85
+ this.port.postMessage(DESTROY_SIGNAL);
86
+ this.port.close();
87
+ }
88
+ }
89
+ signalDestroy() {
90
+ if (this.port) {
91
+ this.port.postMessage(DESTROY_SIGNAL);
92
+ }
93
+ }
94
+ }
95
+ // Child frame version
96
+ class IframeChildIO {
97
+ name = "iframe-child-io";
98
+ messageQueue = [];
99
+ resolveRead = null;
100
+ port = null;
101
+ pendingMessages = [];
102
+ initialized;
103
+ channel;
104
+ constructor() {
105
+ this.channel = new MessageChannel();
106
+ this.port = this.channel.port1;
107
+ this.port.onmessage = this.handleMessage;
108
+ window.parent.postMessage(PORT_INIT_SIGNAL, "*", [this.channel.port2]);
109
+ this.initialized = Promise.resolve();
110
+ }
111
+ handleMessage = (event) => {
112
+ const message = event.data;
113
+ // Handle destroy signal
114
+ if (message === DESTROY_SIGNAL) {
115
+ this.destroy();
116
+ return;
117
+ }
118
+ if (this.resolveRead) {
119
+ this.resolveRead(message);
120
+ this.resolveRead = null;
121
+ }
122
+ else {
123
+ this.messageQueue.push(message);
124
+ }
125
+ };
126
+ async read() {
127
+ await this.initialized;
128
+ if (this.messageQueue.length > 0) {
129
+ return this.messageQueue.shift() ?? null;
130
+ }
131
+ return new Promise((resolve) => {
132
+ this.resolveRead = resolve;
133
+ });
134
+ }
135
+ async write(data) {
136
+ await this.initialized;
137
+ if (this.port) {
138
+ this.port.postMessage(data);
139
+ }
140
+ else {
141
+ this.pendingMessages.push(data);
142
+ }
143
+ }
144
+ destroy() {
145
+ if (this.port) {
146
+ this.port.postMessage(DESTROY_SIGNAL);
147
+ this.port.close();
148
+ }
149
+ }
150
+ signalDestroy() {
151
+ if (this.port) {
152
+ this.port.postMessage(DESTROY_SIGNAL);
153
+ }
154
+ else {
155
+ this.pendingMessages.push(DESTROY_SIGNAL);
156
+ }
157
+ }
158
+ }
159
+
160
+ export { IframeChildIO, IframeParentIO };