├── .editorconfig
├── .github
└── workflows
│ └── publish-to-npm.yml
├── .gitignore
├── .vscode
└── settings.json
├── LICENSE-APACHE
├── LICENSE-MIT
├── README.md
├── deno.json
├── generate.sh
├── import_map.json
├── scripts
└── build-npm-packages
│ ├── buildWrp.ts
│ ├── buildWrpJotai.ts
│ ├── buildWrpReact.ts
│ ├── entrypoint.ts
│ └── index.ts
└── src
├── channel.ts
├── compat
└── std
│ └── io
│ └── buffer.ts
├── generated
├── index.ts
└── messages
│ ├── index.ts
│ └── pbkit
│ ├── index.ts
│ └── wrp
│ ├── WrpGuestMessage_ReqFinish.ts
│ ├── WrpGuestMessage_ReqPayload.ts
│ ├── WrpGuestMessage_ReqStart.ts
│ ├── WrpGuestMessage_ResFinish.ts
│ ├── WrpHostMessage_Error.ts
│ ├── WrpHostMessage_Initialize.ts
│ ├── WrpHostMessage_ResFinish.ts
│ ├── WrpHostMessage_ResPayload.ts
│ ├── WrpHostMessage_ResStart.ts
│ ├── WrpMessage.ts
│ └── index.ts
├── glue
├── android.ts
├── child-window.ts
├── iframe.ts
├── index.ts
├── ios.ts
├── misc
│ ├── util.ts
│ └── web.ts
├── parent-window.ts
└── parent.ts
├── guest.ts
├── host.ts
├── jotai
├── iframe.ts
├── index.ts
├── parent.ts
└── pwasa.ts
├── metadata.ts
├── misc.ts
├── react
├── index.ts
├── server.ts
├── useOnceEffect.ts
├── useWrpClientImpl.ts
├── useWrpIframeSocket.ts
└── useWrpParentSocket.ts
├── rpc
├── client.ts
├── misc.ts
└── server.ts
├── socket.ts
├── tee.ts
└── wrp.proto
/.editorconfig:
--------------------------------------------------------------------------------
1 | [*]
2 | end_of_line = lf
3 | insert_final_newline = true
4 |
5 | [*.{proto,ts,js,json,sh}]
6 | charset = utf-8
7 | indent_style = space
8 | indent_size = 2
9 |
--------------------------------------------------------------------------------
/.github/workflows/publish-to-npm.yml:
--------------------------------------------------------------------------------
1 | name: Publish to NPM
2 | on:
3 | release:
4 | types: [released]
5 |
6 | jobs:
7 | publish-to-npm:
8 | runs-on: ubuntu-latest
9 | steps:
10 | - uses: actions/checkout@v2
11 | - uses: actions/setup-node@v2
12 | with:
13 | node-version: 17.x
14 | registry-url: https://registry.npmjs.org
15 | - uses: denoland/setup-deno@v1
16 | with:
17 | deno-version: v1.x
18 | - run: deno task build:npm
19 | - run: npm publish --access=public
20 | working-directory: tmp/npm/wrp
21 | env:
22 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
23 | - run: npm publish --access=public
24 | working-directory: tmp/npm/wrp-react
25 | env:
26 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
27 | - run: npm publish --access=public
28 | working-directory: tmp/npm/wrp-jotai
29 | env:
30 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
31 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /tmp
2 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "deno.enable": true,
3 | "deno.lint": false,
4 | "deno.unstable": true,
5 | "deno.importMap": "./import_map.json",
6 | "editor.formatOnSave": true,
7 | "editor.defaultFormatter": "denoland.vscode-deno",
8 | "[typescript]": {
9 | "editor.defaultFormatter": "denoland.vscode-deno"
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/LICENSE-APACHE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
--------------------------------------------------------------------------------
/LICENSE-MIT:
--------------------------------------------------------------------------------
1 | Permission is hereby granted, free of charge, to any
2 | person obtaining a copy of this software and associated
3 | documentation files (the "Software"), to deal in the
4 | Software without restriction, including without
5 | limitation the rights to use, copy, modify, merge,
6 | publish, distribute, sublicense, and/or sell copies of
7 | the Software, and to permit persons to whom the Software
8 | is furnished to do so, subject to the following
9 | conditions:
10 |
11 | The above copyright notice and this permission notice
12 | shall be included in all copies or substantial portions
13 | of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
16 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
17 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
18 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
19 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
22 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
23 | DEALINGS IN THE SOFTWARE.
24 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # WRP - Webview/Worker Request Protocol
2 |
3 | pronounce as **wrap**(`ræp`)
4 |
5 | ## Design
6 |
7 |
11 |
12 | - _Glue_ - Platform-specific code to implement sockets
13 | - _Socket_ - Way for sending and receiving data between the two platforms
14 | - _Channel_ - An interface that allows information to be exchanged in units of
15 | messages using sockets.
16 | - _Guest_ - An interface that can send requests to hosts using channels.
17 | - _Host_ - An interface that allows you to process and respond to requests
18 | received from guests.
19 |
20 |
23 |
24 | Whenever the channel sends a message, it writes the message size as a 4-byte
25 | little-endian integer to the socket, and then writes a message payload as that
26 | size.
27 |
28 | The message payload is defined in the [./src/wrp.proto](./src/wrp.proto) file.
29 |
30 | ```mermaid
31 | sequenceDiagram
32 | participant H as WrpHost
33 | participant G as WrpGuest
34 | H->>G: HostInitialize
35 | Note over H, G: Send available methods
36 | loop
37 | G->>+H: GuestReqStart
38 | H-->>G: HostResStart
39 | par request
40 | loop
41 | G-->>H: GuestReqPayload
42 | end
43 | G-->>H: GuestReqFinish
44 | end
45 | par response
46 | loop
47 | H-->>G: HostResPayload
48 | end
49 | H-->>-G: HostResFinish
50 | end
51 | end
52 | ```
53 |
54 | ### Types
55 |
56 | ```typescript
57 | // Glue implementation provides socket object.
58 | type Socket = Reader & Writer;
59 | interface Reader {
60 | read(p: Uint8Array): Promise;
61 | }
62 | interface Writer {
63 | write(p: Uint8Array): Promise;
64 | }
65 |
66 | // Channel provides a per-message communication method.
67 | interface WrpChannel {
68 | listen(): AsyncGenerator;
69 | send(message: WrpMessage): Promise;
70 | }
71 |
72 | type Metadata = Record;
73 | interface LazyMetadata {
74 | [key: string]:
75 | | undefined
76 | | string
77 | | Promise
78 | | (() => string | undefined)
79 | | (() => Promise);
80 | }
81 |
82 | // Guest provides a way to send requests to the host.
83 | interface WrpGuest {
84 | availableMethods: Set;
85 | request(
86 | methodName: string,
87 | req: AsyncGenerator,
88 | metadata?: LazyMetadata,
89 | ): {
90 | res: AsyncGenerator;
91 | header: Promise;
92 | trailer: Promise;
93 | };
94 | }
95 |
96 | // Host provides a way to handle and respond to requests from guests.
97 | interface WrpHost {
98 | listen(): AsyncGenerator;
99 | }
100 | interface WrpRequest {
101 | methodName: string;
102 | metadata: Metadata;
103 | req: AsyncGenerator;
104 | sendHeader(value: Metadata): void;
105 | sendPayload(value: Uint8Array): void;
106 | sendTrailer(value: Metadata): void;
107 | }
108 | ```
109 |
--------------------------------------------------------------------------------
/deno.json:
--------------------------------------------------------------------------------
1 | {
2 | "tasks": {
3 | "build:npm": "deno run -A --no-check scripts/build-npm-packages/entrypoint.ts",
4 | "fmt": "deno fmt --ignore=src/generated,tmp"
5 | },
6 | "compilerOptions": {
7 | "lib": ["deno.ns", "dom"]
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/generate.sh:
--------------------------------------------------------------------------------
1 | rm -rf src/generated
2 | pb gen ts --runtime-package="https://deno.land/x/pbkit@v0.0.45/core/runtime" -o src/generated src/wrp.proto
3 |
--------------------------------------------------------------------------------
/import_map.json:
--------------------------------------------------------------------------------
1 | {
2 | "imports": {
3 | "react": "https://esm.sh/react@18",
4 | "jotai": "https://esm.sh/jotai@1.7.5",
5 | "jotai/utils": "https://esm.sh/jotai@1.7.5/utils"
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/scripts/build-npm-packages/buildWrp.ts:
--------------------------------------------------------------------------------
1 | import { ensureDir } from "https://deno.land/std@0.137.0/fs/mod.ts";
2 | import { walk } from "https://deno.land/std@0.137.0/fs/walk.ts";
3 | import {
4 | dirname,
5 | join,
6 | relative,
7 | } from "https://deno.land/std@0.137.0/path/mod.ts";
8 | import {
9 | BuildConfig,
10 | isPbkitPath,
11 | isStdPath,
12 | NameAndVersion,
13 | removeExt,
14 | replacePbkitRuntimePath,
15 | replaceStdPath,
16 | rewriteModulePath,
17 | } from "./index.ts";
18 |
19 | export default async function buildWrp(config: BuildConfig) {
20 | const packageJson = getPackageJson(config);
21 | const tsDir = `${config.tmp}/ts`;
22 | await ensureDir(config.dist);
23 | await Deno.writeTextFile(
24 | `${config.dist}/package.json`,
25 | JSON.stringify(packageJson, null, 2) + "\n",
26 | );
27 | await Promise.all([
28 | Deno.copyFile("LICENSE-MIT", `${config.dist}/LICENSE-MIT`),
29 | Deno.copyFile("LICENSE-APACHE", `${config.dist}/LICENSE-APACHE`),
30 | Deno.copyFile("README.md", `${config.dist}/README.md`),
31 | ]);
32 | { // copy ts files
33 | const srcDir = "src";
34 | const entries = walk(srcDir, { includeDirs: false, exts: [".ts"] });
35 | for await (const { path: fromPath } of entries) {
36 | const path = fromPath.substring(srcDir.length + 1);
37 | if (path.startsWith("react") || path.startsWith("jotai")) continue;
38 | const toPath = join(tsDir, path);
39 | await ensureDir(dirname(toPath));
40 | const code = await Deno.readTextFile(fromPath);
41 | await Deno.writeTextFile(
42 | toPath,
43 | rewriteModulePath(code, (modulePath) => {
44 | if (modulePath.startsWith(".")) {
45 | return removeExt(modulePath);
46 | } else if (isStdPath(modulePath)) {
47 | const relativePath = relative(
48 | dirname(path),
49 | replaceStdPath(modulePath),
50 | );
51 | if (!relativePath.startsWith(".")) return `./${relativePath}`;
52 | return relativePath;
53 | } else if (isPbkitPath(modulePath)) {
54 | return replacePbkitRuntimePath(modulePath);
55 | } else {
56 | return modulePath;
57 | }
58 | }),
59 | );
60 | }
61 | }
62 | { // tsc
63 | const entries = walk(tsDir, { includeDirs: false, exts: [".ts"] });
64 | const tsFiles: string[] = [];
65 | for await (const { path } of entries) tsFiles.push(path);
66 | await Deno.run({
67 | stdout: "null",
68 | stderr: "null",
69 | cmd: [
70 | "tsc",
71 | "-m",
72 | "commonjs",
73 | "--target",
74 | "es2019",
75 | "--lib",
76 | "es2019,dom",
77 | "--declaration",
78 | "--rootDir",
79 | tsDir,
80 | "--outDir",
81 | config.dist,
82 | ...tsFiles,
83 | ],
84 | }).status();
85 | }
86 | }
87 |
88 | export function getPackageJson(config: NameAndVersion) {
89 | const { name, version } = config;
90 | return {
91 | name,
92 | version,
93 | author: "JongChan Choi ",
94 | license: "(MIT OR Apache-2.0)",
95 | repository: {
96 | type: "git",
97 | url: "git+https://github.com/pbkit/wrp-ts.git",
98 | },
99 | dependencies: {
100 | "@pbkit/runtime": "^0.0.45",
101 | },
102 | };
103 | }
104 |
--------------------------------------------------------------------------------
/scripts/build-npm-packages/buildWrpJotai.ts:
--------------------------------------------------------------------------------
1 | import { ensureDir } from "https://deno.land/std@0.137.0/fs/mod.ts";
2 | import { walk } from "https://deno.land/std@0.137.0/fs/walk.ts";
3 | import {
4 | dirname,
5 | join,
6 | relative,
7 | } from "https://deno.land/std@0.137.0/path/mod.ts";
8 | import {
9 | BuildConfig,
10 | isPbkitPath,
11 | isStdPath,
12 | NameAndVersion,
13 | removeExt,
14 | replacePbkitRuntimePath,
15 | replaceStdPath,
16 | rewriteModulePath,
17 | } from "./index.ts";
18 |
19 | export default async function buildWrpJotai(config: BuildConfig) {
20 | const packageJson = getPackageJson(config);
21 | const tsDir = `${config.tmp}/ts`;
22 | await ensureDir(config.dist);
23 | await Deno.writeTextFile(
24 | `${config.dist}/package.json`,
25 | JSON.stringify(packageJson, null, 2) + "\n",
26 | );
27 | await Promise.all([
28 | Deno.copyFile("LICENSE-MIT", `${config.dist}/LICENSE-MIT`),
29 | Deno.copyFile("LICENSE-APACHE", `${config.dist}/LICENSE-APACHE`),
30 | Deno.copyFile("README.md", `${config.dist}/README.md`),
31 | ]);
32 | { // copy ts files
33 | const srcDir = "src/jotai";
34 | const entries = walk(srcDir, { includeDirs: false, exts: [".ts"] });
35 | for await (const { path: fromPath } of entries) {
36 | const path = fromPath.substring(srcDir.length + 1);
37 | const toPath = join(tsDir, path);
38 | await ensureDir(dirname(toPath));
39 | const code = await Deno.readTextFile(fromPath);
40 | await Deno.writeTextFile(
41 | toPath,
42 | rewriteModulePath(code, (modulePath) => {
43 | if (modulePath.startsWith("../react/")) {
44 | return removeExt(
45 | modulePath.replace(/^\.\.\/react\//, "@pbkit/wrp-react/"),
46 | );
47 | } else if (modulePath.startsWith("../")) {
48 | return removeExt(modulePath.replace(/^\.\.\//, "@pbkit/wrp/"));
49 | } else if (modulePath.startsWith(".")) {
50 | return removeExt(modulePath);
51 | } else if (isStdPath(modulePath)) {
52 | const relativePath = relative(
53 | dirname(path),
54 | replaceStdPath(modulePath),
55 | );
56 | if (!relativePath.startsWith(".")) return `./${relativePath}`;
57 | return relativePath;
58 | } else if (isPbkitPath(modulePath)) {
59 | return replacePbkitRuntimePath(modulePath);
60 | } else {
61 | return modulePath;
62 | }
63 | }),
64 | );
65 | }
66 | }
67 | { // tsc
68 | const entries = walk(tsDir, { includeDirs: false, exts: [".ts"] });
69 | const tsFiles: string[] = [];
70 | for await (const { path } of entries) tsFiles.push(path);
71 | await Deno.run({
72 | stdout: "null",
73 | stderr: "null",
74 | cmd: [
75 | "tsc",
76 | "-m",
77 | "commonjs",
78 | "--target",
79 | "es2019",
80 | "--lib",
81 | "es2019,dom",
82 | "--declaration",
83 | "--rootDir",
84 | tsDir,
85 | "--outDir",
86 | config.dist,
87 | ...tsFiles,
88 | ],
89 | }).status();
90 | }
91 | }
92 |
93 | export function getPackageJson(config: NameAndVersion) {
94 | const { name, version } = config;
95 | return {
96 | name,
97 | version,
98 | author: "JongChan Choi ",
99 | license: "(MIT OR Apache-2.0)",
100 | repository: {
101 | type: "git",
102 | url: "git+https://github.com/pbkit/wrp-ts.git",
103 | },
104 | peerDependencies: {
105 | "@pbkit/wrp": "*",
106 | "jotai": "*",
107 | },
108 | };
109 | }
110 |
--------------------------------------------------------------------------------
/scripts/build-npm-packages/buildWrpReact.ts:
--------------------------------------------------------------------------------
1 | import { ensureDir } from "https://deno.land/std@0.137.0/fs/mod.ts";
2 | import { walk } from "https://deno.land/std@0.137.0/fs/walk.ts";
3 | import {
4 | dirname,
5 | join,
6 | relative,
7 | } from "https://deno.land/std@0.137.0/path/mod.ts";
8 | import {
9 | BuildConfig,
10 | isPbkitPath,
11 | isStdPath,
12 | NameAndVersion,
13 | removeExt,
14 | replacePbkitRuntimePath,
15 | replaceStdPath,
16 | rewriteModulePath,
17 | } from "./index.ts";
18 |
19 | export default async function buildWrpReact(config: BuildConfig) {
20 | const packageJson = getPackageJson(config);
21 | const tsDir = `${config.tmp}/ts`;
22 | await ensureDir(config.dist);
23 | await Deno.writeTextFile(
24 | `${config.dist}/package.json`,
25 | JSON.stringify(packageJson, null, 2) + "\n",
26 | );
27 | await Promise.all([
28 | Deno.copyFile("LICENSE-MIT", `${config.dist}/LICENSE-MIT`),
29 | Deno.copyFile("LICENSE-APACHE", `${config.dist}/LICENSE-APACHE`),
30 | Deno.copyFile("README.md", `${config.dist}/README.md`),
31 | ]);
32 | { // copy ts files
33 | const srcDir = "src/react";
34 | const entries = walk(srcDir, { includeDirs: false, exts: [".ts"] });
35 | for await (const { path: fromPath } of entries) {
36 | const path = fromPath.substring(srcDir.length + 1);
37 | const toPath = join(tsDir, path);
38 | await ensureDir(dirname(toPath));
39 | const code = await Deno.readTextFile(fromPath);
40 | await Deno.writeTextFile(
41 | toPath,
42 | rewriteModulePath(code, (modulePath) => {
43 | if (modulePath.startsWith("../")) {
44 | return removeExt(modulePath.replace(/^\.\.\//, "@pbkit/wrp/"));
45 | } else if (modulePath.startsWith(".")) {
46 | return removeExt(modulePath);
47 | } else if (isStdPath(modulePath)) {
48 | const relativePath = relative(
49 | dirname(path),
50 | replaceStdPath(modulePath),
51 | );
52 | if (!relativePath.startsWith(".")) return `./${relativePath}`;
53 | return relativePath;
54 | } else if (isPbkitPath(modulePath)) {
55 | return replacePbkitRuntimePath(modulePath);
56 | } else {
57 | return modulePath;
58 | }
59 | }),
60 | );
61 | }
62 | }
63 | { // tsc
64 | const entries = walk(tsDir, { includeDirs: false, exts: [".ts"] });
65 | const tsFiles: string[] = [];
66 | for await (const { path } of entries) tsFiles.push(path);
67 | await Deno.run({
68 | stdout: "null",
69 | stderr: "null",
70 | cmd: [
71 | "tsc",
72 | "-m",
73 | "commonjs",
74 | "--target",
75 | "es2019",
76 | "--lib",
77 | "es2019,dom",
78 | "--declaration",
79 | "--rootDir",
80 | tsDir,
81 | "--outDir",
82 | config.dist,
83 | ...tsFiles,
84 | ],
85 | }).status();
86 | }
87 | }
88 |
89 | export function getPackageJson(config: NameAndVersion) {
90 | const { name, version } = config;
91 | return {
92 | name,
93 | version,
94 | author: "JongChan Choi ",
95 | license: "(MIT OR Apache-2.0)",
96 | repository: {
97 | type: "git",
98 | url: "git+https://github.com/pbkit/wrp-ts.git",
99 | },
100 | peerDependencies: {
101 | "@pbkit/wrp": "*",
102 | "react": "*",
103 | },
104 | };
105 | }
106 |
--------------------------------------------------------------------------------
/scripts/build-npm-packages/entrypoint.ts:
--------------------------------------------------------------------------------
1 | import { emptyDir } from "https://deno.land/std@0.122.0/fs/mod.ts";
2 | import buildWrp from "./buildWrp.ts";
3 | import buildWrpJotai from "./buildWrpJotai.ts";
4 | import buildWrpReact from "./buildWrpReact.ts";
5 |
6 | await emptyDir("tmp/npm");
7 |
8 | const latestTag = new TextDecoder().decode(
9 | await Deno.run({
10 | cmd: ["git", "describe", "--tags", "--abbrev=0"],
11 | stdout: "piped",
12 | }).output(),
13 | );
14 | const version = latestTag.slice(1).trim();
15 |
16 | await Promise.all([
17 | buildWrp({
18 | name: "@pbkit/wrp",
19 | version,
20 | dist: "tmp/npm/wrp",
21 | tmp: "tmp/npm/tmp/wrp",
22 | }),
23 | buildWrpReact({
24 | name: "@pbkit/wrp-react",
25 | version,
26 | dist: "tmp/npm/wrp-react",
27 | tmp: "tmp/npm/tmp/wrp-react",
28 | }),
29 | buildWrpJotai({
30 | name: "@pbkit/wrp-jotai",
31 | version,
32 | dist: "tmp/npm/wrp-jotai",
33 | tmp: "tmp/npm/tmp/wrp-jotai",
34 | }),
35 | ]);
36 |
--------------------------------------------------------------------------------
/scripts/build-npm-packages/index.ts:
--------------------------------------------------------------------------------
1 | export interface BuildConfig extends NameAndVersion {
2 | dist: string;
3 | tmp?: string;
4 | }
5 |
6 | export interface NameAndVersion {
7 | name: string;
8 | version: string;
9 | }
10 |
11 | export function rewriteModulePath(
12 | code: string,
13 | fn: Parameters[1],
14 | ): string {
15 | // 1) import "foo";
16 | // 2) import bar from "foo";
17 | // 3) import { bar } from "foo";
18 | // 4) import * as bar from "foo";
19 | // 5) export { bar } from "foo";
20 | // 6) export * from "foo";
21 | // 7) } from "foo";
22 | return code.replaceAll(
23 | /(?<=^\s*(?:import\s*|(?:(?:import|export)\b.*?\b|}\s*)from\s*)(["'])).+(?=\1)/gm,
24 | fn,
25 | );
26 | }
27 |
28 | export function isPbkitPath(path: string): boolean {
29 | return (
30 | path.startsWith("https://deno.land/x/pbkit") ||
31 | path.startsWith("https:/deno.land/x/pbkit")
32 | );
33 | }
34 |
35 | export function isStdPath(path: string): boolean {
36 | return path.startsWith("https://deno.land/std");
37 | }
38 |
39 | export function replacePbkitRuntimePath(path: string): string {
40 | return removeExt(
41 | path.replace(
42 | /^https:\/\/?deno\.land\/x\/pbkit[^\/]*?\/core\/runtime/,
43 | "@pbkit/runtime",
44 | ),
45 | );
46 | }
47 |
48 | export function replaceStdPath(path: string): string {
49 | return removeExt(
50 | path.replace(
51 | /^https:\/\/deno\.land\/std[^\/]*?\/(.+)/,
52 | "compat/std/$1",
53 | ),
54 | );
55 | }
56 |
57 | export function removeExt(path: string): string {
58 | return path.replace(/\.[^.]+$/, "");
59 | }
60 |
--------------------------------------------------------------------------------
/src/channel.ts:
--------------------------------------------------------------------------------
1 | import {
2 | BufReader,
3 | BufWriter,
4 | } from "https://deno.land/std@0.137.0/io/buffer.ts";
5 | import {
6 | decodeBinary,
7 | encodeBinary,
8 | Type as WrpMessage,
9 | } from "./generated/messages/pbkit/wrp/WrpMessage.ts";
10 | import { Socket } from "./socket.ts";
11 | import { chain } from "./misc.ts";
12 |
13 | export interface WrpChannel {
14 | listen(): AsyncGenerator;
15 | send(message: WrpMessage): Promise;
16 | }
17 | export function createWrpChannel(socket: Socket): WrpChannel {
18 | return {
19 | async *listen() {
20 | const bufReader = new BufReader(socket);
21 | while (true) {
22 | let lengthU8s: Uint8Array | null = null;
23 | try {
24 | lengthU8s = await bufReader.readFull(new Uint8Array(4));
25 | } catch { /* error when socket closed */ }
26 | if (!lengthU8s) break;
27 | const length = new DataView(lengthU8s.buffer).getUint32(0, true);
28 | const payload = await bufReader.readFull(new Uint8Array(length));
29 | if (!payload) throw new UnexpectedEof();
30 | yield decodeBinary(payload);
31 | }
32 | },
33 | send: chain(async function send(message) {
34 | const payload = encodeBinary(message);
35 | const lengthU8s = new Uint8Array(4);
36 | new DataView(lengthU8s.buffer).setUint32(0, payload.length, true);
37 | const bufWriter = new BufWriter(socket);
38 | await bufWriter.write(lengthU8s);
39 | await bufWriter.write(payload);
40 | await bufWriter.flush();
41 | }),
42 | };
43 | }
44 |
45 | export const UnexpectedEof = (
46 | typeof Deno === "undefined"
47 | ? class UnexpectedEof extends Error {}
48 | : Deno.errors.UnexpectedEof
49 | );
50 |
--------------------------------------------------------------------------------
/src/compat/std/io/buffer.ts:
--------------------------------------------------------------------------------
1 | // Copyright 2018-2022 the Deno authors. All rights reserved. MIT license.
2 |
3 | function copy(src: Uint8Array, dst: Uint8Array, off = 0): number {
4 | off = Math.max(0, Math.min(off, dst.byteLength));
5 | const dstBytesAvailable = dst.byteLength - off;
6 | if (src.byteLength > dstBytesAvailable) {
7 | src = src.subarray(0, dstBytesAvailable);
8 | }
9 | dst.set(src, off);
10 | return src.byteLength;
11 | }
12 | interface Reader {
13 | read(p: Uint8Array): Promise;
14 | }
15 | interface Writer {
16 | write(p: Uint8Array): Promise;
17 | }
18 |
19 | const DEFAULT_BUF_SIZE = 4096;
20 | const MIN_BUF_SIZE = 16;
21 | const MAX_CONSECUTIVE_EMPTY_READS = 100;
22 | const CR = "\r".charCodeAt(0);
23 | const LF = "\n".charCodeAt(0);
24 |
25 | export class BufferFullError extends Error {
26 | override name = "BufferFullError";
27 | constructor(public partial: Uint8Array) {
28 | super("Buffer full");
29 | }
30 | }
31 |
32 | export class PartialReadError extends Error {
33 | override name = "PartialReadError";
34 | partial?: Uint8Array;
35 | constructor() {
36 | super("Encountered UnexpectedEof, data only partially read");
37 | }
38 | }
39 |
40 | /** Result type returned by of BufReader.readLine(). */
41 | export interface ReadLineResult {
42 | line: Uint8Array;
43 | more: boolean;
44 | }
45 |
46 | /** BufReader implements buffering for a Reader object. */
47 | export class BufReader implements Reader {
48 | #buf!: Uint8Array;
49 | #rd!: Reader; // Reader provided by caller.
50 | #r = 0; // buf read position.
51 | #w = 0; // buf write position.
52 | #eof = false;
53 | // private lastByte: number;
54 | // private lastCharSize: number;
55 |
56 | /** return new BufReader unless r is BufReader */
57 | static create(r: Reader, size: number = DEFAULT_BUF_SIZE): BufReader {
58 | return r instanceof BufReader ? r : new BufReader(r, size);
59 | }
60 |
61 | constructor(rd: Reader, size: number = DEFAULT_BUF_SIZE) {
62 | if (size < MIN_BUF_SIZE) {
63 | size = MIN_BUF_SIZE;
64 | }
65 | this.#reset(new Uint8Array(size), rd);
66 | }
67 |
68 | /** Returns the size of the underlying buffer in bytes. */
69 | size(): number {
70 | return this.#buf.byteLength;
71 | }
72 |
73 | buffered(): number {
74 | return this.#w - this.#r;
75 | }
76 |
77 | // Reads a new chunk into the buffer.
78 | #fill = async () => {
79 | // Slide existing data to beginning.
80 | if (this.#r > 0) {
81 | this.#buf.copyWithin(0, this.#r, this.#w);
82 | this.#w -= this.#r;
83 | this.#r = 0;
84 | }
85 |
86 | if (this.#w >= this.#buf.byteLength) {
87 | throw Error("bufio: tried to fill full buffer");
88 | }
89 |
90 | // Read new data: try a limited number of times.
91 | for (let i = MAX_CONSECUTIVE_EMPTY_READS; i > 0; i--) {
92 | const rr = await this.#rd.read(this.#buf.subarray(this.#w));
93 | if (rr === null) {
94 | this.#eof = true;
95 | return;
96 | }
97 | this.#w += rr;
98 | if (rr > 0) {
99 | return;
100 | }
101 | }
102 |
103 | throw new Error(
104 | `No progress after ${MAX_CONSECUTIVE_EMPTY_READS} read() calls`,
105 | );
106 | };
107 |
108 | /** Discards any buffered data, resets all state, and switches
109 | * the buffered reader to read from r.
110 | */
111 | reset(r: Reader): void {
112 | this.#reset(this.#buf, r);
113 | }
114 |
115 | #reset = (buf: Uint8Array, rd: Reader): void => {
116 | this.#buf = buf;
117 | this.#rd = rd;
118 | this.#eof = false;
119 | // this.lastByte = -1;
120 | // this.lastCharSize = -1;
121 | };
122 |
123 | /** reads data into p.
124 | * It returns the number of bytes read into p.
125 | * The bytes are taken from at most one Read on the underlying Reader,
126 | * hence n may be less than len(p).
127 | * To read exactly len(p) bytes, use io.ReadFull(b, p).
128 | */
129 | async read(p: Uint8Array): Promise {
130 | let rr: number | null = p.byteLength;
131 | if (p.byteLength === 0) return rr;
132 |
133 | if (this.#r === this.#w) {
134 | if (p.byteLength >= this.#buf.byteLength) {
135 | // Large read, empty buffer.
136 | // Read directly into p to avoid copy.
137 | const rr = await this.#rd.read(p);
138 | const nread = rr ?? 0;
139 | // if (rr.nread > 0) {
140 | // this.lastByte = p[rr.nread - 1];
141 | // this.lastCharSize = -1;
142 | // }
143 | return rr;
144 | }
145 |
146 | // One read.
147 | // Do not use this.fill, which will loop.
148 | this.#r = 0;
149 | this.#w = 0;
150 | rr = await this.#rd.read(this.#buf);
151 | if (rr === 0 || rr === null) return rr;
152 | this.#w += rr;
153 | }
154 |
155 | // copy as much as we can
156 | const copied = copy(this.#buf.subarray(this.#r, this.#w), p, 0);
157 | this.#r += copied;
158 | // this.lastByte = this.buf[this.r - 1];
159 | // this.lastCharSize = -1;
160 | return copied;
161 | }
162 |
163 | /** reads exactly `p.length` bytes into `p`.
164 | *
165 | * If successful, `p` is returned.
166 | *
167 | * If the end of the underlying stream has been reached, and there are no more
168 | * bytes available in the buffer, `readFull()` returns `null` instead.
169 | *
170 | * An error is thrown if some bytes could be read, but not enough to fill `p`
171 | * entirely before the underlying stream reported an error or EOF. Any error
172 | * thrown will have a `partial` property that indicates the slice of the
173 | * buffer that has been successfully filled with data.
174 | *
175 | * Ported from https://golang.org/pkg/io/#ReadFull
176 | */
177 | async readFull(p: Uint8Array): Promise {
178 | let bytesRead = 0;
179 | while (bytesRead < p.length) {
180 | try {
181 | const rr = await this.read(p.subarray(bytesRead));
182 | if (rr === null) {
183 | if (bytesRead === 0) {
184 | return null;
185 | } else {
186 | throw new PartialReadError();
187 | }
188 | }
189 | bytesRead += rr;
190 | } catch (err) {
191 | if (err instanceof PartialReadError) {
192 | err.partial = p.subarray(0, bytesRead);
193 | } else if (err instanceof Error) {
194 | const e = new PartialReadError();
195 | e.partial = p.subarray(0, bytesRead);
196 | e.stack = err.stack;
197 | e.message = err.message;
198 | e.cause = err.cause;
199 | throw err;
200 | }
201 | throw err;
202 | }
203 | }
204 | return p;
205 | }
206 |
207 | /** Returns the next byte [0, 255] or `null`. */
208 | async readByte(): Promise {
209 | while (this.#r === this.#w) {
210 | if (this.#eof) return null;
211 | await this.#fill(); // buffer is empty.
212 | }
213 | const c = this.#buf[this.#r];
214 | this.#r++;
215 | // this.lastByte = c;
216 | return c;
217 | }
218 |
219 | /** readString() reads until the first occurrence of delim in the input,
220 | * returning a string containing the data up to and including the delimiter.
221 | * If ReadString encounters an error before finding a delimiter,
222 | * it returns the data read before the error and the error itself
223 | * (often `null`).
224 | * ReadString returns err != nil if and only if the returned data does not end
225 | * in delim.
226 | * For simple uses, a Scanner may be more convenient.
227 | */
228 | async readString(delim: string): Promise {
229 | if (delim.length !== 1) {
230 | throw new Error("Delimiter should be a single character");
231 | }
232 | const buffer = await this.readSlice(delim.charCodeAt(0));
233 | if (buffer === null) return null;
234 | return new TextDecoder().decode(buffer);
235 | }
236 |
237 | /** `readLine()` is a low-level line-reading primitive. Most callers should
238 | * use `readString('\n')` instead or use a Scanner.
239 | *
240 | * `readLine()` tries to return a single line, not including the end-of-line
241 | * bytes. If the line was too long for the buffer then `more` is set and the
242 | * beginning of the line is returned. The rest of the line will be returned
243 | * from future calls. `more` will be false when returning the last fragment
244 | * of the line. The returned buffer is only valid until the next call to
245 | * `readLine()`.
246 | *
247 | * The text returned from ReadLine does not include the line end ("\r\n" or
248 | * "\n").
249 | *
250 | * When the end of the underlying stream is reached, the final bytes in the
251 | * stream are returned. No indication or error is given if the input ends
252 | * without a final line end. When there are no more trailing bytes to read,
253 | * `readLine()` returns `null`.
254 | *
255 | * Calling `unreadByte()` after `readLine()` will always unread the last byte
256 | * read (possibly a character belonging to the line end) even if that byte is
257 | * not part of the line returned by `readLine()`.
258 | */
259 | async readLine(): Promise {
260 | let line: Uint8Array | null = null;
261 |
262 | try {
263 | line = await this.readSlice(LF);
264 | } catch (err) {
265 | if (err instanceof Deno.errors.BadResource) {
266 | throw err;
267 | }
268 | let partial;
269 | if (err instanceof PartialReadError) {
270 | partial = err.partial;
271 | }
272 |
273 | // Don't throw if `readSlice()` failed with `BufferFullError`, instead we
274 | // just return whatever is available and set the `more` flag.
275 | if (!(err instanceof BufferFullError)) {
276 | throw err;
277 | }
278 |
279 | partial = err.partial;
280 |
281 | // Handle the case where "\r\n" straddles the buffer.
282 | if (
283 | !this.#eof && partial &&
284 | partial.byteLength > 0 &&
285 | partial[partial.byteLength - 1] === CR
286 | ) {
287 | // Put the '\r' back on buf and drop it from line.
288 | // Let the next call to ReadLine check for "\r\n".
289 | this.#r--;
290 | partial = partial.subarray(0, partial.byteLength - 1);
291 | }
292 |
293 | if (partial) {
294 | return { line: partial, more: !this.#eof };
295 | }
296 | }
297 |
298 | if (line === null) {
299 | return null;
300 | }
301 |
302 | if (line.byteLength === 0) {
303 | return { line, more: false };
304 | }
305 |
306 | if (line[line.byteLength - 1] == LF) {
307 | let drop = 1;
308 | if (line.byteLength > 1 && line[line.byteLength - 2] === CR) {
309 | drop = 2;
310 | }
311 | line = line.subarray(0, line.byteLength - drop);
312 | }
313 | return { line, more: false };
314 | }
315 |
316 | /** `readSlice()` reads until the first occurrence of `delim` in the input,
317 | * returning a slice pointing at the bytes in the buffer. The bytes stop
318 | * being valid at the next read.
319 | *
320 | * If `readSlice()` encounters an error before finding a delimiter, or the
321 | * buffer fills without finding a delimiter, it throws an error with a
322 | * `partial` property that contains the entire buffer.
323 | *
324 | * If `readSlice()` encounters the end of the underlying stream and there are
325 | * any bytes left in the buffer, the rest of the buffer is returned. In other
326 | * words, EOF is always treated as a delimiter. Once the buffer is empty,
327 | * it returns `null`.
328 | *
329 | * Because the data returned from `readSlice()` will be overwritten by the
330 | * next I/O operation, most clients should use `readString()` instead.
331 | */
332 | async readSlice(delim: number): Promise {
333 | let s = 0; // search start index
334 | let slice: Uint8Array | undefined;
335 |
336 | while (true) {
337 | // Search buffer.
338 | let i = this.#buf.subarray(this.#r + s, this.#w).indexOf(delim);
339 | if (i >= 0) {
340 | i += s;
341 | slice = this.#buf.subarray(this.#r, this.#r + i + 1);
342 | this.#r += i + 1;
343 | break;
344 | }
345 |
346 | // EOF?
347 | if (this.#eof) {
348 | if (this.#r === this.#w) {
349 | return null;
350 | }
351 | slice = this.#buf.subarray(this.#r, this.#w);
352 | this.#r = this.#w;
353 | break;
354 | }
355 |
356 | // Buffer full?
357 | if (this.buffered() >= this.#buf.byteLength) {
358 | this.#r = this.#w;
359 | // #4521 The internal buffer should not be reused across reads because it causes corruption of data.
360 | const oldbuf = this.#buf;
361 | const newbuf = this.#buf.slice(0);
362 | this.#buf = newbuf;
363 | throw new BufferFullError(oldbuf);
364 | }
365 |
366 | s = this.#w - this.#r; // do not rescan area we scanned before
367 |
368 | // Buffer is not full.
369 | try {
370 | await this.#fill();
371 | } catch (err) {
372 | if (err instanceof PartialReadError) {
373 | err.partial = slice;
374 | } else if (err instanceof Error) {
375 | const e = new PartialReadError();
376 | e.partial = slice;
377 | e.stack = err.stack;
378 | e.message = err.message;
379 | e.cause = err.cause;
380 | throw err;
381 | }
382 | throw err;
383 | }
384 | }
385 |
386 | // Handle last byte, if any.
387 | // const i = slice.byteLength - 1;
388 | // if (i >= 0) {
389 | // this.lastByte = slice[i];
390 | // this.lastCharSize = -1
391 | // }
392 |
393 | return slice;
394 | }
395 |
396 | /** `peek()` returns the next `n` bytes without advancing the reader. The
397 | * bytes stop being valid at the next read call.
398 | *
399 | * When the end of the underlying stream is reached, but there are unread
400 | * bytes left in the buffer, those bytes are returned. If there are no bytes
401 | * left in the buffer, it returns `null`.
402 | *
403 | * If an error is encountered before `n` bytes are available, `peek()` throws
404 | * an error with the `partial` property set to a slice of the buffer that
405 | * contains the bytes that were available before the error occurred.
406 | */
407 | async peek(n: number): Promise {
408 | if (n < 0) {
409 | throw Error("negative count");
410 | }
411 |
412 | let avail = this.#w - this.#r;
413 | while (avail < n && avail < this.#buf.byteLength && !this.#eof) {
414 | try {
415 | await this.#fill();
416 | } catch (err) {
417 | if (err instanceof PartialReadError) {
418 | err.partial = this.#buf.subarray(this.#r, this.#w);
419 | } else if (err instanceof Error) {
420 | const e = new PartialReadError();
421 | e.partial = this.#buf.subarray(this.#r, this.#w);
422 | e.stack = err.stack;
423 | e.message = err.message;
424 | e.cause = err.cause;
425 | throw err;
426 | }
427 | throw err;
428 | }
429 | avail = this.#w - this.#r;
430 | }
431 |
432 | if (avail === 0 && this.#eof) {
433 | return null;
434 | } else if (avail < n && this.#eof) {
435 | return this.#buf.subarray(this.#r, this.#r + avail);
436 | } else if (avail < n) {
437 | throw new BufferFullError(this.#buf.subarray(this.#r, this.#w));
438 | }
439 |
440 | return this.#buf.subarray(this.#r, this.#r + n);
441 | }
442 | }
443 |
444 | abstract class AbstractBufBase {
445 | buf: Uint8Array;
446 | usedBufferBytes = 0;
447 | err: Error | null = null;
448 |
449 | constructor(buf: Uint8Array) {
450 | this.buf = buf;
451 | }
452 |
453 | /** Size returns the size of the underlying buffer in bytes. */
454 | size(): number {
455 | return this.buf.byteLength;
456 | }
457 |
458 | /** Returns how many bytes are unused in the buffer. */
459 | available(): number {
460 | return this.buf.byteLength - this.usedBufferBytes;
461 | }
462 |
463 | /** buffered returns the number of bytes that have been written into the
464 | * current buffer.
465 | */
466 | buffered(): number {
467 | return this.usedBufferBytes;
468 | }
469 | }
470 |
471 | /** BufWriter implements buffering for an deno.Writer object.
472 | * If an error occurs writing to a Writer, no more data will be
473 | * accepted and all subsequent writes, and flush(), will return the error.
474 | * After all data has been written, the client should call the
475 | * flush() method to guarantee all data has been forwarded to
476 | * the underlying deno.Writer.
477 | */
478 | export class BufWriter extends AbstractBufBase implements Writer {
479 | #writer: Writer;
480 |
481 | /** return new BufWriter unless writer is BufWriter */
482 | static create(writer: Writer, size: number = DEFAULT_BUF_SIZE): BufWriter {
483 | return writer instanceof BufWriter ? writer : new BufWriter(writer, size);
484 | }
485 |
486 | constructor(writer: Writer, size: number = DEFAULT_BUF_SIZE) {
487 | super(new Uint8Array(size <= 0 ? DEFAULT_BUF_SIZE : size));
488 | this.#writer = writer;
489 | }
490 |
491 | /** Discards any unflushed buffered data, clears any error, and
492 | * resets buffer to write its output to w.
493 | */
494 | reset(w: Writer): void {
495 | this.err = null;
496 | this.usedBufferBytes = 0;
497 | this.#writer = w;
498 | }
499 |
500 | /** Flush writes any buffered data to the underlying io.Writer. */
501 | async flush() {
502 | if (this.err !== null) throw this.err;
503 | if (this.usedBufferBytes === 0) return;
504 |
505 | try {
506 | const p = this.buf.subarray(0, this.usedBufferBytes);
507 | let nwritten = 0;
508 | while (nwritten < p.length) {
509 | nwritten += await this.#writer.write(p.subarray(nwritten));
510 | }
511 | } catch (e) {
512 | if (e instanceof Error) {
513 | this.err = e;
514 | }
515 | throw e;
516 | }
517 |
518 | this.buf = new Uint8Array(this.buf.length);
519 | this.usedBufferBytes = 0;
520 | }
521 |
522 | /** Writes the contents of `data` into the buffer. If the contents won't fully
523 | * fit into the buffer, those bytes that can are copied into the buffer, the
524 | * buffer is the flushed to the writer and the remaining bytes are copied into
525 | * the now empty buffer.
526 | *
527 | * @return the number of bytes written to the buffer.
528 | */
529 | async write(data: Uint8Array): Promise {
530 | if (this.err !== null) throw this.err;
531 | if (data.length === 0) return 0;
532 |
533 | let totalBytesWritten = 0;
534 | let numBytesWritten = 0;
535 | while (data.byteLength > this.available()) {
536 | if (this.buffered() === 0) {
537 | // Large write, empty buffer.
538 | // Write directly from data to avoid copy.
539 | try {
540 | numBytesWritten = await this.#writer.write(data);
541 | } catch (e) {
542 | if (e instanceof Error) {
543 | this.err = e;
544 | }
545 | throw e;
546 | }
547 | } else {
548 | numBytesWritten = copy(data, this.buf, this.usedBufferBytes);
549 | this.usedBufferBytes += numBytesWritten;
550 | await this.flush();
551 | }
552 | totalBytesWritten += numBytesWritten;
553 | data = data.subarray(numBytesWritten);
554 | }
555 |
556 | numBytesWritten = copy(data, this.buf, this.usedBufferBytes);
557 | this.usedBufferBytes += numBytesWritten;
558 | totalBytesWritten += numBytesWritten;
559 | return totalBytesWritten;
560 | }
561 | }
562 |
--------------------------------------------------------------------------------
/src/generated/index.ts:
--------------------------------------------------------------------------------
1 | import * as messages from "./messages/index.ts";
2 | export type {
3 | messages,
4 | };
5 |
--------------------------------------------------------------------------------
/src/generated/messages/index.ts:
--------------------------------------------------------------------------------
1 | import * as pbkit from "./pbkit/index.ts";
2 | export type {
3 | pbkit,
4 | };
5 |
--------------------------------------------------------------------------------
/src/generated/messages/pbkit/index.ts:
--------------------------------------------------------------------------------
1 | import * as wrp from "./wrp/index.ts";
2 | export type {
3 | wrp,
4 | };
5 |
--------------------------------------------------------------------------------
/src/generated/messages/pbkit/wrp/WrpGuestMessage_ReqFinish.ts:
--------------------------------------------------------------------------------
1 | import {
2 | tsValueToJsonValueFns,
3 | jsonValueToTsValueFns,
4 | } from "https://deno.land/x/pbkit@v0.0.45/core/runtime/json/scalar.ts";
5 | import {
6 | WireMessage,
7 | } from "https://deno.land/x/pbkit@v0.0.45/core/runtime/wire/index.ts";
8 | import {
9 | default as serialize,
10 | } from "https://deno.land/x/pbkit@v0.0.45/core/runtime/wire/serialize.ts";
11 | import {
12 | tsValueToWireValueFns,
13 | wireValueToTsValueFns,
14 | } from "https://deno.land/x/pbkit@v0.0.45/core/runtime/wire/scalar.ts";
15 | import {
16 | default as deserialize,
17 | } from "https://deno.land/x/pbkit@v0.0.45/core/runtime/wire/deserialize.ts";
18 |
19 | export declare namespace $.pbkit.wrp {
20 | export interface WrpGuestMessage_ReqFinish {
21 | reqId: string;
22 | }
23 | }
24 | export type Type = $.pbkit.wrp.WrpGuestMessage_ReqFinish;
25 |
26 | export function getDefaultValue(): $.pbkit.wrp.WrpGuestMessage_ReqFinish {
27 | return {
28 | reqId: "",
29 | };
30 | }
31 |
32 | export function createValue(partialValue: Partial<$.pbkit.wrp.WrpGuestMessage_ReqFinish>): $.pbkit.wrp.WrpGuestMessage_ReqFinish {
33 | return {
34 | ...getDefaultValue(),
35 | ...partialValue,
36 | };
37 | }
38 |
39 | export function encodeJson(value: $.pbkit.wrp.WrpGuestMessage_ReqFinish): unknown {
40 | const result: any = {};
41 | if (value.reqId !== undefined) result.reqId = tsValueToJsonValueFns.string(value.reqId);
42 | return result;
43 | }
44 |
45 | export function decodeJson(value: any): $.pbkit.wrp.WrpGuestMessage_ReqFinish {
46 | const result = getDefaultValue();
47 | if (value.reqId !== undefined) result.reqId = jsonValueToTsValueFns.string(value.reqId);
48 | return result;
49 | }
50 |
51 | export function encodeBinary(value: $.pbkit.wrp.WrpGuestMessage_ReqFinish): Uint8Array {
52 | const result: WireMessage = [];
53 | if (value.reqId !== undefined) {
54 | const tsValue = value.reqId;
55 | result.push(
56 | [1, tsValueToWireValueFns.string(tsValue)],
57 | );
58 | }
59 | return serialize(result);
60 | }
61 |
62 | export function decodeBinary(binary: Uint8Array): $.pbkit.wrp.WrpGuestMessage_ReqFinish {
63 | const result = getDefaultValue();
64 | const wireMessage = deserialize(binary);
65 | const wireFields = new Map(wireMessage);
66 | field: {
67 | const wireValue = wireFields.get(1);
68 | if (wireValue === undefined) break field;
69 | const value = wireValueToTsValueFns.string(wireValue);
70 | if (value === undefined) break field;
71 | result.reqId = value;
72 | }
73 | return result;
74 | }
75 |
--------------------------------------------------------------------------------
/src/generated/messages/pbkit/wrp/WrpGuestMessage_ReqPayload.ts:
--------------------------------------------------------------------------------
1 | import {
2 | tsValueToJsonValueFns,
3 | jsonValueToTsValueFns,
4 | } from "https://deno.land/x/pbkit@v0.0.45/core/runtime/json/scalar.ts";
5 | import {
6 | WireMessage,
7 | } from "https://deno.land/x/pbkit@v0.0.45/core/runtime/wire/index.ts";
8 | import {
9 | default as serialize,
10 | } from "https://deno.land/x/pbkit@v0.0.45/core/runtime/wire/serialize.ts";
11 | import {
12 | tsValueToWireValueFns,
13 | wireValueToTsValueFns,
14 | } from "https://deno.land/x/pbkit@v0.0.45/core/runtime/wire/scalar.ts";
15 | import {
16 | default as deserialize,
17 | } from "https://deno.land/x/pbkit@v0.0.45/core/runtime/wire/deserialize.ts";
18 |
19 | export declare namespace $.pbkit.wrp {
20 | export interface WrpGuestMessage_ReqPayload {
21 | reqId: string;
22 | payload: Uint8Array;
23 | }
24 | }
25 | export type Type = $.pbkit.wrp.WrpGuestMessage_ReqPayload;
26 |
27 | export function getDefaultValue(): $.pbkit.wrp.WrpGuestMessage_ReqPayload {
28 | return {
29 | reqId: "",
30 | payload: new Uint8Array(),
31 | };
32 | }
33 |
34 | export function createValue(partialValue: Partial<$.pbkit.wrp.WrpGuestMessage_ReqPayload>): $.pbkit.wrp.WrpGuestMessage_ReqPayload {
35 | return {
36 | ...getDefaultValue(),
37 | ...partialValue,
38 | };
39 | }
40 |
41 | export function encodeJson(value: $.pbkit.wrp.WrpGuestMessage_ReqPayload): unknown {
42 | const result: any = {};
43 | if (value.reqId !== undefined) result.reqId = tsValueToJsonValueFns.string(value.reqId);
44 | if (value.payload !== undefined) result.payload = tsValueToJsonValueFns.bytes(value.payload);
45 | return result;
46 | }
47 |
48 | export function decodeJson(value: any): $.pbkit.wrp.WrpGuestMessage_ReqPayload {
49 | const result = getDefaultValue();
50 | if (value.reqId !== undefined) result.reqId = jsonValueToTsValueFns.string(value.reqId);
51 | if (value.payload !== undefined) result.payload = jsonValueToTsValueFns.bytes(value.payload);
52 | return result;
53 | }
54 |
55 | export function encodeBinary(value: $.pbkit.wrp.WrpGuestMessage_ReqPayload): Uint8Array {
56 | const result: WireMessage = [];
57 | if (value.reqId !== undefined) {
58 | const tsValue = value.reqId;
59 | result.push(
60 | [1, tsValueToWireValueFns.string(tsValue)],
61 | );
62 | }
63 | if (value.payload !== undefined) {
64 | const tsValue = value.payload;
65 | result.push(
66 | [2, tsValueToWireValueFns.bytes(tsValue)],
67 | );
68 | }
69 | return serialize(result);
70 | }
71 |
72 | export function decodeBinary(binary: Uint8Array): $.pbkit.wrp.WrpGuestMessage_ReqPayload {
73 | const result = getDefaultValue();
74 | const wireMessage = deserialize(binary);
75 | const wireFields = new Map(wireMessage);
76 | field: {
77 | const wireValue = wireFields.get(1);
78 | if (wireValue === undefined) break field;
79 | const value = wireValueToTsValueFns.string(wireValue);
80 | if (value === undefined) break field;
81 | result.reqId = value;
82 | }
83 | field: {
84 | const wireValue = wireFields.get(2);
85 | if (wireValue === undefined) break field;
86 | const value = wireValueToTsValueFns.bytes(wireValue);
87 | if (value === undefined) break field;
88 | result.payload = value;
89 | }
90 | return result;
91 | }
92 |
--------------------------------------------------------------------------------
/src/generated/messages/pbkit/wrp/WrpGuestMessage_ReqStart.ts:
--------------------------------------------------------------------------------
1 | import {
2 | tsValueToJsonValueFns,
3 | jsonValueToTsValueFns,
4 | } from "https://deno.land/x/pbkit@v0.0.45/core/runtime/json/scalar.ts";
5 | import {
6 | WireMessage,
7 | WireType,
8 | } from "https://deno.land/x/pbkit@v0.0.45/core/runtime/wire/index.ts";
9 | import {
10 | default as serialize,
11 | } from "https://deno.land/x/pbkit@v0.0.45/core/runtime/wire/serialize.ts";
12 | import {
13 | tsValueToWireValueFns,
14 | wireValueToTsValueFns,
15 | } from "https://deno.land/x/pbkit@v0.0.45/core/runtime/wire/scalar.ts";
16 | import {
17 | default as deserialize,
18 | } from "https://deno.land/x/pbkit@v0.0.45/core/runtime/wire/deserialize.ts";
19 |
20 | export declare namespace $.pbkit.wrp {
21 | export interface WrpGuestMessage_ReqStart {
22 | reqId: string;
23 | methodName: string;
24 | metadata: Map;
25 | }
26 | }
27 | export type Type = $.pbkit.wrp.WrpGuestMessage_ReqStart;
28 |
29 | export function getDefaultValue(): $.pbkit.wrp.WrpGuestMessage_ReqStart {
30 | return {
31 | reqId: "",
32 | methodName: "",
33 | metadata: new Map(),
34 | };
35 | }
36 |
37 | export function createValue(partialValue: Partial<$.pbkit.wrp.WrpGuestMessage_ReqStart>): $.pbkit.wrp.WrpGuestMessage_ReqStart {
38 | return {
39 | ...getDefaultValue(),
40 | ...partialValue,
41 | };
42 | }
43 |
44 | export function encodeJson(value: $.pbkit.wrp.WrpGuestMessage_ReqStart): unknown {
45 | const result: any = {};
46 | if (value.reqId !== undefined) result.reqId = tsValueToJsonValueFns.string(value.reqId);
47 | if (value.methodName !== undefined) result.methodName = tsValueToJsonValueFns.string(value.methodName);
48 | if (value.metadata !== undefined) result.metadata = Object.fromEntries([...value.metadata.entries()].map(([key, value]) => [key, tsValueToJsonValueFns.string(value)]));
49 | return result;
50 | }
51 |
52 | export function decodeJson(value: any): $.pbkit.wrp.WrpGuestMessage_ReqStart {
53 | const result = getDefaultValue();
54 | if (value.reqId !== undefined) result.reqId = jsonValueToTsValueFns.string(value.reqId);
55 | if (value.methodName !== undefined) result.methodName = jsonValueToTsValueFns.string(value.methodName);
56 | if (value.metadata !== undefined) result.metadata = Object.fromEntries([...value.metadata.entries()].map(([key, value]) => [key, jsonValueToTsValueFns.string(value)]));
57 | return result;
58 | }
59 |
60 | export function encodeBinary(value: $.pbkit.wrp.WrpGuestMessage_ReqStart): Uint8Array {
61 | const result: WireMessage = [];
62 | if (value.reqId !== undefined) {
63 | const tsValue = value.reqId;
64 | result.push(
65 | [1, tsValueToWireValueFns.string(tsValue)],
66 | );
67 | }
68 | if (value.methodName !== undefined) {
69 | const tsValue = value.methodName;
70 | result.push(
71 | [2, tsValueToWireValueFns.string(tsValue)],
72 | );
73 | }
74 | {
75 | const fields = value.metadata.entries();
76 | for (const [key, value] of fields) {
77 | result.push(
78 | [3, { type: WireType.LengthDelimited as const, value: serialize([[1, tsValueToWireValueFns.string(key)], [2, tsValueToWireValueFns.string(value)]]) }],
79 | );
80 | }
81 | }
82 | return serialize(result);
83 | }
84 |
85 | export function decodeBinary(binary: Uint8Array): $.pbkit.wrp.WrpGuestMessage_ReqStart {
86 | const result = getDefaultValue();
87 | const wireMessage = deserialize(binary);
88 | const wireFields = new Map(wireMessage);
89 | field: {
90 | const wireValue = wireFields.get(1);
91 | if (wireValue === undefined) break field;
92 | const value = wireValueToTsValueFns.string(wireValue);
93 | if (value === undefined) break field;
94 | result.reqId = value;
95 | }
96 | field: {
97 | const wireValue = wireFields.get(2);
98 | if (wireValue === undefined) break field;
99 | const value = wireValueToTsValueFns.string(wireValue);
100 | if (value === undefined) break field;
101 | result.methodName = value;
102 | }
103 | collection: {
104 | const wireValues = wireMessage.filter(([fieldNumber]) => fieldNumber === 3).map(([, wireValue]) => wireValue);
105 | const value = wireValues.map((wireValue) => (() => { if (wireValue.type !== WireType.LengthDelimited) { return; } const { 1: key, 2: value } = Object.fromEntries(deserialize(wireValue.value)); if (key === undefined || value === undefined) return; return [wireValueToTsValueFns.string(key), wireValueToTsValueFns.string(value)] as const;})()).filter(x => x !== undefined);
106 | if (!value.length) break collection;
107 | result.metadata = new Map(value as any);
108 | }
109 | return result;
110 | }
111 |
--------------------------------------------------------------------------------
/src/generated/messages/pbkit/wrp/WrpGuestMessage_ResFinish.ts:
--------------------------------------------------------------------------------
1 | import {
2 | tsValueToJsonValueFns,
3 | jsonValueToTsValueFns,
4 | } from "https://deno.land/x/pbkit@v0.0.45/core/runtime/json/scalar.ts";
5 | import {
6 | WireMessage,
7 | } from "https://deno.land/x/pbkit@v0.0.45/core/runtime/wire/index.ts";
8 | import {
9 | default as serialize,
10 | } from "https://deno.land/x/pbkit@v0.0.45/core/runtime/wire/serialize.ts";
11 | import {
12 | tsValueToWireValueFns,
13 | wireValueToTsValueFns,
14 | } from "https://deno.land/x/pbkit@v0.0.45/core/runtime/wire/scalar.ts";
15 | import {
16 | default as deserialize,
17 | } from "https://deno.land/x/pbkit@v0.0.45/core/runtime/wire/deserialize.ts";
18 |
19 | export declare namespace $.pbkit.wrp {
20 | export interface WrpGuestMessage_ResFinish {
21 | reqId: string;
22 | }
23 | }
24 | export type Type = $.pbkit.wrp.WrpGuestMessage_ResFinish;
25 |
26 | export function getDefaultValue(): $.pbkit.wrp.WrpGuestMessage_ResFinish {
27 | return {
28 | reqId: "",
29 | };
30 | }
31 |
32 | export function createValue(partialValue: Partial<$.pbkit.wrp.WrpGuestMessage_ResFinish>): $.pbkit.wrp.WrpGuestMessage_ResFinish {
33 | return {
34 | ...getDefaultValue(),
35 | ...partialValue,
36 | };
37 | }
38 |
39 | export function encodeJson(value: $.pbkit.wrp.WrpGuestMessage_ResFinish): unknown {
40 | const result: any = {};
41 | if (value.reqId !== undefined) result.reqId = tsValueToJsonValueFns.string(value.reqId);
42 | return result;
43 | }
44 |
45 | export function decodeJson(value: any): $.pbkit.wrp.WrpGuestMessage_ResFinish {
46 | const result = getDefaultValue();
47 | if (value.reqId !== undefined) result.reqId = jsonValueToTsValueFns.string(value.reqId);
48 | return result;
49 | }
50 |
51 | export function encodeBinary(value: $.pbkit.wrp.WrpGuestMessage_ResFinish): Uint8Array {
52 | const result: WireMessage = [];
53 | if (value.reqId !== undefined) {
54 | const tsValue = value.reqId;
55 | result.push(
56 | [1, tsValueToWireValueFns.string(tsValue)],
57 | );
58 | }
59 | return serialize(result);
60 | }
61 |
62 | export function decodeBinary(binary: Uint8Array): $.pbkit.wrp.WrpGuestMessage_ResFinish {
63 | const result = getDefaultValue();
64 | const wireMessage = deserialize(binary);
65 | const wireFields = new Map(wireMessage);
66 | field: {
67 | const wireValue = wireFields.get(1);
68 | if (wireValue === undefined) break field;
69 | const value = wireValueToTsValueFns.string(wireValue);
70 | if (value === undefined) break field;
71 | result.reqId = value;
72 | }
73 | return result;
74 | }
75 |
--------------------------------------------------------------------------------
/src/generated/messages/pbkit/wrp/WrpHostMessage_Error.ts:
--------------------------------------------------------------------------------
1 | import {
2 | tsValueToJsonValueFns,
3 | jsonValueToTsValueFns,
4 | } from "https://deno.land/x/pbkit@v0.0.45/core/runtime/json/scalar.ts";
5 | import {
6 | WireMessage,
7 | } from "https://deno.land/x/pbkit@v0.0.45/core/runtime/wire/index.ts";
8 | import {
9 | default as serialize,
10 | } from "https://deno.land/x/pbkit@v0.0.45/core/runtime/wire/serialize.ts";
11 | import {
12 | tsValueToWireValueFns,
13 | wireValueToTsValueFns,
14 | } from "https://deno.land/x/pbkit@v0.0.45/core/runtime/wire/scalar.ts";
15 | import {
16 | default as deserialize,
17 | } from "https://deno.land/x/pbkit@v0.0.45/core/runtime/wire/deserialize.ts";
18 |
19 | export declare namespace $.pbkit.wrp {
20 | export interface WrpHostMessage_Error {
21 | message: string;
22 | }
23 | }
24 | export type Type = $.pbkit.wrp.WrpHostMessage_Error;
25 |
26 | export function getDefaultValue(): $.pbkit.wrp.WrpHostMessage_Error {
27 | return {
28 | message: "",
29 | };
30 | }
31 |
32 | export function createValue(partialValue: Partial<$.pbkit.wrp.WrpHostMessage_Error>): $.pbkit.wrp.WrpHostMessage_Error {
33 | return {
34 | ...getDefaultValue(),
35 | ...partialValue,
36 | };
37 | }
38 |
39 | export function encodeJson(value: $.pbkit.wrp.WrpHostMessage_Error): unknown {
40 | const result: any = {};
41 | if (value.message !== undefined) result.message = tsValueToJsonValueFns.string(value.message);
42 | return result;
43 | }
44 |
45 | export function decodeJson(value: any): $.pbkit.wrp.WrpHostMessage_Error {
46 | const result = getDefaultValue();
47 | if (value.message !== undefined) result.message = jsonValueToTsValueFns.string(value.message);
48 | return result;
49 | }
50 |
51 | export function encodeBinary(value: $.pbkit.wrp.WrpHostMessage_Error): Uint8Array {
52 | const result: WireMessage = [];
53 | if (value.message !== undefined) {
54 | const tsValue = value.message;
55 | result.push(
56 | [1, tsValueToWireValueFns.string(tsValue)],
57 | );
58 | }
59 | return serialize(result);
60 | }
61 |
62 | export function decodeBinary(binary: Uint8Array): $.pbkit.wrp.WrpHostMessage_Error {
63 | const result = getDefaultValue();
64 | const wireMessage = deserialize(binary);
65 | const wireFields = new Map(wireMessage);
66 | field: {
67 | const wireValue = wireFields.get(1);
68 | if (wireValue === undefined) break field;
69 | const value = wireValueToTsValueFns.string(wireValue);
70 | if (value === undefined) break field;
71 | result.message = value;
72 | }
73 | return result;
74 | }
75 |
--------------------------------------------------------------------------------
/src/generated/messages/pbkit/wrp/WrpHostMessage_Initialize.ts:
--------------------------------------------------------------------------------
1 | import {
2 | tsValueToJsonValueFns,
3 | jsonValueToTsValueFns,
4 | } from "https://deno.land/x/pbkit@v0.0.45/core/runtime/json/scalar.ts";
5 | import {
6 | WireMessage,
7 | } from "https://deno.land/x/pbkit@v0.0.45/core/runtime/wire/index.ts";
8 | import {
9 | default as serialize,
10 | } from "https://deno.land/x/pbkit@v0.0.45/core/runtime/wire/serialize.ts";
11 | import {
12 | tsValueToWireValueFns,
13 | wireValueToTsValueFns,
14 | } from "https://deno.land/x/pbkit@v0.0.45/core/runtime/wire/scalar.ts";
15 | import {
16 | default as deserialize,
17 | } from "https://deno.land/x/pbkit@v0.0.45/core/runtime/wire/deserialize.ts";
18 |
19 | export declare namespace $.pbkit.wrp {
20 | export interface WrpHostMessage_Initialize {
21 | availableMethods: string[];
22 | }
23 | }
24 | export type Type = $.pbkit.wrp.WrpHostMessage_Initialize;
25 |
26 | export function getDefaultValue(): $.pbkit.wrp.WrpHostMessage_Initialize {
27 | return {
28 | availableMethods: [],
29 | };
30 | }
31 |
32 | export function createValue(partialValue: Partial<$.pbkit.wrp.WrpHostMessage_Initialize>): $.pbkit.wrp.WrpHostMessage_Initialize {
33 | return {
34 | ...getDefaultValue(),
35 | ...partialValue,
36 | };
37 | }
38 |
39 | export function encodeJson(value: $.pbkit.wrp.WrpHostMessage_Initialize): unknown {
40 | const result: any = {};
41 | result.availableMethods = value.availableMethods.map(value => tsValueToJsonValueFns.string(value));
42 | return result;
43 | }
44 |
45 | export function decodeJson(value: any): $.pbkit.wrp.WrpHostMessage_Initialize {
46 | const result = getDefaultValue();
47 | result.availableMethods = value.availableMethods?.map((value: any) => jsonValueToTsValueFns.string(value)) ?? [];
48 | return result;
49 | }
50 |
51 | export function encodeBinary(value: $.pbkit.wrp.WrpHostMessage_Initialize): Uint8Array {
52 | const result: WireMessage = [];
53 | for (const tsValue of value.availableMethods) {
54 | result.push(
55 | [1, tsValueToWireValueFns.string(tsValue)],
56 | );
57 | }
58 | return serialize(result);
59 | }
60 |
61 | export function decodeBinary(binary: Uint8Array): $.pbkit.wrp.WrpHostMessage_Initialize {
62 | const result = getDefaultValue();
63 | const wireMessage = deserialize(binary);
64 | const wireFields = new Map(wireMessage);
65 | collection: {
66 | const wireValues = wireMessage.filter(([fieldNumber]) => fieldNumber === 1).map(([, wireValue]) => wireValue);
67 | const value = wireValues.map((wireValue) => wireValueToTsValueFns.string(wireValue)).filter(x => x !== undefined);
68 | if (!value.length) break collection;
69 | result.availableMethods = value as any;
70 | }
71 | return result;
72 | }
73 |
--------------------------------------------------------------------------------
/src/generated/messages/pbkit/wrp/WrpHostMessage_ResFinish.ts:
--------------------------------------------------------------------------------
1 | import {
2 | tsValueToJsonValueFns,
3 | jsonValueToTsValueFns,
4 | } from "https://deno.land/x/pbkit@v0.0.45/core/runtime/json/scalar.ts";
5 | import {
6 | WireMessage,
7 | WireType,
8 | } from "https://deno.land/x/pbkit@v0.0.45/core/runtime/wire/index.ts";
9 | import {
10 | default as serialize,
11 | } from "https://deno.land/x/pbkit@v0.0.45/core/runtime/wire/serialize.ts";
12 | import {
13 | tsValueToWireValueFns,
14 | wireValueToTsValueFns,
15 | } from "https://deno.land/x/pbkit@v0.0.45/core/runtime/wire/scalar.ts";
16 | import {
17 | default as deserialize,
18 | } from "https://deno.land/x/pbkit@v0.0.45/core/runtime/wire/deserialize.ts";
19 |
20 | export declare namespace $.pbkit.wrp {
21 | export interface WrpHostMessage_ResFinish {
22 | reqId: string;
23 | trailer: Map;
24 | }
25 | }
26 | export type Type = $.pbkit.wrp.WrpHostMessage_ResFinish;
27 |
28 | export function getDefaultValue(): $.pbkit.wrp.WrpHostMessage_ResFinish {
29 | return {
30 | reqId: "",
31 | trailer: new Map(),
32 | };
33 | }
34 |
35 | export function createValue(partialValue: Partial<$.pbkit.wrp.WrpHostMessage_ResFinish>): $.pbkit.wrp.WrpHostMessage_ResFinish {
36 | return {
37 | ...getDefaultValue(),
38 | ...partialValue,
39 | };
40 | }
41 |
42 | export function encodeJson(value: $.pbkit.wrp.WrpHostMessage_ResFinish): unknown {
43 | const result: any = {};
44 | if (value.reqId !== undefined) result.reqId = tsValueToJsonValueFns.string(value.reqId);
45 | if (value.trailer !== undefined) result.trailer = Object.fromEntries([...value.trailer.entries()].map(([key, value]) => [key, tsValueToJsonValueFns.string(value)]));
46 | return result;
47 | }
48 |
49 | export function decodeJson(value: any): $.pbkit.wrp.WrpHostMessage_ResFinish {
50 | const result = getDefaultValue();
51 | if (value.reqId !== undefined) result.reqId = jsonValueToTsValueFns.string(value.reqId);
52 | if (value.trailer !== undefined) result.trailer = Object.fromEntries([...value.trailer.entries()].map(([key, value]) => [key, jsonValueToTsValueFns.string(value)]));
53 | return result;
54 | }
55 |
56 | export function encodeBinary(value: $.pbkit.wrp.WrpHostMessage_ResFinish): Uint8Array {
57 | const result: WireMessage = [];
58 | if (value.reqId !== undefined) {
59 | const tsValue = value.reqId;
60 | result.push(
61 | [1, tsValueToWireValueFns.string(tsValue)],
62 | );
63 | }
64 | {
65 | const fields = value.trailer.entries();
66 | for (const [key, value] of fields) {
67 | result.push(
68 | [2, { type: WireType.LengthDelimited as const, value: serialize([[1, tsValueToWireValueFns.string(key)], [2, tsValueToWireValueFns.string(value)]]) }],
69 | );
70 | }
71 | }
72 | return serialize(result);
73 | }
74 |
75 | export function decodeBinary(binary: Uint8Array): $.pbkit.wrp.WrpHostMessage_ResFinish {
76 | const result = getDefaultValue();
77 | const wireMessage = deserialize(binary);
78 | const wireFields = new Map(wireMessage);
79 | field: {
80 | const wireValue = wireFields.get(1);
81 | if (wireValue === undefined) break field;
82 | const value = wireValueToTsValueFns.string(wireValue);
83 | if (value === undefined) break field;
84 | result.reqId = value;
85 | }
86 | collection: {
87 | const wireValues = wireMessage.filter(([fieldNumber]) => fieldNumber === 2).map(([, wireValue]) => wireValue);
88 | const value = wireValues.map((wireValue) => (() => { if (wireValue.type !== WireType.LengthDelimited) { return; } const { 1: key, 2: value } = Object.fromEntries(deserialize(wireValue.value)); if (key === undefined || value === undefined) return; return [wireValueToTsValueFns.string(key), wireValueToTsValueFns.string(value)] as const;})()).filter(x => x !== undefined);
89 | if (!value.length) break collection;
90 | result.trailer = new Map(value as any);
91 | }
92 | return result;
93 | }
94 |
--------------------------------------------------------------------------------
/src/generated/messages/pbkit/wrp/WrpHostMessage_ResPayload.ts:
--------------------------------------------------------------------------------
1 | import {
2 | tsValueToJsonValueFns,
3 | jsonValueToTsValueFns,
4 | } from "https://deno.land/x/pbkit@v0.0.45/core/runtime/json/scalar.ts";
5 | import {
6 | WireMessage,
7 | } from "https://deno.land/x/pbkit@v0.0.45/core/runtime/wire/index.ts";
8 | import {
9 | default as serialize,
10 | } from "https://deno.land/x/pbkit@v0.0.45/core/runtime/wire/serialize.ts";
11 | import {
12 | tsValueToWireValueFns,
13 | wireValueToTsValueFns,
14 | } from "https://deno.land/x/pbkit@v0.0.45/core/runtime/wire/scalar.ts";
15 | import {
16 | default as deserialize,
17 | } from "https://deno.land/x/pbkit@v0.0.45/core/runtime/wire/deserialize.ts";
18 |
19 | export declare namespace $.pbkit.wrp {
20 | export interface WrpHostMessage_ResPayload {
21 | reqId: string;
22 | payload: Uint8Array;
23 | }
24 | }
25 | export type Type = $.pbkit.wrp.WrpHostMessage_ResPayload;
26 |
27 | export function getDefaultValue(): $.pbkit.wrp.WrpHostMessage_ResPayload {
28 | return {
29 | reqId: "",
30 | payload: new Uint8Array(),
31 | };
32 | }
33 |
34 | export function createValue(partialValue: Partial<$.pbkit.wrp.WrpHostMessage_ResPayload>): $.pbkit.wrp.WrpHostMessage_ResPayload {
35 | return {
36 | ...getDefaultValue(),
37 | ...partialValue,
38 | };
39 | }
40 |
41 | export function encodeJson(value: $.pbkit.wrp.WrpHostMessage_ResPayload): unknown {
42 | const result: any = {};
43 | if (value.reqId !== undefined) result.reqId = tsValueToJsonValueFns.string(value.reqId);
44 | if (value.payload !== undefined) result.payload = tsValueToJsonValueFns.bytes(value.payload);
45 | return result;
46 | }
47 |
48 | export function decodeJson(value: any): $.pbkit.wrp.WrpHostMessage_ResPayload {
49 | const result = getDefaultValue();
50 | if (value.reqId !== undefined) result.reqId = jsonValueToTsValueFns.string(value.reqId);
51 | if (value.payload !== undefined) result.payload = jsonValueToTsValueFns.bytes(value.payload);
52 | return result;
53 | }
54 |
55 | export function encodeBinary(value: $.pbkit.wrp.WrpHostMessage_ResPayload): Uint8Array {
56 | const result: WireMessage = [];
57 | if (value.reqId !== undefined) {
58 | const tsValue = value.reqId;
59 | result.push(
60 | [1, tsValueToWireValueFns.string(tsValue)],
61 | );
62 | }
63 | if (value.payload !== undefined) {
64 | const tsValue = value.payload;
65 | result.push(
66 | [2, tsValueToWireValueFns.bytes(tsValue)],
67 | );
68 | }
69 | return serialize(result);
70 | }
71 |
72 | export function decodeBinary(binary: Uint8Array): $.pbkit.wrp.WrpHostMessage_ResPayload {
73 | const result = getDefaultValue();
74 | const wireMessage = deserialize(binary);
75 | const wireFields = new Map(wireMessage);
76 | field: {
77 | const wireValue = wireFields.get(1);
78 | if (wireValue === undefined) break field;
79 | const value = wireValueToTsValueFns.string(wireValue);
80 | if (value === undefined) break field;
81 | result.reqId = value;
82 | }
83 | field: {
84 | const wireValue = wireFields.get(2);
85 | if (wireValue === undefined) break field;
86 | const value = wireValueToTsValueFns.bytes(wireValue);
87 | if (value === undefined) break field;
88 | result.payload = value;
89 | }
90 | return result;
91 | }
92 |
--------------------------------------------------------------------------------
/src/generated/messages/pbkit/wrp/WrpHostMessage_ResStart.ts:
--------------------------------------------------------------------------------
1 | import {
2 | tsValueToJsonValueFns,
3 | jsonValueToTsValueFns,
4 | } from "https://deno.land/x/pbkit@v0.0.45/core/runtime/json/scalar.ts";
5 | import {
6 | WireMessage,
7 | WireType,
8 | } from "https://deno.land/x/pbkit@v0.0.45/core/runtime/wire/index.ts";
9 | import {
10 | default as serialize,
11 | } from "https://deno.land/x/pbkit@v0.0.45/core/runtime/wire/serialize.ts";
12 | import {
13 | tsValueToWireValueFns,
14 | wireValueToTsValueFns,
15 | } from "https://deno.land/x/pbkit@v0.0.45/core/runtime/wire/scalar.ts";
16 | import {
17 | default as deserialize,
18 | } from "https://deno.land/x/pbkit@v0.0.45/core/runtime/wire/deserialize.ts";
19 |
20 | export declare namespace $.pbkit.wrp {
21 | export interface WrpHostMessage_ResStart {
22 | reqId: string;
23 | header: Map;
24 | }
25 | }
26 | export type Type = $.pbkit.wrp.WrpHostMessage_ResStart;
27 |
28 | export function getDefaultValue(): $.pbkit.wrp.WrpHostMessage_ResStart {
29 | return {
30 | reqId: "",
31 | header: new Map(),
32 | };
33 | }
34 |
35 | export function createValue(partialValue: Partial<$.pbkit.wrp.WrpHostMessage_ResStart>): $.pbkit.wrp.WrpHostMessage_ResStart {
36 | return {
37 | ...getDefaultValue(),
38 | ...partialValue,
39 | };
40 | }
41 |
42 | export function encodeJson(value: $.pbkit.wrp.WrpHostMessage_ResStart): unknown {
43 | const result: any = {};
44 | if (value.reqId !== undefined) result.reqId = tsValueToJsonValueFns.string(value.reqId);
45 | if (value.header !== undefined) result.header = Object.fromEntries([...value.header.entries()].map(([key, value]) => [key, tsValueToJsonValueFns.string(value)]));
46 | return result;
47 | }
48 |
49 | export function decodeJson(value: any): $.pbkit.wrp.WrpHostMessage_ResStart {
50 | const result = getDefaultValue();
51 | if (value.reqId !== undefined) result.reqId = jsonValueToTsValueFns.string(value.reqId);
52 | if (value.header !== undefined) result.header = Object.fromEntries([...value.header.entries()].map(([key, value]) => [key, jsonValueToTsValueFns.string(value)]));
53 | return result;
54 | }
55 |
56 | export function encodeBinary(value: $.pbkit.wrp.WrpHostMessage_ResStart): Uint8Array {
57 | const result: WireMessage = [];
58 | if (value.reqId !== undefined) {
59 | const tsValue = value.reqId;
60 | result.push(
61 | [1, tsValueToWireValueFns.string(tsValue)],
62 | );
63 | }
64 | {
65 | const fields = value.header.entries();
66 | for (const [key, value] of fields) {
67 | result.push(
68 | [2, { type: WireType.LengthDelimited as const, value: serialize([[1, tsValueToWireValueFns.string(key)], [2, tsValueToWireValueFns.string(value)]]) }],
69 | );
70 | }
71 | }
72 | return serialize(result);
73 | }
74 |
75 | export function decodeBinary(binary: Uint8Array): $.pbkit.wrp.WrpHostMessage_ResStart {
76 | const result = getDefaultValue();
77 | const wireMessage = deserialize(binary);
78 | const wireFields = new Map(wireMessage);
79 | field: {
80 | const wireValue = wireFields.get(1);
81 | if (wireValue === undefined) break field;
82 | const value = wireValueToTsValueFns.string(wireValue);
83 | if (value === undefined) break field;
84 | result.reqId = value;
85 | }
86 | collection: {
87 | const wireValues = wireMessage.filter(([fieldNumber]) => fieldNumber === 2).map(([, wireValue]) => wireValue);
88 | const value = wireValues.map((wireValue) => (() => { if (wireValue.type !== WireType.LengthDelimited) { return; } const { 1: key, 2: value } = Object.fromEntries(deserialize(wireValue.value)); if (key === undefined || value === undefined) return; return [wireValueToTsValueFns.string(key), wireValueToTsValueFns.string(value)] as const;})()).filter(x => x !== undefined);
89 | if (!value.length) break collection;
90 | result.header = new Map(value as any);
91 | }
92 | return result;
93 | }
94 |
--------------------------------------------------------------------------------
/src/generated/messages/pbkit/wrp/WrpMessage.ts:
--------------------------------------------------------------------------------
1 | import {
2 | Type as WrpHostMessage_Initialize,
3 | encodeJson as encodeJson_1,
4 | decodeJson as decodeJson_1,
5 | encodeBinary as encodeBinary_1,
6 | decodeBinary as decodeBinary_1,
7 | } from "./WrpHostMessage_Initialize.ts";
8 | import {
9 | Type as WrpHostMessage_Error,
10 | encodeJson as encodeJson_2,
11 | decodeJson as decodeJson_2,
12 | encodeBinary as encodeBinary_2,
13 | decodeBinary as decodeBinary_2,
14 | } from "./WrpHostMessage_Error.ts";
15 | import {
16 | Type as WrpHostMessage_ResStart,
17 | encodeJson as encodeJson_3,
18 | decodeJson as decodeJson_3,
19 | encodeBinary as encodeBinary_3,
20 | decodeBinary as decodeBinary_3,
21 | } from "./WrpHostMessage_ResStart.ts";
22 | import {
23 | Type as WrpHostMessage_ResPayload,
24 | encodeJson as encodeJson_4,
25 | decodeJson as decodeJson_4,
26 | encodeBinary as encodeBinary_4,
27 | decodeBinary as decodeBinary_4,
28 | } from "./WrpHostMessage_ResPayload.ts";
29 | import {
30 | Type as WrpHostMessage_ResFinish,
31 | encodeJson as encodeJson_5,
32 | decodeJson as decodeJson_5,
33 | encodeBinary as encodeBinary_5,
34 | decodeBinary as decodeBinary_5,
35 | } from "./WrpHostMessage_ResFinish.ts";
36 | import {
37 | Type as WrpGuestMessage_ReqStart,
38 | encodeJson as encodeJson_6,
39 | decodeJson as decodeJson_6,
40 | encodeBinary as encodeBinary_6,
41 | decodeBinary as decodeBinary_6,
42 | } from "./WrpGuestMessage_ReqStart.ts";
43 | import {
44 | Type as WrpGuestMessage_ReqPayload,
45 | encodeJson as encodeJson_7,
46 | decodeJson as decodeJson_7,
47 | encodeBinary as encodeBinary_7,
48 | decodeBinary as decodeBinary_7,
49 | } from "./WrpGuestMessage_ReqPayload.ts";
50 | import {
51 | Type as WrpGuestMessage_ReqFinish,
52 | encodeJson as encodeJson_8,
53 | decodeJson as decodeJson_8,
54 | encodeBinary as encodeBinary_8,
55 | decodeBinary as decodeBinary_8,
56 | } from "./WrpGuestMessage_ReqFinish.ts";
57 | import {
58 | Type as WrpGuestMessage_ResFinish,
59 | encodeJson as encodeJson_9,
60 | decodeJson as decodeJson_9,
61 | encodeBinary as encodeBinary_9,
62 | decodeBinary as decodeBinary_9,
63 | } from "./WrpGuestMessage_ResFinish.ts";
64 | import {
65 | jsonValueToTsValueFns,
66 | } from "https://deno.land/x/pbkit@v0.0.45/core/runtime/json/scalar.ts";
67 | import {
68 | WireMessage,
69 | WireType,
70 | Field,
71 | } from "https://deno.land/x/pbkit@v0.0.45/core/runtime/wire/index.ts";
72 | import {
73 | default as serialize,
74 | } from "https://deno.land/x/pbkit@v0.0.45/core/runtime/wire/serialize.ts";
75 | import {
76 | default as deserialize,
77 | } from "https://deno.land/x/pbkit@v0.0.45/core/runtime/wire/deserialize.ts";
78 |
79 | export declare namespace $.pbkit.wrp {
80 | export interface WrpMessage {
81 | message?: (
82 | | { field: "HostInitialize", value: WrpHostMessage_Initialize }
83 | | { field: "HostError", value: WrpHostMessage_Error }
84 | | { field: "HostResStart", value: WrpHostMessage_ResStart }
85 | | { field: "HostResPayload", value: WrpHostMessage_ResPayload }
86 | | { field: "HostResFinish", value: WrpHostMessage_ResFinish }
87 | | { field: "GuestReqStart", value: WrpGuestMessage_ReqStart }
88 | | { field: "GuestReqPayload", value: WrpGuestMessage_ReqPayload }
89 | | { field: "GuestReqFinish", value: WrpGuestMessage_ReqFinish }
90 | | { field: "GuestResFinish", value: WrpGuestMessage_ResFinish }
91 | );
92 | }
93 | }
94 | export type Type = $.pbkit.wrp.WrpMessage;
95 |
96 | export function getDefaultValue(): $.pbkit.wrp.WrpMessage {
97 | return {
98 | message: undefined,
99 | };
100 | }
101 |
102 | export function createValue(partialValue: Partial<$.pbkit.wrp.WrpMessage>): $.pbkit.wrp.WrpMessage {
103 | return {
104 | ...getDefaultValue(),
105 | ...partialValue,
106 | };
107 | }
108 |
109 | export function encodeJson(value: $.pbkit.wrp.WrpMessage): unknown {
110 | const result: any = {};
111 | switch (value.message?.field) {
112 | case "HostInitialize": {
113 | result.HostInitialize = encodeJson_1(value.message.value);
114 | break;
115 | }
116 | case "HostError": {
117 | result.HostError = encodeJson_2(value.message.value);
118 | break;
119 | }
120 | case "HostResStart": {
121 | result.HostResStart = encodeJson_3(value.message.value);
122 | break;
123 | }
124 | case "HostResPayload": {
125 | result.HostResPayload = encodeJson_4(value.message.value);
126 | break;
127 | }
128 | case "HostResFinish": {
129 | result.HostResFinish = encodeJson_5(value.message.value);
130 | break;
131 | }
132 | case "GuestReqStart": {
133 | result.GuestReqStart = encodeJson_6(value.message.value);
134 | break;
135 | }
136 | case "GuestReqPayload": {
137 | result.GuestReqPayload = encodeJson_7(value.message.value);
138 | break;
139 | }
140 | case "GuestReqFinish": {
141 | result.GuestReqFinish = encodeJson_8(value.message.value);
142 | break;
143 | }
144 | case "GuestResFinish": {
145 | result.GuestResFinish = encodeJson_9(value.message.value);
146 | break;
147 | }
148 | }
149 | return result;
150 | }
151 |
152 | export function decodeJson(value: any): $.pbkit.wrp.WrpMessage {
153 | const result = getDefaultValue();
154 | if (value.HostInitialize !== undefined) result.message = {field: "HostInitialize", value: decodeJson_1(value.HostInitialize)};
155 | if (value.HostError !== undefined) result.message = {field: "HostError", value: decodeJson_2(value.HostError)};
156 | if (value.HostResStart !== undefined) result.message = {field: "HostResStart", value: decodeJson_3(value.HostResStart)};
157 | if (value.HostResPayload !== undefined) result.message = {field: "HostResPayload", value: decodeJson_4(value.HostResPayload)};
158 | if (value.HostResFinish !== undefined) result.message = {field: "HostResFinish", value: decodeJson_5(value.HostResFinish)};
159 | if (value.GuestReqStart !== undefined) result.message = {field: "GuestReqStart", value: decodeJson_6(value.GuestReqStart)};
160 | if (value.GuestReqPayload !== undefined) result.message = {field: "GuestReqPayload", value: decodeJson_7(value.GuestReqPayload)};
161 | if (value.GuestReqFinish !== undefined) result.message = {field: "GuestReqFinish", value: decodeJson_8(value.GuestReqFinish)};
162 | if (value.GuestResFinish !== undefined) result.message = {field: "GuestResFinish", value: decodeJson_9(value.GuestResFinish)};
163 | return result;
164 | }
165 |
166 | export function encodeBinary(value: $.pbkit.wrp.WrpMessage): Uint8Array {
167 | const result: WireMessage = [];
168 | switch (value.message?.field) {
169 | case "HostInitialize": {
170 | const tsValue = value.message.value;
171 | result.push(
172 | [1, { type: WireType.LengthDelimited as const, value: encodeBinary_1(tsValue) }],
173 | );
174 | break;
175 | }
176 | case "HostError": {
177 | const tsValue = value.message.value;
178 | result.push(
179 | [2, { type: WireType.LengthDelimited as const, value: encodeBinary_2(tsValue) }],
180 | );
181 | break;
182 | }
183 | case "HostResStart": {
184 | const tsValue = value.message.value;
185 | result.push(
186 | [3, { type: WireType.LengthDelimited as const, value: encodeBinary_3(tsValue) }],
187 | );
188 | break;
189 | }
190 | case "HostResPayload": {
191 | const tsValue = value.message.value;
192 | result.push(
193 | [4, { type: WireType.LengthDelimited as const, value: encodeBinary_4(tsValue) }],
194 | );
195 | break;
196 | }
197 | case "HostResFinish": {
198 | const tsValue = value.message.value;
199 | result.push(
200 | [5, { type: WireType.LengthDelimited as const, value: encodeBinary_5(tsValue) }],
201 | );
202 | break;
203 | }
204 | case "GuestReqStart": {
205 | const tsValue = value.message.value;
206 | result.push(
207 | [6, { type: WireType.LengthDelimited as const, value: encodeBinary_6(tsValue) }],
208 | );
209 | break;
210 | }
211 | case "GuestReqPayload": {
212 | const tsValue = value.message.value;
213 | result.push(
214 | [7, { type: WireType.LengthDelimited as const, value: encodeBinary_7(tsValue) }],
215 | );
216 | break;
217 | }
218 | case "GuestReqFinish": {
219 | const tsValue = value.message.value;
220 | result.push(
221 | [8, { type: WireType.LengthDelimited as const, value: encodeBinary_8(tsValue) }],
222 | );
223 | break;
224 | }
225 | case "GuestResFinish": {
226 | const tsValue = value.message.value;
227 | result.push(
228 | [9, { type: WireType.LengthDelimited as const, value: encodeBinary_9(tsValue) }],
229 | );
230 | break;
231 | }
232 | }
233 | return serialize(result);
234 | }
235 |
236 | const fieldNames: Map = new Map([
237 | [1, "HostInitialize"],
238 | [2, "HostError"],
239 | [3, "HostResStart"],
240 | [4, "HostResPayload"],
241 | [5, "HostResFinish"],
242 | [6, "GuestReqStart"],
243 | [7, "GuestReqPayload"],
244 | [8, "GuestReqFinish"],
245 | [9, "GuestResFinish"],
246 | ]);
247 | const oneofFieldNumbersMap: { [oneof: string]: Set } = {
248 | message: new Set([1, 2, 3, 4, 5, 6, 7, 8, 9]),
249 | };
250 | const oneofFieldNamesMap = {
251 | message: new Map([
252 | [1, "HostInitialize" as const],
253 | [2, "HostError" as const],
254 | [3, "HostResStart" as const],
255 | [4, "HostResPayload" as const],
256 | [5, "HostResFinish" as const],
257 | [6, "GuestReqStart" as const],
258 | [7, "GuestReqPayload" as const],
259 | [8, "GuestReqFinish" as const],
260 | [9, "GuestResFinish" as const],
261 | ]),
262 | };
263 | export function decodeBinary(binary: Uint8Array): $.pbkit.wrp.WrpMessage {
264 | const result = getDefaultValue();
265 | const wireMessage = deserialize(binary);
266 | const wireFields = new Map(wireMessage);
267 | const wireFieldNumbers = Array.from(wireFields.keys()).reverse();
268 | oneof: {
269 | const oneofFieldNumbers = oneofFieldNumbersMap.message;
270 | const oneofFieldNames = oneofFieldNamesMap.message;
271 | const fieldNumber = wireFieldNumbers.find(v => oneofFieldNumbers.has(v));
272 | if (fieldNumber == null) break oneof;
273 | const wireValue = wireFields.get(fieldNumber);
274 | const wireValueToTsValueMap = {
275 | [1](wireValue: Field) { return wireValue.type === WireType.LengthDelimited ? decodeBinary_1(wireValue.value) : undefined; },
276 | [2](wireValue: Field) { return wireValue.type === WireType.LengthDelimited ? decodeBinary_2(wireValue.value) : undefined; },
277 | [3](wireValue: Field) { return wireValue.type === WireType.LengthDelimited ? decodeBinary_3(wireValue.value) : undefined; },
278 | [4](wireValue: Field) { return wireValue.type === WireType.LengthDelimited ? decodeBinary_4(wireValue.value) : undefined; },
279 | [5](wireValue: Field) { return wireValue.type === WireType.LengthDelimited ? decodeBinary_5(wireValue.value) : undefined; },
280 | [6](wireValue: Field) { return wireValue.type === WireType.LengthDelimited ? decodeBinary_6(wireValue.value) : undefined; },
281 | [7](wireValue: Field) { return wireValue.type === WireType.LengthDelimited ? decodeBinary_7(wireValue.value) : undefined; },
282 | [8](wireValue: Field) { return wireValue.type === WireType.LengthDelimited ? decodeBinary_8(wireValue.value) : undefined; },
283 | [9](wireValue: Field) { return wireValue.type === WireType.LengthDelimited ? decodeBinary_9(wireValue.value) : undefined; },
284 | };
285 | const value = (wireValueToTsValueMap[fieldNumber as keyof typeof wireValueToTsValueMap] as any)?.(wireValue!);
286 | if (value === undefined) break oneof;
287 | result.message = { field: oneofFieldNames.get(fieldNumber)!, value: value as any };
288 | }
289 | return result;
290 | }
291 |
--------------------------------------------------------------------------------
/src/generated/messages/pbkit/wrp/index.ts:
--------------------------------------------------------------------------------
1 | export type { Type as WrpMessage } from "./WrpMessage.ts";
2 | export type { Type as WrpHostMessage_Initialize } from "./WrpHostMessage_Initialize.ts";
3 | export type { Type as WrpHostMessage_Error } from "./WrpHostMessage_Error.ts";
4 | export type { Type as WrpHostMessage_ResStart } from "./WrpHostMessage_ResStart.ts";
5 | export type { Type as WrpHostMessage_ResPayload } from "./WrpHostMessage_ResPayload.ts";
6 | export type { Type as WrpHostMessage_ResFinish } from "./WrpHostMessage_ResFinish.ts";
7 | export type { Type as WrpGuestMessage_ReqStart } from "./WrpGuestMessage_ReqStart.ts";
8 | export type { Type as WrpGuestMessage_ReqPayload } from "./WrpGuestMessage_ReqPayload.ts";
9 | export type { Type as WrpGuestMessage_ReqFinish } from "./WrpGuestMessage_ReqFinish.ts";
10 | export type { Type as WrpGuestMessage_ResFinish } from "./WrpGuestMessage_ResFinish.ts";
11 |
--------------------------------------------------------------------------------
/src/glue/android.ts:
--------------------------------------------------------------------------------
1 | import { Socket } from "../socket.ts";
2 | import { checkAndRetryUntilSuccess, u8s2str } from "./misc/util.ts";
3 | import { getGlue } from "./index.ts";
4 |
5 | // https://developer.android.com/reference/android/webkit/WebView#addJavascriptInterface(java.lang.Object,%20java.lang.String)
6 | // https://stackoverflow.com/a/45506857
7 |
8 | const key = "";
9 |
10 | export async function createAndroidSocket(): Promise {
11 | const androidGlue = await getAndroidGlue();
12 | return {
13 | read: getGlue().read,
14 | async write(data) {
15 | androidGlue.recv(u8s2str(data));
16 | return data.byteLength;
17 | },
18 | };
19 | }
20 |
21 | interface AndroidGlue {
22 | recv(data: string): Promise;
23 | }
24 | async function getAndroidGlue(): Promise {
25 | return await checkAndRetryUntilSuccess(
26 | () => (globalThis as any)[key] || undefined,
27 | );
28 | }
29 |
--------------------------------------------------------------------------------
/src/glue/child-window.ts:
--------------------------------------------------------------------------------
1 | import { defer } from "https://deno.land/x/pbkit@v0.0.45/core/runtime/async/observer.ts";
2 | import { Closer, Socket } from "../socket.ts";
3 | import { createGlue } from "./index.ts";
4 | import {
5 | isGlueEvent,
6 | isGlueHandshakeEvent,
7 | postGlueHandshakeMessage,
8 | postGlueMessage,
9 | } from "./misc/web.ts";
10 |
11 | export interface CreateChildWindowSocketConfig {
12 | child: Window;
13 | childWindowOrigin: string;
14 | onClosed?: () => void;
15 | }
16 | export async function createChildWindowSocket(
17 | config: CreateChildWindowSocketConfig,
18 | ): Promise {
19 | const { child, childWindowOrigin, onClosed } = config;
20 | await handshake(child, childWindowOrigin);
21 | globalThis.addEventListener("message", messageHandler);
22 | const healthcheckId = setInterval(healthcheck, 100);
23 | const glue = createGlue();
24 | return {
25 | read: glue.read,
26 | async write(data) {
27 | const length = data.byteLength;
28 | const success = postGlueMessage({
29 | target: child,
30 | targetOrigin: childWindowOrigin,
31 | payload: data,
32 | });
33 | if (!success) close();
34 | return success ? length : 0;
35 | },
36 | close,
37 | };
38 | function close() {
39 | clearInterval(healthcheckId);
40 | globalThis.removeEventListener("message", messageHandler);
41 | glue.close();
42 | onClosed?.();
43 | }
44 | function healthcheck() {
45 | if (child.closed) close();
46 | }
47 | function messageHandler(e: MessageEvent) {
48 | if (e.source !== child) return;
49 | if (!isGlueEvent(e)) return;
50 | const [, data] = e.data;
51 | glue.recv(data);
52 | }
53 | }
54 |
55 | // wait syn -> send syn-ack -> wait ack
56 | async function handshake(child: Window, childWindowOrigin: string) {
57 | let synAcked = false;
58 | const wait = defer();
59 | const healthcheckId = setInterval(healthcheck, 100);
60 | const timeoutId = setTimeout(() => {
61 | abort(new Error("Handshake timeout."));
62 | }, 10000);
63 | globalThis.addEventListener("message", handshakeHandler);
64 | function handshakeHandler(e: MessageEvent) {
65 | if (e.source !== child) return;
66 | if (!isGlueHandshakeEvent(e)) return;
67 | const [, data] = e.data;
68 | if (data === "syn") {
69 | synAck();
70 | } else if (data === "ack") {
71 | if (synAcked) finish();
72 | else abort(new Error("Invalid ack."));
73 | }
74 | }
75 | return await wait;
76 | function healthcheck() {
77 | if (child.closed) abort(new Error("Child window is closed."));
78 | }
79 | function cleanup() {
80 | clearInterval(healthcheckId);
81 | clearTimeout(timeoutId);
82 | globalThis.removeEventListener("message", handshakeHandler);
83 | }
84 | function abort(reason: Error) {
85 | cleanup();
86 | wait.reject(reason);
87 | }
88 | function finish() {
89 | cleanup();
90 | wait.resolve();
91 | }
92 | function synAck() {
93 | synAcked = true;
94 | const success = postGlueHandshakeMessage({
95 | target: child,
96 | targetOrigin: childWindowOrigin,
97 | payload: "syn-ack",
98 | });
99 | if (!success) abort(new Error("Failed to send syn-ack."));
100 | }
101 | }
102 |
--------------------------------------------------------------------------------
/src/glue/iframe.ts:
--------------------------------------------------------------------------------
1 | import { Closer, Socket } from "../socket.ts";
2 | import { checkAndRetryUntilSuccess } from "./misc/util.ts";
3 | import { createChildWindowSocket } from "./child-window.ts";
4 |
5 | export interface CreateIframeSocketConfig {
6 | iframeElement: HTMLIFrameElement;
7 | iframeOrigin: string;
8 | onClosed?: () => void;
9 | }
10 | export async function createIframeSocket(
11 | config: CreateIframeSocketConfig,
12 | ): Promise {
13 | const { iframeElement, iframeOrigin, onClosed } = config;
14 | const iframeWindow = await checkAndRetryUntilSuccess(
15 | () => iframeElement.contentWindow || undefined,
16 | );
17 | const childWindowSocket = await createChildWindowSocket({
18 | child: iframeWindow,
19 | childWindowOrigin: iframeOrigin,
20 | onClosed,
21 | });
22 | onceIframeReloaded(iframeElement, childWindowSocket.close);
23 | return childWindowSocket;
24 | }
25 |
26 | function onceIframeReloaded(iframeElement: HTMLIFrameElement, fn: () => void) {
27 | iframeElement.addEventListener("load", loadHandler);
28 | function loadHandler() {
29 | iframeElement.removeEventListener("load", loadHandler);
30 | fn();
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/glue/index.ts:
--------------------------------------------------------------------------------
1 | import {
2 | defer,
3 | Deferred,
4 | } from "https://deno.land/x/pbkit@v0.0.45/core/runtime/async/observer.ts";
5 | import { Closer, Reader } from "../socket.ts";
6 | import { chain } from "../misc.ts";
7 | import { str2u8s } from "./misc/util.ts";
8 |
9 | export interface Glue extends Closer, Reader {
10 | recv(data: Uint8Array | string): void;
11 | }
12 |
13 | const key = "";
14 |
15 | export function getGlue(): Glue {
16 | const global = globalThis as any;
17 | if (global[key]) return global[key];
18 | return global[key] = createGlue();
19 | }
20 |
21 | export function createGlue(): Glue {
22 | const queue: Uint8Array[] = [];
23 | let closed = false;
24 | let wait: Deferred | undefined;
25 | return {
26 | close() {
27 | closed = true;
28 | wait?.resolve();
29 | },
30 | recv: (data) => {
31 | if (closed) throw new Error("Glue has been closed.");
32 | queue.push(typeof data === "string" ? str2u8s(data) : data);
33 | wait?.resolve();
34 | },
35 | read: chain(async (data) => {
36 | if (queue.length < 1) {
37 | if (closed) return null;
38 | await (wait = defer());
39 | wait = undefined;
40 | }
41 | const first = queue[0];
42 | if (first.byteLength <= data.byteLength) {
43 | queue.shift();
44 | data.set(first);
45 | return first.byteLength;
46 | }
47 | data.set(first.subarray(0, data.byteLength));
48 | queue[0] = first.subarray(data.byteLength);
49 | return data.byteLength;
50 | }),
51 | };
52 | }
53 |
--------------------------------------------------------------------------------
/src/glue/ios.ts:
--------------------------------------------------------------------------------
1 | import { Socket } from "../socket.ts";
2 | import { checkAndRetryUntilSuccess, u8s2str } from "./misc/util.ts";
3 | import { getGlue } from "./index.ts";
4 |
5 | // https://developer.apple.com/documentation/webkit/wkusercontentcontroller/1537172-add
6 |
7 | export async function createIosSocket(): Promise {
8 | const iosGlue = await getIosGlue();
9 | return {
10 | read: getGlue().read,
11 | async write(data) {
12 | return iosGlue.postMessage(u8s2str(data)).then(() => data.byteLength);
13 | },
14 | };
15 | }
16 |
17 | interface IosGlue {
18 | postMessage(data: string): Promise;
19 | }
20 | async function getIosGlue(): Promise {
21 | return await checkAndRetryUntilSuccess(
22 | () => (globalThis as any).webkit?.messageHandlers?.glue || undefined,
23 | );
24 | }
25 |
--------------------------------------------------------------------------------
/src/glue/misc/util.ts:
--------------------------------------------------------------------------------
1 | export function u8s2str(u8s: Uint8Array): string {
2 | return u8s.reduce((acc, curr) => acc + String.fromCharCode(curr), "");
3 | }
4 |
5 | export function str2u8s(str: string): Uint8Array {
6 | const u8s = new Uint8Array(str.length);
7 | for (let i = 0; i < str.length; ++i) u8s[i] = str.charCodeAt(i);
8 | return u8s;
9 | }
10 |
11 | export function checkAndRetryUntilSuccess(
12 | checkFn: () => T | undefined,
13 | retryFn?: () => void,
14 | interval: number = 100,
15 | limit: number = 10,
16 | ): Promise {
17 | return new Promise((resolve, reject) => {
18 | let count = 0;
19 | const intervalId = setInterval(() => {
20 | const result = checkFn();
21 | if (result !== undefined) {
22 | clearInterval(intervalId);
23 | resolve(result);
24 | return;
25 | }
26 | if (count++ < limit) {
27 | retryFn?.();
28 | } else {
29 | clearInterval(intervalId);
30 | reject(new Error("Exhausted all retries"));
31 | }
32 | }, interval);
33 | });
34 | }
35 |
--------------------------------------------------------------------------------
/src/glue/misc/web.ts:
--------------------------------------------------------------------------------
1 | export const eventKey = "";
2 | export const handshakeEventKey = "";
3 |
4 | export interface GlueEvent {
5 | data: [
6 | glueKey: typeof eventKey,
7 | payload: Uint8Array,
8 | ];
9 | source: typeof globalThis | Window;
10 | }
11 | export function isGlueEvent(event: any): event is GlueEvent {
12 | if (!Array.isArray(event.data)) return false;
13 | if (event.data.length < 2) return false;
14 | if (event.data[0] !== eventKey) return false;
15 | return true;
16 | }
17 |
18 | export interface GlueHandshakeEvent {
19 | data: [
20 | glueKey: typeof handshakeEventKey,
21 | payload: "syn" | "syn-ack" | "ack",
22 | ];
23 | source: typeof globalThis | Window;
24 | }
25 | export function isGlueHandshakeEvent(event: any): event is GlueHandshakeEvent {
26 | if (!Array.isArray(event.data)) return false;
27 | if (event.data.length < 2) return false;
28 | if (event.data[0] !== handshakeEventKey) return false;
29 | return true;
30 | }
31 |
32 | export interface PostGlueHandshakeMessageConfig {
33 | target: Window;
34 | targetOrigin: string;
35 | payload: GlueHandshakeEvent["data"]["1"];
36 | }
37 | export function postGlueHandshakeMessage(
38 | config: PostGlueHandshakeMessageConfig,
39 | ): boolean {
40 | const { target, targetOrigin, payload } = config;
41 | if (target.closed) return false;
42 | target.postMessage([handshakeEventKey, payload], targetOrigin);
43 | return true;
44 | }
45 |
46 | export interface PostGlueMessageConfig {
47 | target: Window;
48 | targetOrigin: string;
49 | payload: Uint8Array;
50 | }
51 | export function postGlueMessage(config: PostGlueMessageConfig): boolean {
52 | const { target, targetOrigin, payload } = config;
53 | if (target.closed) return false;
54 | target.postMessage([eventKey, payload], targetOrigin, [payload.buffer]);
55 | return true;
56 | }
57 |
--------------------------------------------------------------------------------
/src/glue/parent-window.ts:
--------------------------------------------------------------------------------
1 | import { defer } from "https://deno.land/x/pbkit@v0.0.45/core/runtime/async/observer.ts";
2 | import { Closer, Socket } from "../socket.ts";
3 | import { getGlue } from "./index.ts";
4 | import {
5 | isGlueEvent,
6 | isGlueHandshakeEvent,
7 | postGlueHandshakeMessage,
8 | postGlueMessage,
9 | } from "./misc/web.ts";
10 |
11 | // https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API
12 | // https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage
13 |
14 | export interface CreateParentWindowSocketConfig {
15 | parent?: Window | null;
16 | parentWindowOrigin: string;
17 | onClosed?: () => void;
18 | }
19 | export async function createParentWindowSocket(
20 | config: CreateParentWindowSocketConfig,
21 | ): Promise {
22 | const { parent = globalThis.parent, parentWindowOrigin, onClosed } = config;
23 | if (!parent?.postMessage) throw new Error("There is no parent window.");
24 | if (parent === globalThis.self) throw new Error("Invalid parent window.");
25 | const wait = defer();
26 | let acked = false;
27 | globalThis.addEventListener("message", messageHandler);
28 | globalThis.addEventListener("message", handshakeHandler);
29 | const glue = getGlue();
30 | const connId = setInterval(syn, 100);
31 | syn();
32 | await wait;
33 | return {
34 | read: glue.read,
35 | async write(data) {
36 | const length = data.byteLength;
37 | const success = postGlueMessage({
38 | target: parent,
39 | targetOrigin: parentWindowOrigin,
40 | payload: data,
41 | });
42 | if (!success) close();
43 | return success ? length : 0;
44 | },
45 | close,
46 | };
47 | function close() {
48 | clearInterval(connId);
49 | globalThis.removeEventListener("message", messageHandler);
50 | globalThis.removeEventListener("message", handshakeHandler);
51 | glue.close();
52 | onClosed?.();
53 | wait.reject(new Error("Connection closed."));
54 | }
55 | function syn() {
56 | const success = postGlueHandshakeMessage({
57 | target: parent!,
58 | targetOrigin: parentWindowOrigin,
59 | payload: "syn",
60 | });
61 | if (!success) close();
62 | }
63 | function ack() {
64 | acked = true;
65 | const success = postGlueHandshakeMessage({
66 | target: parent!,
67 | targetOrigin: parentWindowOrigin,
68 | payload: "ack",
69 | });
70 | if (success) wait.resolve();
71 | else close();
72 | }
73 | function messageHandler(e: MessageEvent) {
74 | if (e.source !== parent) return;
75 | if (!isGlueEvent(e)) return;
76 | const [, data] = e.data;
77 | glue.recv(data);
78 | }
79 | function handshakeHandler(event: any) {
80 | if (event.source !== parent) return;
81 | if (!isGlueHandshakeEvent(event)) return;
82 | const [, payload] = event.data;
83 | if (payload === "syn-ack") {
84 | if (acked) close();
85 | else ack();
86 | }
87 | }
88 | }
89 |
--------------------------------------------------------------------------------
/src/glue/parent.ts:
--------------------------------------------------------------------------------
1 | import { defer } from "https://deno.land/x/pbkit@v0.0.45/core/runtime/async/observer.ts";
2 | import { Socket } from "../socket.ts";
3 | import { createIosSocket } from "../glue/ios.ts";
4 | import { createAndroidSocket } from "../glue/android.ts";
5 | import { createParentWindowSocket } from "../glue/parent-window.ts";
6 |
7 | export type UnsubscribeFn = () => void;
8 | export type SetSocketFn = (socket?: Socket, error?: Error) => void;
9 | export function subscribeParentSocket(set: SetSocketFn): UnsubscribeFn {
10 | let run = true;
11 | const unsubscribe = () => (run = false);
12 | const parent = globalThis.opener || globalThis.parent;
13 | if (parent && parent !== globalThis.self) {
14 | (async () => {
15 | while (run) {
16 | const closed = defer();
17 | try {
18 | const socket = await createParentWindowSocket({
19 | parent,
20 | parentWindowOrigin: "*",
21 | onClosed: () => closed.resolve(),
22 | });
23 | run && set(socket, undefined);
24 | } catch (error) {
25 | run && set(undefined, error);
26 | }
27 | await closed;
28 | run && set(undefined, undefined);
29 | }
30 | })();
31 | } else {
32 | getAndroidOrIosSocket().then(
33 | (socket) => run && set(socket),
34 | ).catch(
35 | (error) => run && set(undefined, error),
36 | );
37 | }
38 | return unsubscribe;
39 | }
40 |
41 | async function getAndroidOrIosSocket(): Promise {
42 | try {
43 | return await Promise.any([createAndroidSocket(), createIosSocket()]);
44 | } catch {}
45 | return;
46 | }
47 |
--------------------------------------------------------------------------------
/src/guest.ts:
--------------------------------------------------------------------------------
1 | import {
2 | createEventBuffer,
3 | EventBuffer,
4 | } from "https://deno.land/x/pbkit@v0.0.45/core/runtime/async/event-buffer.ts";
5 | import {
6 | defer,
7 | Deferred,
8 | } from "https://deno.land/x/pbkit@v0.0.45/core/runtime/async/observer.ts";
9 | import type { WrpChannel } from "./channel.ts";
10 | import { LazyMetadata, Metadata, resolveLazyMetadata } from "./metadata.ts";
11 |
12 | export interface WrpGuest {
13 | readonly availableMethods: Set;
14 | request(
15 | methodName: string,
16 | req: AsyncGenerator,
17 | metadata?: LazyMetadata,
18 | ): {
19 | res: AsyncGenerator;
20 | header: Promise;
21 | trailer: Promise;
22 | };
23 | }
24 |
25 | export class WrpError extends Error {
26 | constructor(public message: string) {
27 | super(message);
28 | }
29 | }
30 |
31 | export interface CreateWrpGuestConfig {
32 | channel: WrpChannel;
33 | onError?: (error: WrpError) => void;
34 | }
35 | export async function createWrpGuest(
36 | config: CreateWrpGuestConfig,
37 | ): Promise {
38 | const { channel } = config;
39 | let availableMethods: Set;
40 | const waitFirstHostInitialize = defer();
41 | interface Request {
42 | eventBuffer: EventBuffer;
43 | header: Deferred;
44 | trailer: Deferred;
45 | }
46 | const requests: { [reqId: string]: Request } = {};
47 | function finishRequest(reqId: string, trailerObject: Record) {
48 | const request = requests[reqId];
49 | request.trailer.resolve(trailerObject);
50 | if (trailerObject["wrp-status"] === "ok") {
51 | request.eventBuffer.finish();
52 | } else {
53 | const message = trailerObject["wrp-message"] || "";
54 | request.eventBuffer.error(new WrpError(message));
55 | }
56 | delete requests[reqId];
57 | }
58 | let reqIdCounter = 0;
59 | (async () => {
60 | for await (const { message } of channel.listen()) {
61 | if (message == null) continue;
62 | switch (message.field) {
63 | default:
64 | continue;
65 | case "HostInitialize": {
66 | const { value } = message;
67 | availableMethods = new Set(value.availableMethods);
68 | waitFirstHostInitialize.resolve();
69 | // handle host reconnection
70 | for (const reqId in requests) {
71 | finishRequest(reqId, {
72 | "wrp-status": "error",
73 | "wrp-message": "Host disconnected and reconnected.",
74 | });
75 | }
76 | continue;
77 | }
78 | case "HostError": {
79 | config.onError?.(new WrpError(message.value.message));
80 | continue;
81 | }
82 | case "HostResStart": {
83 | const { reqId, header } = message.value;
84 | requests[reqId]?.header.resolve(Object.fromEntries(header));
85 | continue;
86 | }
87 | case "HostResPayload": {
88 | const { reqId, payload } = message.value;
89 | requests[reqId]?.eventBuffer.push(payload);
90 | continue;
91 | }
92 | case "HostResFinish": {
93 | const { reqId, trailer } = message.value;
94 | if (!(reqId in requests)) continue;
95 | finishRequest(reqId, Object.fromEntries(trailer));
96 | continue;
97 | }
98 | }
99 | }
100 | })();
101 | await waitFirstHostInitialize;
102 | return {
103 | get availableMethods() {
104 | return availableMethods;
105 | },
106 | request(methodName, req, lazyMetadata) {
107 | const reqId = `${++reqIdCounter}`;
108 | const eventBuffer = createEventBuffer({
109 | onDrainEnd() {
110 | channel.send({
111 | message: {
112 | field: "GuestResFinish",
113 | value: { reqId },
114 | },
115 | });
116 | },
117 | });
118 | const header = defer();
119 | const trailer = defer();
120 | const res = eventBuffer.drain();
121 | requests[reqId] = { eventBuffer, header, trailer };
122 | (async () => {
123 | try {
124 | const metadata = new Map(Object.entries(
125 | await resolveLazyMetadata(lazyMetadata),
126 | ));
127 | channel.send({
128 | message: {
129 | field: "GuestReqStart",
130 | value: { reqId, methodName, metadata },
131 | },
132 | });
133 | for await (const payload of req) {
134 | channel.send({
135 | message: {
136 | field: "GuestReqPayload",
137 | value: { reqId, payload },
138 | },
139 | });
140 | }
141 | } finally {
142 | channel.send({
143 | message: {
144 | field: "GuestReqFinish",
145 | value: { reqId },
146 | },
147 | });
148 | }
149 | })();
150 | return { res, header, trailer };
151 | },
152 | };
153 | }
154 |
--------------------------------------------------------------------------------
/src/host.ts:
--------------------------------------------------------------------------------
1 | import {
2 | createEventBuffer,
3 | EventBuffer,
4 | } from "https://deno.land/x/pbkit@v0.0.45/core/runtime/async/event-buffer.ts";
5 | import {
6 | createEventEmitter,
7 | EventEmitter,
8 | } from "https://deno.land/x/pbkit@v0.0.45/core/runtime/async/event-emitter.ts";
9 | import type { WrpChannel } from "./channel.ts";
10 | import type { Metadata } from "./metadata.ts";
11 |
12 | export interface WrpHost {
13 | listen(): AsyncGenerator;
14 | }
15 | export interface WrpRequest extends EventEmitter {
16 | methodName: string;
17 | metadata: Metadata;
18 | req: AsyncGenerator;
19 | sendHeader(value: Metadata): void;
20 | sendPayload(value: Uint8Array): void;
21 | sendTrailer(value: Metadata): void;
22 | }
23 | interface WrpRequestEvents {
24 | "cancel-response": void;
25 | "close": void;
26 | }
27 |
28 | interface RequestHandlingState {
29 | reqId: string;
30 | request: WrpRequest;
31 | eventBuffer: EventBuffer;
32 | reqFinished: boolean;
33 | resFinished: boolean;
34 | }
35 | interface RequestHandlingStateTable {
36 | [reqId: string]: RequestHandlingState;
37 | }
38 |
39 | export interface CreateWrpHostConfig {
40 | channel: WrpChannel;
41 | availableMethods: Set;
42 | }
43 | export async function createWrpHost(
44 | config: CreateWrpHostConfig,
45 | ): Promise {
46 | const { channel, availableMethods } = config;
47 | const states: RequestHandlingStateTable = {};
48 | function tryForgetState(state: RequestHandlingState): void {
49 | if (!state.reqFinished) return;
50 | if (!state.resFinished) return;
51 | delete states[state.reqId];
52 | }
53 | await channel.send({
54 | message: {
55 | field: "HostInitialize",
56 | value: { availableMethods: Array.from(availableMethods) },
57 | },
58 | });
59 | return {
60 | async *listen() {
61 | try {
62 | for await (const { message } of channel.listen()) {
63 | if (message == null) {
64 | channel.send({
65 | message: {
66 | field: "HostError",
67 | value: { message: "Received null message" },
68 | },
69 | });
70 | continue;
71 | }
72 | switch (message.field) {
73 | case "GuestReqStart": {
74 | const { reqId, methodName, metadata } = message.value;
75 | const eventBuffer = createEventBuffer({
76 | onDrainEnd() {
77 | state.reqFinished = true;
78 | tryForgetState(state);
79 | },
80 | });
81 | const request = createWrpRequest({
82 | methodName,
83 | channel,
84 | reqId,
85 | req: eventBuffer.drain(),
86 | metadata: Object.fromEntries(metadata),
87 | });
88 | const state: RequestHandlingState = states[reqId] = {
89 | reqId,
90 | request,
91 | eventBuffer,
92 | reqFinished: false,
93 | resFinished: false,
94 | };
95 | yield request;
96 | continue;
97 | }
98 | case "GuestReqPayload": {
99 | const { reqId, payload } = message.value;
100 | processMessage(channel, states, reqId, (state) => {
101 | state.eventBuffer.push(payload);
102 | });
103 | continue;
104 | }
105 | case "GuestReqFinish": {
106 | const { reqId } = message.value;
107 | processMessage(channel, states, reqId, (state) => {
108 | state.eventBuffer.finish();
109 | state.reqFinished = true;
110 | tryForgetState(state);
111 | });
112 | continue;
113 | }
114 | case "GuestResFinish": {
115 | const { reqId } = message.value;
116 | processMessage(channel, states, reqId, (state) => {
117 | state.request.emit("cancel-response", undefined);
118 | state.resFinished = true;
119 | tryForgetState(state);
120 | });
121 | continue;
122 | }
123 | }
124 | }
125 | } finally {
126 | for (const reqId in states) {
127 | states[reqId].request.emit("close", undefined);
128 | delete states[reqId];
129 | }
130 | }
131 | },
132 | };
133 | }
134 |
135 | function processMessage(
136 | channel: WrpChannel,
137 | states: RequestHandlingStateTable,
138 | reqId: string,
139 | fn: (request: RequestHandlingState) => void,
140 | ): void {
141 | if (reqId in states) {
142 | fn(states[reqId]);
143 | } else {
144 | channel.send({
145 | message: {
146 | field: "HostError",
147 | value: {
148 | message:
149 | `Received unexpected request finish for { reqId: "${reqId}" }`,
150 | },
151 | },
152 | });
153 | }
154 | }
155 |
156 | interface CreateWrpRequestConfig {
157 | methodName: string;
158 | channel: WrpChannel;
159 | reqId: string;
160 | req: AsyncGenerator;
161 | metadata: Metadata;
162 | }
163 | function createWrpRequest(config: CreateWrpRequestConfig): WrpRequest {
164 | const { methodName, channel, reqId, req, metadata } = config;
165 | return {
166 | ...createEventEmitter(),
167 | methodName,
168 | metadata,
169 | req,
170 | sendHeader(header) {
171 | channel.send({
172 | message: {
173 | field: "HostResStart",
174 | value: { reqId, header: new Map(Object.entries(header)) },
175 | },
176 | });
177 | },
178 | sendPayload(payload) {
179 | channel.send({
180 | message: {
181 | field: "HostResPayload",
182 | value: { reqId, payload },
183 | },
184 | });
185 | },
186 | sendTrailer(trailer) {
187 | trailer["wrp-status"] ||= "ok";
188 | trailer["wrp-message"] ||= "";
189 | channel.send({
190 | message: {
191 | field: "HostResFinish",
192 | value: { reqId, trailer: new Map(Object.entries(trailer)) },
193 | },
194 | });
195 | },
196 | };
197 | }
198 |
--------------------------------------------------------------------------------
/src/jotai/iframe.ts:
--------------------------------------------------------------------------------
1 | import { useEffect } from "react";
2 | import { atom, PrimitiveAtom, useSetAtom } from "jotai";
3 | import {
4 | createWrpAtomSetFromSourceChannelAtom,
5 | PrimitiveSocketAtom,
6 | WrpAtomSet,
7 | } from "./index.ts";
8 | import { Socket } from "../socket.ts";
9 | import {
10 | createWrpChannel as createWrpChannelFn,
11 | WrpChannel,
12 | } from "../channel.ts";
13 | import {
14 | default as useWrpIframeSocket,
15 | UseWrpIframeSocketResult,
16 | } from "../react/useWrpIframeSocket.ts";
17 |
18 | export function useIframeWrpSocketAtomUpdateEffect(
19 | socketAtom: PrimitiveSocketAtom,
20 | ): UseWrpIframeSocketResult {
21 | const setSocket = useSetAtom(socketAtom);
22 | const useWrpIframeSocketResult = useWrpIframeSocket();
23 | const { socket } = useWrpIframeSocketResult;
24 | useEffect(() => {
25 | if (!socket) return;
26 | setSocket(socket);
27 | }, [socket]);
28 | return useWrpIframeSocketResult;
29 | }
30 |
31 | export function useIframeWrpAtomSetUpdateEffect(
32 | primitiveWrpAtomSetAtom: PrimitiveAtom,
33 | createWrpChannel: (socket: Socket) => WrpChannel = createWrpChannelFn,
34 | ): UseWrpIframeSocketResult {
35 | const setIframeAtomSet = useSetAtom(primitiveWrpAtomSetAtom);
36 | const useWrpIframeSocketResult = useWrpIframeSocket();
37 | const { socket } = useWrpIframeSocketResult;
38 | useEffect(() => {
39 | if (!socket) return;
40 | const channelAtom = atom(createWrpChannel(socket));
41 | setIframeAtomSet(createWrpAtomSetFromSourceChannelAtom(channelAtom));
42 | }, [socket]);
43 | return useWrpIframeSocketResult;
44 | }
45 |
--------------------------------------------------------------------------------
/src/jotai/index.ts:
--------------------------------------------------------------------------------
1 | import { Atom, atom, PrimitiveAtom } from "jotai";
2 | import { selectAtom } from "jotai/utils";
3 | import type { RpcClientImpl } from "https://deno.land/x/pbkit@v0.0.45/core/runtime/rpc.ts";
4 | import { Socket } from "../socket.ts";
5 | import { createWrpChannel, WrpChannel } from "../channel.ts";
6 | import { createWrpClientImpl } from "../rpc/client.ts";
7 | import { WrpGuest } from "../guest.ts";
8 | import tee from "../tee.ts";
9 |
10 | export type SocketAtom = PrimitiveSocketAtom | AsyncSocketAtom;
11 | export type ChannelAtom = Atom;
12 | export type GuestAtom = Atom | undefined>;
13 | export type ClientImplAtom = Atom;
14 |
15 | export type PrimitiveSocketAtom = PrimitiveAtom;
16 | export type AsyncSocketAtom = Atom>;
17 |
18 | export interface WrpAtomSet {
19 | channelAtom: ChannelAtom;
20 | guestAtom: GuestAtom;
21 | clientImplAtom: ClientImplAtom;
22 | }
23 | export function createWrpAtomSet(socketAtom: SocketAtom): WrpAtomSet {
24 | const channelAtom: ChannelAtom = atom((get) => {
25 | const socket = get(socketAtom);
26 | if (!socket) return;
27 | return createWrpChannel(socket);
28 | });
29 | return createWrpAtomSetFromSourceChannelAtom(channelAtom);
30 | }
31 |
32 | export function createWrpAtomSetFromSourceChannelAtom(
33 | sourceChannelAtom: ChannelAtom,
34 | ): WrpAtomSet {
35 | interface ChannelAndGuest {
36 | channel: WrpChannel;
37 | guest: Promise;
38 | }
39 | const channelAndGuestAtom = atom>(
40 | async (get) => {
41 | const sourceChannel = get(sourceChannelAtom);
42 | if (!sourceChannel) return;
43 | return tee(sourceChannel);
44 | },
45 | );
46 | const channelAtom = selectAtom(
47 | channelAndGuestAtom,
48 | (cag) => cag?.channel,
49 | );
50 | const guestAtom = selectAtom(
51 | channelAndGuestAtom,
52 | (cag) => cag?.guest,
53 | );
54 | const clientImplAtom = atom((get) => {
55 | const guest = get(guestAtom);
56 | if (!guest) return;
57 | return createWrpClientImpl({ guest: guest });
58 | });
59 | return { channelAtom, guestAtom, clientImplAtom };
60 | }
61 |
--------------------------------------------------------------------------------
/src/jotai/parent.ts:
--------------------------------------------------------------------------------
1 | import { atom, useAtomValue, useSetAtom } from "jotai";
2 | import type { RpcClientImpl } from "https://deno.land/x/pbkit@v0.0.45/core/runtime/rpc.ts";
3 | import { Socket } from "../socket.ts";
4 | import { WrpChannel } from "../channel.ts";
5 | import { WrpGuest } from "../guest.ts";
6 | import useOnceEffect from "../react/useOnceEffect.ts";
7 | import { subscribeParentSocket } from "../glue/parent.ts";
8 | import {
9 | ChannelAtom,
10 | ClientImplAtom,
11 | createWrpAtomSet,
12 | GuestAtom,
13 | PrimitiveSocketAtom,
14 | WrpAtomSet,
15 | } from "./index.ts";
16 |
17 | /**
18 | * Use it on root of your react application
19 | */
20 | export function useInitParentSocketEffect() {
21 | const setSocket = useSetAtom(socketAtom);
22 | useOnceEffect(() => subscribeParentSocket(setSocket));
23 | }
24 |
25 | export const socketAtom: PrimitiveSocketAtom = atom(
26 | undefined,
27 | );
28 |
29 | const wrpAtomSet: WrpAtomSet = createWrpAtomSet(socketAtom);
30 |
31 | export const channelAtom: ChannelAtom = wrpAtomSet.channelAtom;
32 | export function useChannel(): WrpChannel | undefined {
33 | return useAtomValue(wrpAtomSet.channelAtom);
34 | }
35 |
36 | export const guestAtom: GuestAtom = wrpAtomSet.guestAtom;
37 | export function useGuest(): WrpGuest | undefined {
38 | return useAtomValue(wrpAtomSet.guestAtom);
39 | }
40 |
41 | export const clientImplAtom: ClientImplAtom = wrpAtomSet.clientImplAtom;
42 | export function useClientImpl(): RpcClientImpl | undefined {
43 | return useAtomValue(wrpAtomSet.clientImplAtom);
44 | }
45 |
--------------------------------------------------------------------------------
/src/jotai/pwasa.ts:
--------------------------------------------------------------------------------
1 | import { atom, PrimitiveAtom, useAtomValue } from "jotai";
2 | import type { RpcClientImpl } from "https://deno.land/x/pbkit@v0.0.45/core/runtime/rpc.ts";
3 | import { WrpChannel } from "../channel.ts";
4 | import { WrpGuest } from "../guest.ts";
5 | import { WrpAtomSet } from "./index.ts";
6 |
7 | export type PrimitiveWrpAtomSetAtom = PrimitiveAtom;
8 | export function createPrimitiveWrpAtomSetAtom(): PrimitiveWrpAtomSetAtom {
9 | return atom({
10 | channelAtom: atom(undefined),
11 | guestAtom: atom(undefined),
12 | clientImplAtom: atom(undefined),
13 | });
14 | }
15 | export function useChannel(
16 | primitiveWrpAtomSetAtom: PrimitiveAtom,
17 | ): WrpChannel | undefined {
18 | const wrpAtomSet = useAtomValue(primitiveWrpAtomSetAtom);
19 | const channel = useAtomValue(wrpAtomSet.channelAtom);
20 | return channel;
21 | }
22 | export function useGuest(
23 | primitiveWrpAtomSetAtom: PrimitiveAtom,
24 | ): WrpGuest | undefined {
25 | const wrpAtomSet = useAtomValue(primitiveWrpAtomSetAtom);
26 | const guest = useAtomValue(wrpAtomSet.guestAtom);
27 | return guest;
28 | }
29 | export function useClientImpl(
30 | primitiveWrpAtomSetAtom: PrimitiveAtom,
31 | ): RpcClientImpl | undefined {
32 | const wrpAtomSet = useAtomValue(primitiveWrpAtomSetAtom);
33 | const clientImpl = useAtomValue(wrpAtomSet.clientImplAtom);
34 | return clientImpl;
35 | }
36 |
--------------------------------------------------------------------------------
/src/metadata.ts:
--------------------------------------------------------------------------------
1 | export type Metadata = Record;
2 |
3 | export interface LazyMetadata {
4 | [key: string]:
5 | | undefined
6 | | string
7 | | Promise
8 | | (() => string | undefined)
9 | | (() => Promise);
10 | }
11 |
12 | export async function resolveLazyMetadata(
13 | lazyMetadata?: LazyMetadata,
14 | ): Promise {
15 | if (!lazyMetadata) return {};
16 | return Object.fromEntries(
17 | await Promise.all(
18 | dropNullishEntries(Object.entries(lazyMetadata)).map(
19 | async (entry) => {
20 | const [key, value] = entry;
21 | if (value instanceof Promise) [key, await value] as typeof entry;
22 | if (typeof value !== "function") return entry;
23 | else return [key, await value()] as typeof entry;
24 | },
25 | ),
26 | ).then(dropNullishEntries),
27 | );
28 | function dropNullishEntries(entries: [string, any][]) {
29 | return entries.filter((entry) => entry[1] != null);
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/misc.ts:
--------------------------------------------------------------------------------
1 | export function chain Promise>(fn: T): T {
2 | let prev: Promise | undefined;
3 | return function _fn(...args) {
4 | const curr = (prev || Promise.resolve())
5 | .then(() => fn(...args))
6 | .then((result) => {
7 | if (prev === curr) prev = undefined;
8 | return result;
9 | });
10 | return (prev = curr);
11 | } as T;
12 | }
13 |
--------------------------------------------------------------------------------
/src/react/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./useWrpClientImpl.ts";
2 | export { default as useWrpClientImpl } from "./useWrpClientImpl.ts";
3 | export * from "./useWrpIframeSocket.ts";
4 | export { default as useWrpIframeSocket } from "./useWrpIframeSocket.ts";
5 | export * from "./useWrpParentSocket.ts";
6 | export { default as useWrpParentSocket } from "./useWrpParentSocket.ts";
7 | export * as server from "./server.ts";
8 |
--------------------------------------------------------------------------------
/src/react/server.ts:
--------------------------------------------------------------------------------
1 | import {
2 | MethodDescriptor,
3 | MethodImplHandlerReq,
4 | } from "https://deno.land/x/pbkit@v0.0.45/core/runtime/rpc.ts";
5 | import { first } from "https://deno.land/x/pbkit@v0.0.45/core/runtime/async/async-generator.ts";
6 | import { createEventBuffer } from "https://deno.land/x/pbkit@v0.0.45/core/runtime/async/event-buffer.ts";
7 | import {
8 | createEventEmitter,
9 | EventEmitter,
10 | } from "https://deno.land/x/pbkit@v0.0.45/core/runtime/async/event-emitter.ts";
11 | import { useEffect, useRef } from "react";
12 | import { WrpChannel } from "../channel.ts";
13 | import { createWrpHost, WrpRequest } from "../host.ts";
14 | import { createWrpServer, createWrpServerImplBuilder } from "../rpc/server.ts";
15 |
16 | export function useWrpServer>(
17 | channel: WrpChannel | undefined,
18 | state: TState,
19 | ...rpcs: MethodImpl>[]
20 | ) {
21 | const ref = useRef[ | undefined>(undefined);
22 | useEffect(() => {
23 | if (!channel) return;
24 | if (!ref.current) ref.current = createRef();
25 | const getState = () => ref.current?.state!;
26 | const changes = ref.current.changes;
27 | (async () => {
28 | const host = await createWrpHost({
29 | channel,
30 | availableMethods: new Set(
31 | rpcs.map(({ methodDescriptor: { service, methodName } }) =>
32 | `${service.serviceName}/${methodName}`
33 | ),
34 | ),
35 | });
36 | const builder = createWrpServerImplBuilder();
37 | for (const { methodDescriptor, handler } of rpcs) {
38 | builder.register(
39 | methodDescriptor,
40 | async (req, res) => {
41 | const { requestStream, responseStream } = methodDescriptor;
42 | const { teardown, callTeardown } = getTeardown(req);
43 | const stateChanges = new Proxy({}, {
44 | get(_, key) {
45 | const eb = createEventBuffer();
46 | teardown(changes.on(key as keyof TState, eb.push));
47 | teardown(eb.finish);
48 | return eb.drain();
49 | },
50 | }) as StateChanges;
51 | try {
52 | res.header({});
53 | const _req = requestStream ? req.messages : first(req.messages);
54 | const _res = handler({
55 | req: _req,
56 | getState,
57 | stateChanges,
58 | teardown,
59 | });
60 | if (responseStream) {
61 | for await (const value of _res as AsyncGenerator) {
62 | res.send(value);
63 | }
64 | } else {
65 | res.send(await _res);
66 | }
67 | res.end({});
68 | } catch (err) {
69 | res.end({
70 | "wrp-status": "error",
71 | "wrp-message": err?.message ?? "",
72 | });
73 | } finally {
74 | callTeardown();
75 | }
76 | },
77 | );
78 | }
79 | builder.finish();
80 | const methods = builder.drain();
81 | const server = await createWrpServer({ host, methods });
82 | server.listen();
83 | })();
84 | }, [channel]);
85 | useEffect(() => {
86 | if (!ref.current) ref.current = createRef();
87 | const prev = { ...ref.current.state };
88 | ref.current.state = state;
89 | for (const key of new Set([...Object.keys(prev), ...Object.keys(state)])) {
90 | if (prev[key] !== state[key]) {
91 | ref.current.changes.emit(key, state[key]);
92 | }
93 | }
94 | }, [state]);
95 | }
96 |
97 | interface Ref {
98 | state: TState;
99 | changes: EventEmitter;
100 | }
101 | function createRef(): Ref {
102 | return {
103 | state: undefined as unknown as TState,
104 | changes: createEventEmitter(),
105 | };
106 | }
107 |
108 | export function rpc<
109 | TState extends Record,
110 | TMethodDescriptor extends MethodDescriptor,
111 | >(
112 | methodDescriptor: TMethodDescriptor,
113 | handler: RpcHandler<
114 | TState,
115 | TMethodDescriptor["requestStream"],
116 | TMethodDescriptor["responseStream"],
117 | Parameters[0],
118 | Parameters[0]
119 | >,
120 | ): MethodImpl {
121 | return { methodDescriptor, handler };
122 | }
123 |
124 | export type GetStateFn> = () => TState;
125 |
126 | export type StateChanges> = {
127 | [key in keyof TState]: AsyncGenerator;
128 | };
129 |
130 | export type TeardownFn = (handler: () => void) => void;
131 |
132 | interface GetTeardownResult {
133 | teardown: TeardownFn;
134 | callTeardown: () => void;
135 | }
136 | function getTeardown(
137 | req: MethodImplHandlerReq,
138 | ): GetTeardownResult {
139 | const handlers: (() => void)[] = [];
140 | const teardown: TeardownFn = (handler) => handlers.push(handler);
141 | const o1 = req.metadata?.on("cancel-response", callTeardown);
142 | const o2 = req.metadata?.on("close", callTeardown);
143 | const offAll = () => (o1?.(), o2?.());
144 | function callTeardown() {
145 | offAll();
146 | for (const handler of handlers) handler();
147 | handlers.length = 0;
148 | }
149 | return { teardown, callTeardown };
150 | }
151 |
152 | export interface MethodImpl<
153 | TState,
154 | TMethodDescriptor extends MethodDescriptor,
155 | > {
156 | methodDescriptor: TMethodDescriptor;
157 | handler: RpcHandler<
158 | TState,
159 | TMethodDescriptor["requestStream"],
160 | TMethodDescriptor["responseStream"],
161 | Parameters[0],
162 | Parameters[0]
163 | >;
164 | }
165 |
166 | export interface RpcHandler<
167 | TState extends Record,
168 | TReqStream extends boolean,
169 | TResStream extends boolean,
170 | TReq,
171 | TRes,
172 | > {
173 | (param: {
174 | req: RpcReq;
175 | getState: GetStateFn;
176 | stateChanges: StateChanges;
177 | teardown: TeardownFn;
178 | }): RpcRes;
179 | }
180 |
181 | type RpcReq = IsStream extends true
182 | ? AsyncGenerator
183 | : T;
184 | type RpcRes = IsStream extends true
185 | ? AsyncGenerator
186 | : Promise;
187 |
--------------------------------------------------------------------------------
/src/react/useOnceEffect.ts:
--------------------------------------------------------------------------------
1 | import { useEffect, useRef } from "react";
2 |
3 | /**
4 | * Guaranteed to be called only once in a component's lifecycle.
5 | * It called only once, even in strict mode.
6 | */
7 | const useOnceEffect: typeof useEffect = (effect) => {
8 | const effectHasFiredRef = useRef();
9 | useEffect(() => {
10 | if (effectHasFiredRef.current) return;
11 | else effectHasFiredRef.current = true;
12 | return effect();
13 | }, []);
14 | };
15 |
16 | export default useOnceEffect;
17 |
--------------------------------------------------------------------------------
/src/react/useWrpClientImpl.ts:
--------------------------------------------------------------------------------
1 | import type { RpcClientImpl } from "https://deno.land/x/pbkit@v0.0.45/core/runtime/rpc.ts";
2 | import { useEffect, useState } from "react";
3 | import { WrpChannel } from "../channel.ts";
4 | import { createWrpGuest } from "../guest.ts";
5 | import { createWrpClientImpl } from "../rpc/client.ts";
6 |
7 | /**
8 | * @deprecated use `@pbkit/wrp-jotai` instead
9 | */
10 | export default function useWrpClientImpl(
11 | channel: WrpChannel | undefined,
12 | ): ReturnType | undefined {
13 | const [
14 | clientImpl,
15 | setClientImpl,
16 | ] = useState(undefined);
17 | useEffect(() => {
18 | if (!channel) return;
19 | createWrpGuest({ channel }).then(
20 | (guest) => setClientImpl(() => createWrpClientImpl({ guest })),
21 | );
22 | }, [channel]);
23 | return clientImpl;
24 | }
25 |
--------------------------------------------------------------------------------
/src/react/useWrpIframeSocket.ts:
--------------------------------------------------------------------------------
1 | import { defer } from "https://deno.land/x/pbkit@v0.0.45/core/runtime/async/observer.ts";
2 | import { Ref, useRef, useState } from "react";
3 | import { Closer, Socket } from "../socket.ts";
4 | import { createIframeSocket } from "../glue/iframe.ts";
5 | import useOnceEffect from "./useOnceEffect.ts";
6 |
7 | export interface UseWrpIframeSocketResult {
8 | iframeRef: Ref;
9 | socket: Socket | undefined;
10 | }
11 | export default function useWrpIframeSocket(): UseWrpIframeSocketResult {
12 | const iframeRef = useRef(null);
13 | const [socket, setSocket] = useState(undefined);
14 | useOnceEffect(() => {
15 | let socket: Closer & Socket;
16 | let unmounted = false;
17 | let waitForReconnect = defer();
18 | const iframeElement = iframeRef.current!;
19 | (async () => { // reconnection loop
20 | while (true) {
21 | if (unmounted) return;
22 | try {
23 | socket = await createIframeSocket({
24 | iframeElement,
25 | iframeOrigin: "*",
26 | onClosed: tryReconnect,
27 | });
28 | setSocket(socket);
29 | await waitForReconnect;
30 | } catch { /* ignore handshake timeout */ }
31 | }
32 | })();
33 | return () => {
34 | unmounted = true;
35 | tryReconnect();
36 | };
37 | function tryReconnect() {
38 | waitForReconnect.resolve();
39 | waitForReconnect = defer();
40 | }
41 | });
42 | return { iframeRef, socket };
43 | }
44 |
--------------------------------------------------------------------------------
/src/react/useWrpParentSocket.ts:
--------------------------------------------------------------------------------
1 | import { useState } from "react";
2 | import { Socket } from "../socket.ts";
3 | import useOnceEffect from "../react/useOnceEffect.ts";
4 | import { subscribeParentSocket } from "../glue/parent.ts";
5 |
6 | export interface UseWrpParentSocketResult {
7 | socket: Socket | undefined;
8 | error: Error | undefined;
9 | }
10 | /**
11 | * @deprecated use `@pbkit/wrp-jotai/parent` instead
12 | */
13 | export default function useWrpParentSocket(): UseWrpParentSocketResult {
14 | const [socket, setSocket] = useState(undefined);
15 | const [error, setError] = useState(undefined);
16 | useOnceEffect(() =>
17 | subscribeParentSocket((socket, error) => {
18 | setSocket(socket);
19 | setError(error);
20 | })
21 | );
22 | return { socket, error };
23 | }
24 |
--------------------------------------------------------------------------------
/src/rpc/client.ts:
--------------------------------------------------------------------------------
1 | import type { RpcClientImpl } from "https://deno.land/x/pbkit@v0.0.45/core/runtime/rpc.ts";
2 | import type { WrpGuest } from "../guest.ts";
3 | import type { LazyMetadata, Metadata } from "../metadata.ts";
4 | import { getMethodName, mapAsyncGenerator } from "./misc.ts";
5 |
6 | export interface CreateWrpClientImplConfig {
7 | guest: WrpGuest;
8 | metadata?: LazyMetadata;
9 | }
10 |
11 | export class WrpClientError extends Error {
12 | constructor(message: string) {
13 | super(message);
14 | }
15 | }
16 |
17 | export function createWrpClientImpl(
18 | config: CreateWrpClientImplConfig,
19 | ): RpcClientImpl {
20 | return (methodDescriptor) => {
21 | const { requestType, responseType } = methodDescriptor;
22 | const methodName = getMethodName(methodDescriptor);
23 | if (!config.guest.availableMethods.has(methodName)) {
24 | return () => {
25 | throw new WrpClientError("Method not available");
26 | };
27 | }
28 | return (req, metadata) => {
29 | const { res, header, trailer } = config.guest.request(
30 | methodName,
31 | mapAsyncGenerator(req, requestType.serializeBinary),
32 | { ...config.metadata, ...metadata },
33 | );
34 | const _res = mapAsyncGenerator(res, responseType.deserializeBinary);
35 | return [_res, header, trailer];
36 | };
37 | };
38 | }
39 |
--------------------------------------------------------------------------------
/src/rpc/misc.ts:
--------------------------------------------------------------------------------
1 | import type { MethodDescriptor } from "https://deno.land/x/pbkit@v0.0.45/core/runtime/rpc.ts";
2 |
3 | export async function* mapAsyncGenerator(
4 | asyncGenerator: AsyncGenerator,
5 | fn: (value: T) => U,
6 | ): AsyncGenerator {
7 | for await (const value of asyncGenerator) yield fn(value);
8 | }
9 |
10 | export function getMethodName<
11 | TMethodName extends string,
12 | TServiceName extends string,
13 | TDescriptor extends MethodDescriptor,
14 | >(descriptor: TDescriptor): `${TServiceName}/${TMethodName}` {
15 | return `${descriptor.service.serviceName}/${descriptor.methodName}`;
16 | }
17 |
--------------------------------------------------------------------------------
/src/rpc/server.ts:
--------------------------------------------------------------------------------
1 | import {
2 | createServerImplBuilder,
3 | Method,
4 | } from "https://deno.land/x/pbkit@v0.0.45/core/runtime/rpc.ts";
5 | import type { WrpHost, WrpRequest } from "../host.ts";
6 | import type { Metadata } from "../metadata.ts";
7 | import { getMethodName, mapAsyncGenerator } from "./misc.ts";
8 |
9 | export interface CreateWrpServerConfig {
10 | host: WrpHost;
11 | methods: AsyncGenerator>;
12 | }
13 | export async function createWrpServer(config: CreateWrpServerConfig) {
14 | const methods: {
15 | [methodName: string]: Method;
16 | } = {};
17 | for await (const method of config.methods) {
18 | const methodName = getMethodName(method[0]);
19 | methods[methodName] = method;
20 | }
21 | return {
22 | async listen() {
23 | for await (const request of config.host.listen()) {
24 | const method = methods[request.methodName];
25 | const sendHeader = doOnce(request.sendHeader);
26 | const sendTrailer = doOnce(request.sendTrailer);
27 | if (!method) {
28 | sendHeader({});
29 | sendTrailer({
30 | "wrp-status": "error",
31 | "wrp-message": `Method not found: ${request.methodName}`,
32 | });
33 | continue;
34 | }
35 | const [methodDescriptor, methodImpl] = method;
36 | const { req } = request;
37 | const { requestType, responseType } = methodDescriptor;
38 | const [res, header, trailer] = methodImpl(
39 | mapAsyncGenerator(req, requestType.deserializeBinary),
40 | request,
41 | );
42 | (async function () {
43 | let _header: Metadata = {};
44 | try {
45 | sendHeader(_header = await header);
46 | for await (const value of res) {
47 | request.sendPayload(responseType.serializeBinary(value));
48 | }
49 | sendTrailer(await trailer);
50 | } catch (err) {
51 | sendHeader(_header);
52 | sendTrailer({
53 | "wrp-status": "error",
54 | "wrp-message": err?.message ?? "",
55 | });
56 | }
57 | })();
58 | }
59 | },
60 | };
61 | }
62 |
63 | export function createWrpServerImplBuilder() {
64 | return createServerImplBuilder();
65 | }
66 |
67 | function doOnce any>(fn: T) {
68 | let flag = false;
69 | return (...args: Parameters) => {
70 | if (flag) return;
71 | flag = true;
72 | return fn.apply(null, args);
73 | };
74 | }
75 |
--------------------------------------------------------------------------------
/src/socket.ts:
--------------------------------------------------------------------------------
1 | export type Socket = Reader & Writer;
2 |
3 | // same as `Deno.Reader`
4 | export interface Reader {
5 | read(p: Uint8Array): Promise;
6 | }
7 |
8 | // same as `Deno.Writer`
9 | export interface Writer {
10 | write(p: Uint8Array): Promise;
11 | }
12 |
13 | // same as `Deno.Closer`
14 | export interface Closer {
15 | close(): void;
16 | }
17 |
--------------------------------------------------------------------------------
/src/tee.ts:
--------------------------------------------------------------------------------
1 | import { Type as WrpMessage } from "./generated/messages/pbkit/wrp/WrpMessage.ts";
2 | import { WrpChannel } from "./channel.ts";
3 | import { createWrpGuest, WrpGuest } from "./guest.ts";
4 |
5 | export interface ChannelAndGuest {
6 | channel: WrpChannel;
7 | guest: Promise;
8 | }
9 | export default function tee(sourceChannel: WrpChannel): ChannelAndGuest {
10 | const listeners: ((message?: WrpMessage) => void)[] = [];
11 | const guest = createWrpGuest({
12 | channel: {
13 | ...sourceChannel,
14 | async *listen() {
15 | for await (const message of sourceChannel.listen()) {
16 | yield message;
17 | for (const listener of listeners) listener(message);
18 | listeners.length = 0;
19 | }
20 | },
21 | },
22 | });
23 | const channel: WrpChannel = {
24 | ...sourceChannel,
25 | async *listen() {
26 | while (true) {
27 | const message = await new Promise(
28 | (resolve) => listeners.push(resolve),
29 | );
30 | if (!message) break;
31 | yield message;
32 | }
33 | },
34 | };
35 | return { channel, guest };
36 | }
37 |
--------------------------------------------------------------------------------
/src/wrp.proto:
--------------------------------------------------------------------------------
1 | syntax = "proto3";
2 | package pbkit.wrp;
3 |
4 | message WrpMessage {
5 | oneof message {
6 | WrpHostMessage_Initialize Host_Initialize = 1;
7 | WrpHostMessage_Error Host_Error = 2;
8 | WrpHostMessage_ResStart Host_ResStart = 3;
9 | WrpHostMessage_ResPayload Host_ResPayload = 4;
10 | WrpHostMessage_ResFinish Host_ResFinish = 5;
11 | WrpGuestMessage_ReqStart Guest_ReqStart = 6;
12 | WrpGuestMessage_ReqPayload Guest_ReqPayload = 7;
13 | WrpGuestMessage_ReqFinish Guest_ReqFinish = 8;
14 | WrpGuestMessage_ResFinish Guest_ResFinish = 9;
15 | }
16 | }
17 |
18 | message WrpHostMessage_Initialize {
19 | repeated string available_methods = 1;
20 | }
21 | message WrpHostMessage_Error {
22 | string message = 1;
23 | }
24 | message WrpHostMessage_ResStart {
25 | string req_id = 1;
26 | map header = 2;
27 | }
28 | message WrpHostMessage_ResPayload {
29 | string req_id = 1;
30 | bytes payload = 2;
31 | }
32 | message WrpHostMessage_ResFinish {
33 | string req_id = 1;
34 | map trailer = 2;
35 | }
36 |
37 | message WrpGuestMessage_ReqStart {
38 | string req_id = 1;
39 | string method_name = 2;
40 | map metadata = 3;
41 | }
42 | message WrpGuestMessage_ReqPayload {
43 | string req_id = 1;
44 | bytes payload = 2;
45 | }
46 | message WrpGuestMessage_ReqFinish {
47 | string req_id = 1;
48 | }
49 | message WrpGuestMessage_ResFinish {
50 | string req_id = 1;
51 | }
52 |
--------------------------------------------------------------------------------
]