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.
- package/dist/__tests__/bun.worker.test.d.ts +2 -0
- package/dist/__tests__/bun.worker.test.d.ts.map +1 -0
- package/dist/__tests__/http.test.d.ts +2 -0
- package/dist/__tests__/http.test.d.ts.map +1 -0
- package/dist/__tests__/scripts/api.d.ts +19 -0
- package/dist/__tests__/scripts/api.d.ts.map +1 -0
- package/dist/__tests__/scripts/deno-api.d.ts +2 -0
- package/dist/__tests__/scripts/deno-api.d.ts.map +1 -0
- package/dist/__tests__/scripts/node-api.d.ts +2 -0
- package/dist/__tests__/scripts/node-api.d.ts.map +1 -0
- package/dist/__tests__/scripts/worker.d.ts +2 -0
- package/dist/__tests__/scripts/worker.d.ts.map +1 -0
- package/dist/__tests__/serialization.test.d.ts +2 -0
- package/dist/__tests__/serialization.test.d.ts.map +1 -0
- package/dist/__tests__/stdio-rpc.test.d.ts +2 -0
- package/dist/__tests__/stdio-rpc.test.d.ts.map +1 -0
- package/dist/__tests__/websocket.test.d.ts +2 -0
- package/dist/__tests__/websocket.test.d.ts.map +1 -0
- package/dist/adapters/deno.cjs +38 -0
- package/dist/adapters/deno.js +36 -0
- package/dist/adapters/http.cjs +104 -0
- package/dist/adapters/http.js +101 -0
- package/dist/adapters/iframe.cjs +163 -0
- package/dist/adapters/iframe.js +160 -0
- package/dist/adapters/node.cjs +66 -0
- package/dist/adapters/node.js +64 -0
- package/dist/adapters/websocket.cjs +110 -0
- package/dist/adapters/websocket.js +107 -0
- package/dist/adapters/worker.cjs +101 -0
- package/dist/adapters/worker.js +98 -0
- package/dist/browser-mod.cjs +21 -0
- package/dist/browser-mod.d.ts +7 -0
- package/dist/browser-mod.d.ts.map +1 -0
- package/dist/browser-mod.js +6 -0
- package/dist/channel.cjs +229 -0
- package/dist/channel.js +227 -0
- package/dist/http.cjs +21 -0
- package/dist/http.d.ts +12 -0
- package/dist/http.d.ts.map +1 -0
- package/dist/http.js +15 -0
- package/dist/mod.cjs +28 -0
- package/dist/mod.d.ts +10 -0
- package/dist/mod.d.ts.map +1 -0
- package/dist/mod.js +9 -0
- package/dist/scripts/prepare.d.ts +2 -0
- package/dist/scripts/prepare.d.ts.map +1 -0
- package/dist/scripts/test.d.ts +2 -0
- package/dist/scripts/test.d.ts.map +1 -0
- package/dist/serialization.cjs +41 -0
- package/dist/serialization.js +36 -0
- package/dist/src/adapters/deno.d.ts +17 -0
- package/dist/src/adapters/deno.d.ts.map +1 -0
- package/dist/src/adapters/http.d.ts +32 -0
- package/dist/src/adapters/http.d.ts.map +1 -0
- package/dist/src/adapters/iframe.d.ts +57 -0
- package/dist/src/adapters/iframe.d.ts.map +1 -0
- package/dist/src/adapters/node.d.ts +22 -0
- package/dist/src/adapters/node.d.ts.map +1 -0
- package/dist/src/adapters/websocket.d.ts +38 -0
- package/dist/src/adapters/websocket.d.ts.map +1 -0
- package/dist/src/adapters/worker.d.ts +25 -0
- package/dist/src/adapters/worker.d.ts.map +1 -0
- package/dist/src/channel.d.ts +41 -0
- package/dist/src/channel.d.ts.map +1 -0
- package/{src/interface.ts → dist/src/interface.d.ts} +7 -8
- package/dist/src/interface.d.ts.map +1 -0
- package/dist/src/serialization.d.ts +19 -0
- package/dist/src/serialization.d.ts.map +1 -0
- package/dist/src/utils.d.ts +2 -0
- package/dist/src/utils.d.ts.map +1 -0
- package/dist/utils.cjs +10 -0
- package/dist/utils.js +8 -0
- package/package.json +4 -1
- package/.turbo/turbo-build.log +0 -15
- package/.turbo/turbo-test.log +0 -57
- package/__deno_tests__/deno-web-worker.test.ts +0 -31
- package/__tests__/bun.worker.test.ts +0 -24
- package/__tests__/http.test.ts +0 -100
- package/__tests__/scripts/api.ts +0 -47
- package/__tests__/scripts/deno-api.ts +0 -5
- package/__tests__/scripts/node-api.ts +0 -5
- package/__tests__/scripts/worker.ts +0 -10
- package/__tests__/serialization.test.ts +0 -31
- package/__tests__/stdio-rpc.test.ts +0 -90
- package/__tests__/websocket.test.ts +0 -111
- package/browser-mod.ts +0 -6
- package/deno.json +0 -21
- package/http.ts +0 -24
- package/mod.ts +0 -9
- package/rollup.config.js +0 -33
- package/scripts/prepare.ts +0 -12
- package/scripts/test.ts +0 -11
- package/src/adapters/deno.ts +0 -38
- package/src/adapters/http.ts +0 -115
- package/src/adapters/iframe.ts +0 -180
- package/src/adapters/node.ts +0 -69
- package/src/adapters/websocket.ts +0 -127
- package/src/adapters/worker.ts +0 -116
- package/src/channel.ts +0 -267
- package/src/serialization.ts +0 -51
- package/src/utils.ts +0 -6
- package/tsconfig.json +0 -31
- package/typedoc.json +0 -12
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bun.worker.test.d.ts","sourceRoot":"","sources":["../../__tests__/bun.worker.test.ts"],"names":[],"mappings":""}
|
|
@@ -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 @@
|
|
|
1
|
+
{"version":3,"file":"deno-api.d.ts","sourceRoot":"","sources":["../../../__tests__/scripts/deno-api.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"node-api.d.ts","sourceRoot":"","sources":["../../../__tests__/scripts/node-api.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"worker.d.ts","sourceRoot":"","sources":["../../../__tests__/scripts/worker.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"serialization.test.d.ts","sourceRoot":"","sources":["../../__tests__/serialization.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stdio-rpc.test.d.ts","sourceRoot":"","sources":["../../__tests__/stdio-rpc.test.ts"],"names":[],"mappings":""}
|
|
@@ -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 };
|