swarpc 0.1.1 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -7
- package/dist/swarpc.d.ts +13 -54
- package/dist/swarpc.d.ts.map +1 -1
- package/dist/typings.d.ts +18 -0
- package/dist/typings.d.ts.map +1 -0
- package/package.json +4 -2
- package/src/swarpc.js +116 -62
- package/src/typings.js +42 -0
package/README.md
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
<div align=center>
|
|
2
|
-
<h1>
|
|
2
|
+
<h1>
|
|
3
|
+
<img src="./logo.svg" alt="sw&rpc" />
|
|
4
|
+
</h1>
|
|
3
5
|
|
|
4
6
|
RPC for Service Workers -- move that heavy computation off of your UI thread!
|
|
5
7
|
|
|
@@ -10,14 +12,15 @@ RPC for Service Workers -- move that heavy computation off of your UI thread!
|
|
|
10
12
|
## Installation
|
|
11
13
|
|
|
12
14
|
```bash
|
|
13
|
-
npm add swarpc
|
|
15
|
+
npm add swarpc arktype
|
|
14
16
|
```
|
|
15
17
|
|
|
16
18
|
## Usage
|
|
17
19
|
|
|
18
20
|
### 1. Declare your procedures in a shared file
|
|
19
21
|
|
|
20
|
-
```
|
|
22
|
+
```typescript
|
|
23
|
+
import type { ProceduresMap } from "swarpc"
|
|
21
24
|
import { type } from "arktype"
|
|
22
25
|
|
|
23
26
|
export const procedures = {
|
|
@@ -25,7 +28,7 @@ export const procedures = {
|
|
|
25
28
|
// Input for the procedure
|
|
26
29
|
input: type({ query: "string", "pageSize?": "number" }),
|
|
27
30
|
// Function to be called whenever you can update progress while the procedure is running -- long computations are a first-class concern here. Examples include using the fetch-progress NPM package.
|
|
28
|
-
progress: type({
|
|
31
|
+
progress: type({ transferred: "number", total: "number" }),
|
|
29
32
|
// Output of a successful procedure call
|
|
30
33
|
success: type({
|
|
31
34
|
id: "string",
|
|
@@ -33,7 +36,7 @@ export const procedures = {
|
|
|
33
36
|
genres: "string[]",
|
|
34
37
|
}).array(),
|
|
35
38
|
},
|
|
36
|
-
}
|
|
39
|
+
} as const satisfies ProceduresMap
|
|
37
40
|
```
|
|
38
41
|
|
|
39
42
|
### 2. Register your procedures in the service worker
|
|
@@ -58,6 +61,7 @@ swarpc.searchIMDb(async ({ query, pageSize = 10 }, onProgress) => {
|
|
|
58
61
|
return fetch(`https://rest.imdbapi.dev/v2/search/titles?${queryParams}`)
|
|
59
62
|
.then(fetchProgress({ onProgress }))
|
|
60
63
|
.then((response) => response.json())
|
|
64
|
+
.then(({ titles } => titles)
|
|
61
65
|
})
|
|
62
66
|
|
|
63
67
|
// ...
|
|
@@ -84,9 +88,9 @@ Here's a Svelte example!
|
|
|
84
88
|
|
|
85
89
|
<search>
|
|
86
90
|
<input type="text" bind:value={query} placeholder="Search IMDb" />
|
|
87
|
-
<button
|
|
91
|
+
<button onclick={async () => {
|
|
88
92
|
results = await swarpc.searchIMDb({ query }, (p) => {
|
|
89
|
-
progress = p.
|
|
93
|
+
progress = p.transferred / p.total
|
|
90
94
|
})
|
|
91
95
|
}}>
|
|
92
96
|
Search
|
package/dist/swarpc.d.ts
CHANGED
|
@@ -1,64 +1,23 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @import {
|
|
3
|
-
*/
|
|
4
|
-
/**
|
|
5
|
-
* @template {Type} I
|
|
6
|
-
* @template {Type} P
|
|
7
|
-
* @template {Type} S
|
|
8
|
-
* @typedef {Object} Procedure
|
|
9
|
-
* @property {I} input
|
|
10
|
-
* @property {P} progress
|
|
11
|
-
* @property {S} success
|
|
12
|
-
*/
|
|
13
|
-
/**
|
|
14
|
-
* @template {Type} I
|
|
15
|
-
* @template {Type} P
|
|
16
|
-
* @template {Type} S
|
|
17
|
-
* @typedef {(input: I['inferOut'], onProgress: (progress: P['inferOut']) => void) => Promise<NoInfer<S>['inferOut'] | NoInfer<S>['inferOut']>} ProcedureImplementation
|
|
18
|
-
*/
|
|
19
|
-
/**
|
|
20
|
-
* @typedef {Record<string, Procedure<Type, Type, Type>>} ProceduresMap
|
|
21
|
-
*/
|
|
22
|
-
/**
|
|
23
|
-
* @template {ProceduresMap} Procedures
|
|
24
|
-
* @typedef {{ procedures: Procedures, implementations: {[F in keyof Procedures]: ProcedureImplementation<Procedures[F]['input'], Procedures[F]['progress'], Procedures[F]['success']> }, start: (self: Window) => void } & { [F in keyof Procedures]: (impl: NoInfer<ProcedureImplementation<Procedures[F]['input'], Procedures[F]['progress'], Procedures[F]['success'] >>) => void }} SwarpServer
|
|
2
|
+
* @import { ProceduresMap, SwarpcClient, SwarpcServer } from './typings.js'
|
|
25
3
|
*/
|
|
26
4
|
/**
|
|
27
5
|
* @template {ProceduresMap} Procedures
|
|
28
6
|
* @param {Procedures} procedures
|
|
29
|
-
* @returns {
|
|
30
|
-
*/
|
|
31
|
-
export function Server<Procedures extends ProceduresMap>(procedures: Procedures): SwarpServer<Procedures>;
|
|
32
|
-
/**
|
|
33
|
-
* @template {Procedure<Type, Type, Type>} P
|
|
34
|
-
* @typedef {(input: P['input']['inferOut'], onProgress?: (progress: P['progress']['inferOut']) => void) => Promise<P['success']['inferOut']>} ClientMethod
|
|
35
|
-
*/
|
|
36
|
-
/**
|
|
37
|
-
* @template {ProceduresMap} Procedures
|
|
38
|
-
* @typedef {{ procedures: Procedures } & { [F in keyof Procedures]: ClientMethod<Procedures[F]> }} SwarpClient
|
|
7
|
+
* @returns {SwarpcServer<Procedures>}
|
|
39
8
|
*/
|
|
9
|
+
export function Server<Procedures extends ProceduresMap>(procedures: Procedures): SwarpcServer<Procedures>;
|
|
40
10
|
/**
|
|
41
11
|
* @template {ProceduresMap} Procedures
|
|
42
12
|
* @param {Procedures} procedures
|
|
43
|
-
* @
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
};
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
export type SwarpServer<Procedures extends ProceduresMap> = {
|
|
54
|
-
procedures: Procedures;
|
|
55
|
-
implementations: { [F in keyof Procedures]: ProcedureImplementation<Procedures[F]["input"], Procedures[F]["progress"], Procedures[F]["success"]>; };
|
|
56
|
-
start: (self: Window) => void;
|
|
57
|
-
} & { [F in keyof Procedures]: (impl: NoInfer<ProcedureImplementation<Procedures[F]["input"], Procedures[F]["progress"], Procedures[F]["success"]>>) => void; };
|
|
58
|
-
export type ClientMethod<P extends Procedure<Type, Type, Type>> = (input: P["input"]["inferOut"], onProgress?: (progress: P["progress"]["inferOut"]) => void) => Promise<P["success"]["inferOut"]>;
|
|
59
|
-
export type SwarpClient<Procedures extends ProceduresMap> = {
|
|
60
|
-
procedures: Procedures;
|
|
61
|
-
} & { [F in keyof Procedures]: ClientMethod<Procedures[F]>; };
|
|
62
|
-
import type { Type } from 'arktype';
|
|
63
|
-
import type { Type as Type_1 } from 'arktype';
|
|
13
|
+
* @param {object} [options]
|
|
14
|
+
* @param {Worker} [options.worker] the worker instance to connect to -- uses the navigator's service worker if not provided
|
|
15
|
+
* @returns {SwarpcClient<Procedures>}
|
|
16
|
+
*/
|
|
17
|
+
export function Client<Procedures extends ProceduresMap>(procedures: Procedures, { worker }?: {
|
|
18
|
+
worker?: Worker;
|
|
19
|
+
}): SwarpcClient<Procedures>;
|
|
20
|
+
import type { ProceduresMap } from './typings.js';
|
|
21
|
+
import type { SwarpcServer } from './typings.js';
|
|
22
|
+
import type { SwarpcClient } from './typings.js';
|
|
64
23
|
//# sourceMappingURL=swarpc.d.ts.map
|
package/dist/swarpc.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"swarpc.d.ts","sourceRoot":"","sources":["../src/swarpc.js"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"swarpc.d.ts","sourceRoot":"","sources":["../src/swarpc.js"],"names":[],"mappings":"AAEA;;GAEG;AAEH;;;;GAIG;AACH,uBAJ6B,UAAU,SAA1B,aAAe,cACjB,UAAU,GACR,aAAa,UAAU,CAAC,CAsGpC;AA0DD;;;;;;GAMG;AACH,uBAN6B,UAAU,SAA1B,aAAe,cACjB,UAAU,eAElB;IAAyB,MAAM,GAAvB,MAAM;CACd,GAAU,aAAa,UAAU,CAAC,CAiCpC;mCA5M6D,cAAc;kCAAd,cAAc;kCAAd,cAAc"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export type Procedure<I extends Type, P extends Type, S extends Type> = {
|
|
2
|
+
input: I;
|
|
3
|
+
progress: P;
|
|
4
|
+
success: S;
|
|
5
|
+
};
|
|
6
|
+
export type ProcedureImplementation<I extends Type, P extends Type, S extends Type> = (input: I["inferOut"], onProgress: (progress: P["inferOut"]) => void) => Promise<NoInfer<S>["inferOut"] | NoInfer<S>["inferOut"]>;
|
|
7
|
+
export type ProceduresMap = Record<string, Procedure<Type, Type, Type>>;
|
|
8
|
+
export type ClientMethod<P extends Procedure<Type, Type, Type>> = (input: P["input"]["inferIn"], onProgress?: (progress: P["progress"]["inferOut"]) => void) => Promise<P["success"]["inferOut"]>;
|
|
9
|
+
export type SwarpcClient<Procedures extends ProceduresMap> = {
|
|
10
|
+
procedures: Procedures;
|
|
11
|
+
} & { [F in keyof Procedures]: ClientMethod<Procedures[F]>; };
|
|
12
|
+
export type SwarpcServer<Procedures extends ProceduresMap> = {
|
|
13
|
+
procedures: Procedures;
|
|
14
|
+
implementations: { [F in keyof Procedures]: ProcedureImplementation<Procedures[F]["input"], Procedures[F]["progress"], Procedures[F]["success"]>; };
|
|
15
|
+
start: (self: Window) => void;
|
|
16
|
+
} & { [F in keyof Procedures]: (impl: NoInfer<ProcedureImplementation<Procedures[F]["input"], Procedures[F]["progress"], Procedures[F]["success"]>>) => void; };
|
|
17
|
+
import type { Type } from 'arktype';
|
|
18
|
+
//# sourceMappingURL=typings.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"typings.d.ts","sourceRoot":"","sources":["../src/typings.js"],"names":[],"mappings":"sBAKoB,CAAC,SAAR,IAAM,EACC,CAAC,SAAR,IAAM,EACC,CAAC,SAAR,IAAM;WAEL,CAAC;cACD,CAAC;aACD,CAAC;;oCAIK,CAAC,SAAR,IAAM,EACC,CAAC,SAAR,IAAM,EACC,CAAC,SAAR,IAAM,IACN,CAAC,KAAK,EAAE,CAAC,CAAC,UAAU,CAAC,EAAE,UAAU,EAAE,CAAC,QAAQ,EAAE,CAAC,CAAC,UAAU,CAAC,KAAK,IAAI,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC;4BAIjI,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC;yBAIb,CAAC,SAA9B,SAAS,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,CAAE,IAC7B,CAAC,KAAK,EAAE,CAAC,CAAC,OAAO,CAAC,CAAC,SAAS,CAAC,EAAE,UAAU,CAAC,EAAE,CAAC,QAAQ,EAAE,CAAC,CAAC,UAAU,CAAC,CAAC,UAAU,CAAC,KAAK,IAAI,KAAK,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,UAAU,CAAC,CAAC;yBAI/G,UAAU,SAAzB,aAAc,IACf;IAAE,UAAU,EAAE,UAAU,CAAA;CAAE,GAAG,GAAG,CAAC,IAAI,MAAM,UAAU,GAAG,YAAY,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,GAAE;yBAIrE,UAAU,SAAzB,aAAc,IACf;IAAE,UAAU,EAAE,UAAU,CAAC;IAAC,eAAe,EAAE,GAAE,CAAC,IAAI,MAAM,UAAU,GAAG,uBAAuB,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,UAAU,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,EAAE,UAAU,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,GAAE,CAAC;IAAC,KAAK,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAA;CAAG,GAAG,GAAG,CAAC,IAAI,MAAM,UAAU,GAAG,CAAC,IAAI,EAAE,OAAO,CAAC,uBAAuB,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,UAAU,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,EAAE,UAAU,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAE,CAAC,KAAK,IAAI,GAAE;0BApC/V,SAAS"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "swarpc",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Full type-safe RPC library for service worker -- move things off of the UI thread with ease!",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"service-workers",
|
|
@@ -28,12 +28,14 @@
|
|
|
28
28
|
"types": "dist/swarpc.d.ts",
|
|
29
29
|
"scripts": {
|
|
30
30
|
"build": "tsc",
|
|
31
|
-
"test": "echo \"Error: no test specified\" && exit 1"
|
|
31
|
+
"test": "echo \"Error: no test specified\" && exit 1",
|
|
32
|
+
"typedoc": "typedoc src/swarpc.js src/typings.js --readme README.md"
|
|
32
33
|
},
|
|
33
34
|
"dependencies": {
|
|
34
35
|
"arktype": "^2.1.20"
|
|
35
36
|
},
|
|
36
37
|
"devDependencies": {
|
|
38
|
+
"typedoc": "^0.28.7",
|
|
37
39
|
"typescript": "^5.8.3"
|
|
38
40
|
}
|
|
39
41
|
}
|
package/src/swarpc.js
CHANGED
|
@@ -1,41 +1,18 @@
|
|
|
1
1
|
import { type } from "arktype"
|
|
2
|
-
/**
|
|
3
|
-
* @import { Type } from 'arktype';
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* @template {Type} I
|
|
8
|
-
* @template {Type} P
|
|
9
|
-
* @template {Type} S
|
|
10
|
-
* @typedef {Object} Procedure
|
|
11
|
-
* @property {I} input
|
|
12
|
-
* @property {P} progress
|
|
13
|
-
* @property {S} success
|
|
14
|
-
*/
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* @template {Type} I
|
|
18
|
-
* @template {Type} P
|
|
19
|
-
* @template {Type} S
|
|
20
|
-
* @typedef {(input: I['inferOut'], onProgress: (progress: P['inferOut']) => void) => Promise<NoInfer<S>['inferOut'] | NoInfer<S>['inferOut']>} ProcedureImplementation
|
|
21
|
-
*/
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* @typedef {Record<string, Procedure<Type, Type, Type>>} ProceduresMap
|
|
25
|
-
*/
|
|
26
2
|
|
|
27
3
|
/**
|
|
28
|
-
* @
|
|
29
|
-
* @typedef {{ procedures: Procedures, implementations: {[F in keyof Procedures]: ProcedureImplementation<Procedures[F]['input'], Procedures[F]['progress'], Procedures[F]['success']> }, start: (self: Window) => void } & { [F in keyof Procedures]: (impl: NoInfer<ProcedureImplementation<Procedures[F]['input'], Procedures[F]['progress'], Procedures[F]['success'] >>) => void }} SwarpServer
|
|
4
|
+
* @import { ProceduresMap, SwarpcClient, SwarpcServer } from './typings.js'
|
|
30
5
|
*/
|
|
31
6
|
|
|
32
7
|
/**
|
|
33
8
|
* @template {ProceduresMap} Procedures
|
|
34
9
|
* @param {Procedures} procedures
|
|
35
|
-
* @
|
|
10
|
+
* @param {object} [options]
|
|
11
|
+
* @param {Worker} [options.worker] the worker instance to connect to -- uses the navigator's service worker if not provided
|
|
12
|
+
* @returns {SwarpcServer<Procedures>}
|
|
36
13
|
*/
|
|
37
|
-
export function Server(procedures) {
|
|
38
|
-
/** @type {
|
|
14
|
+
export function Server(procedures, { worker } = { worker: undefined }) {
|
|
15
|
+
/** @type {SwarpcServer<Procedures>} */
|
|
39
16
|
// @ts-expect-error
|
|
40
17
|
const instance = {
|
|
41
18
|
procedures,
|
|
@@ -60,6 +37,7 @@ export function Server(procedures) {
|
|
|
60
37
|
const PayloadSchema = type.or(
|
|
61
38
|
...Object.entries(procedures).map(([functionName, { input }]) => ({
|
|
62
39
|
functionName: type(`"${functionName}"`),
|
|
40
|
+
requestId: type("string >= 1"),
|
|
63
41
|
input,
|
|
64
42
|
}))
|
|
65
43
|
)
|
|
@@ -73,18 +51,26 @@ export function Server(procedures) {
|
|
|
73
51
|
* @param {{functionName: string} & Partial<{ result: any; error: any; progress: any }>} data
|
|
74
52
|
*/
|
|
75
53
|
const postMessage = async (data) => {
|
|
76
|
-
|
|
77
|
-
.
|
|
78
|
-
|
|
54
|
+
if (worker) {
|
|
55
|
+
self.postMessage(data)
|
|
56
|
+
} else {
|
|
57
|
+
await self.clients.matchAll().then((clients) => {
|
|
58
|
+
console.debug(`[SWARPC Server] Posting message to clients`, clients)
|
|
79
59
|
clients.forEach((client) => client.postMessage(data))
|
|
80
|
-
)
|
|
60
|
+
})
|
|
61
|
+
}
|
|
81
62
|
}
|
|
82
63
|
|
|
83
64
|
console.log("[SWARPC Server] Starting message listener on", self)
|
|
84
65
|
|
|
85
66
|
self.addEventListener("message", async (event) => {
|
|
86
|
-
const { functionName, input } = PayloadSchema.assert(
|
|
87
|
-
|
|
67
|
+
const { functionName, requestId, input } = PayloadSchema.assert(
|
|
68
|
+
event.data
|
|
69
|
+
)
|
|
70
|
+
console.log(
|
|
71
|
+
`[SWARPC Server] ${requestId} Running ${functionName} with`,
|
|
72
|
+
input
|
|
73
|
+
)
|
|
88
74
|
|
|
89
75
|
/**
|
|
90
76
|
* @param {*} error
|
|
@@ -92,6 +78,7 @@ export function Server(procedures) {
|
|
|
92
78
|
const postError = async (error) =>
|
|
93
79
|
postMessage({
|
|
94
80
|
functionName,
|
|
81
|
+
requestId,
|
|
95
82
|
error: {
|
|
96
83
|
message: "message" in error ? error.message : String(error),
|
|
97
84
|
},
|
|
@@ -103,56 +90,123 @@ export function Server(procedures) {
|
|
|
103
90
|
return
|
|
104
91
|
}
|
|
105
92
|
|
|
106
|
-
await implementation(input, async (progress) =>
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
93
|
+
await implementation(input, async (progress) => {
|
|
94
|
+
console.debug(
|
|
95
|
+
`[SWARPC Server] ${requestId} Progress for ${functionName}:`,
|
|
96
|
+
progress
|
|
97
|
+
)
|
|
98
|
+
await postMessage({ functionName, requestId, progress })
|
|
99
|
+
})
|
|
100
|
+
.catch(async (error) => {
|
|
101
|
+
console.debug(
|
|
102
|
+
`[SWARPC Server] ${requestId} Error in ${functionName}:`,
|
|
103
|
+
error
|
|
104
|
+
)
|
|
105
|
+
await postError(error)
|
|
106
|
+
})
|
|
107
|
+
.then(async (result) => {
|
|
108
|
+
console.debug(
|
|
109
|
+
`[SWARPC Server] ${requestId} Result for ${functionName}:`,
|
|
110
|
+
result
|
|
111
|
+
)
|
|
112
|
+
await postMessage({ functionName, requestId, result })
|
|
113
|
+
})
|
|
111
114
|
})
|
|
112
115
|
}
|
|
113
116
|
|
|
114
117
|
return instance
|
|
115
118
|
}
|
|
116
119
|
|
|
120
|
+
function generateRequestId() {
|
|
121
|
+
return Math.random().toString(36).substring(2, 15)
|
|
122
|
+
}
|
|
123
|
+
|
|
117
124
|
/**
|
|
118
|
-
* @
|
|
119
|
-
* @typedef {(input: P['input']['inferOut'], onProgress?: (progress: P['progress']['inferOut']) => void) => Promise<P['success']['inferOut']>} ClientMethod
|
|
125
|
+
* @type {Map<string, { reject: (err: Error) => void; onProgress: (progress: any) => void; resolve: (result: any) => void }>}
|
|
120
126
|
*/
|
|
127
|
+
const pendingRequests = new Map()
|
|
121
128
|
|
|
129
|
+
let _clientListenerStarted = false
|
|
122
130
|
/**
|
|
123
|
-
* @
|
|
124
|
-
* @typedef {{ procedures: Procedures } & { [F in keyof Procedures]: ClientMethod<Procedures[F]> }} SwarpClient
|
|
131
|
+
* @param {Worker} [worker] the worker instance to connect to -- uses the navigator's service worker if not provided
|
|
125
132
|
*/
|
|
133
|
+
async function startClientListener(worker) {
|
|
134
|
+
if (_clientListenerStarted) return
|
|
135
|
+
|
|
136
|
+
if (!worker) {
|
|
137
|
+
const sw = await navigator.serviceWorker.ready
|
|
138
|
+
if (!sw?.active) {
|
|
139
|
+
throw new Error("[SWARPC Client] Service Worker is not active")
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
if (!navigator.serviceWorker.controller) {
|
|
143
|
+
console.warn("[SWARPC Client] Service Worker is not controlling the page")
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const w = worker ?? navigator.serviceWorker
|
|
148
|
+
console.debug("[SWARPC Client] Registering message listener for client", w)
|
|
149
|
+
|
|
150
|
+
w.addEventListener("message", (event) => {
|
|
151
|
+
const { functionName, requestId, ...data } = event.data || {}
|
|
152
|
+
if (!requestId) {
|
|
153
|
+
throw new Error("[SWARPC Client] Message received without requestId")
|
|
154
|
+
}
|
|
155
|
+
const handlers = pendingRequests.get(requestId)
|
|
156
|
+
if (!handlers) {
|
|
157
|
+
throw new Error(
|
|
158
|
+
`[SWARPC Client] ${requestId} has no active request handlers`
|
|
159
|
+
)
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if ("error" in data) {
|
|
163
|
+
handlers.reject(new Error(data.error.message))
|
|
164
|
+
pendingRequests.delete(requestId)
|
|
165
|
+
} else if ("progress" in data) {
|
|
166
|
+
handlers.onProgress(data.progress)
|
|
167
|
+
} else if ("result" in data) {
|
|
168
|
+
handlers.resolve(data.result)
|
|
169
|
+
pendingRequests.delete(requestId)
|
|
170
|
+
}
|
|
171
|
+
})
|
|
172
|
+
|
|
173
|
+
_clientListenerStarted = true
|
|
174
|
+
}
|
|
126
175
|
|
|
127
176
|
/**
|
|
128
177
|
* @template {ProceduresMap} Procedures
|
|
129
178
|
* @param {Procedures} procedures
|
|
130
|
-
* @
|
|
179
|
+
* @param {object} [options]
|
|
180
|
+
* @param {Worker} [options.worker] the worker instance to connect to -- uses the navigator's service worker if not provided
|
|
181
|
+
* @returns {SwarpcClient<Procedures>}
|
|
131
182
|
*/
|
|
132
|
-
export function Client(procedures) {
|
|
133
|
-
/** @type {
|
|
183
|
+
export function Client(procedures, { worker } = { worker: undefined }) {
|
|
184
|
+
/** @type {SwarpcClient<Procedures>} */
|
|
134
185
|
// @ts-expect-error
|
|
135
186
|
const instance = { procedures }
|
|
136
187
|
|
|
137
188
|
for (const functionName of Object.keys(procedures)) {
|
|
138
189
|
instance[functionName] = async (input, onProgress = () => {}) => {
|
|
139
190
|
procedures[functionName].input.assert(input)
|
|
140
|
-
|
|
141
|
-
|
|
191
|
+
await startClientListener(worker)
|
|
192
|
+
|
|
193
|
+
const w =
|
|
194
|
+
worker ?? (await navigator.serviceWorker.ready.then((r) => r.active))
|
|
142
195
|
return new Promise((resolve, reject) => {
|
|
143
|
-
navigator.serviceWorker.
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
196
|
+
if (!worker && !navigator.serviceWorker.controller)
|
|
197
|
+
console.warn(
|
|
198
|
+
"[SWARPC Client] Service Worker is not controlling the page"
|
|
199
|
+
)
|
|
200
|
+
|
|
201
|
+
const requestId = generateRequestId()
|
|
202
|
+
|
|
203
|
+
pendingRequests.set(requestId, { resolve, onProgress, reject })
|
|
204
|
+
|
|
205
|
+
console.log(
|
|
206
|
+
`[SWARPC Client] ${requestId} Requesting ${functionName} with`,
|
|
207
|
+
input
|
|
208
|
+
)
|
|
209
|
+
w.postMessage({ functionName, input, requestId })
|
|
156
210
|
})
|
|
157
211
|
}
|
|
158
212
|
}
|
package/src/typings.js
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @import { Type } from 'arktype';
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @template {Type} I
|
|
7
|
+
* @template {Type} P
|
|
8
|
+
* @template {Type} S
|
|
9
|
+
* @typedef {Object} Procedure
|
|
10
|
+
* @property {I} input
|
|
11
|
+
* @property {P} progress
|
|
12
|
+
* @property {S} success
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* @template {Type} I
|
|
17
|
+
* @template {Type} P
|
|
18
|
+
* @template {Type} S
|
|
19
|
+
* @typedef {(input: I['inferOut'], onProgress: (progress: P['inferOut']) => void) => Promise<NoInfer<S>['inferOut'] | NoInfer<S>['inferOut']>} ProcedureImplementation
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* @typedef {Record<string, Procedure<Type, Type, Type>>} ProceduresMap
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* @template {Procedure<Type, Type, Type>} P
|
|
28
|
+
* @typedef {(input: P['input']['inferIn'], onProgress?: (progress: P['progress']['inferOut']) => void) => Promise<P['success']['inferOut']>} ClientMethod
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* @template {ProceduresMap} Procedures
|
|
33
|
+
* @typedef {{ procedures: Procedures } & { [F in keyof Procedures]: ClientMethod<Procedures[F]> }} SwarpcClient
|
|
34
|
+
*/
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* @template {ProceduresMap} Procedures
|
|
38
|
+
* @typedef {{ procedures: Procedures, implementations: {[F in keyof Procedures]: ProcedureImplementation<Procedures[F]['input'], Procedures[F]['progress'], Procedures[F]['success']> }, start: (self: Window) => void } & { [F in keyof Procedures]: (impl: NoInfer<ProcedureImplementation<Procedures[F]['input'], Procedures[F]['progress'], Procedures[F]['success'] >>) => void }} SwarpcServer
|
|
39
|
+
*/
|
|
40
|
+
|
|
41
|
+
// Required, otherwise nothing can be imported from this file
|
|
42
|
+
export {}
|