swarpc 0.9.0 → 0.11.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 +18 -0
- package/dist/client.d.ts +37 -9
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +83 -21
- package/dist/localstorage.d.ts +14 -0
- package/dist/localstorage.d.ts.map +1 -0
- package/dist/localstorage.js +39 -0
- package/dist/server.d.ts +7 -4
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +70 -12
- package/dist/types.d.ts +63 -3
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +9 -4
- package/package.json +5 -5
- package/src/client.ts +115 -27
- package/src/localstorage.ts +46 -0
- package/src/server.ts +98 -12
- package/src/types.ts +16 -8
package/dist/types.d.ts
CHANGED
|
@@ -48,7 +48,7 @@ export type CancelablePromise<T = unknown> = {
|
|
|
48
48
|
* Abort the request.
|
|
49
49
|
* @param reason The reason for cancelling the request.
|
|
50
50
|
*/
|
|
51
|
-
cancel: (reason: string) =>
|
|
51
|
+
cancel: (reason: string) => void;
|
|
52
52
|
};
|
|
53
53
|
/**
|
|
54
54
|
* An implementation of a procedure
|
|
@@ -105,6 +105,12 @@ export type Hooks<Procedures extends ProceduresMap> = {
|
|
|
105
105
|
*/
|
|
106
106
|
progress?: <Procedure extends keyof ProceduresMap>(procedure: Procedure, data: Procedures[Procedure]["progress"]["inferOut"]) => void;
|
|
107
107
|
};
|
|
108
|
+
export declare const PayloadInitializeSchema: import("arktype/internal/methods/object.ts").ObjectType<{
|
|
109
|
+
by: "sw&rpc";
|
|
110
|
+
functionName: "#initialize";
|
|
111
|
+
localStorageData: Record<string, unknown>;
|
|
112
|
+
}, {}>;
|
|
113
|
+
export type PayloadInitialize = typeof PayloadInitializeSchema.infer;
|
|
108
114
|
/**
|
|
109
115
|
* @source
|
|
110
116
|
*/
|
|
@@ -150,7 +156,7 @@ export type PayloadCore<PM extends ProceduresMap, Name extends keyof PM = keyof
|
|
|
150
156
|
/**
|
|
151
157
|
* @source
|
|
152
158
|
*/
|
|
153
|
-
export declare const PayloadSchema: import("arktype").Generic<[["Name", string], ["I", unknown], ["P", unknown], ["S", unknown]], readonly ["PayloadHeaderSchema<Name>", "&", "PayloadCoreSchema<I, P, S>"], {
|
|
159
|
+
export declare const PayloadSchema: import("arktype").Generic<[["Name", string], ["I", unknown], ["P", unknown], ["S", unknown]], readonly [readonly ["PayloadHeaderSchema<Name>", "&", "PayloadCoreSchema<I, P, S>"], "|", "PayloadInitializeSchema"], {
|
|
154
160
|
PayloadCoreSchema: import("arktype/internal/scope.ts").bindGenericToScope<import("@ark/schema").GenericAst<[["I", unknown], ["P", unknown], ["S", unknown]], {
|
|
155
161
|
readonly "input?": "I";
|
|
156
162
|
readonly "progress?": "P";
|
|
@@ -178,6 +184,17 @@ export declare const PayloadSchema: import("arktype").Generic<[["Name", string],
|
|
|
178
184
|
readonly functionName: "Name";
|
|
179
185
|
readonly requestId: "string >= 1";
|
|
180
186
|
}, {}, {}>;
|
|
187
|
+
PayloadInitializeSchema: import("arktype/internal/methods/object.ts").ObjectType<{
|
|
188
|
+
by: "sw&rpc";
|
|
189
|
+
functionName: "#initialize";
|
|
190
|
+
localStorageData: Record<string, unknown>;
|
|
191
|
+
}, {}> & {
|
|
192
|
+
readonly " brand": [import("arktype/internal/methods/object.ts").ObjectType<{
|
|
193
|
+
by: "sw&rpc";
|
|
194
|
+
functionName: "#initialize";
|
|
195
|
+
localStorageData: Record<string, unknown>;
|
|
196
|
+
}, {}>, "unparsed"];
|
|
197
|
+
};
|
|
181
198
|
} & {}>;
|
|
182
199
|
PayloadHeaderSchema: import("arktype/internal/scope.ts").bindGenericToScope<import("@ark/schema").GenericAst<[["Name", string]], {
|
|
183
200
|
readonly by: "\"sw&rpc\"";
|
|
@@ -200,7 +217,23 @@ export declare const PayloadSchema: import("arktype").Generic<[["Name", string],
|
|
|
200
217
|
readonly functionName: "Name";
|
|
201
218
|
readonly requestId: "string >= 1";
|
|
202
219
|
}, {}, {}>;
|
|
220
|
+
PayloadInitializeSchema: import("arktype/internal/methods/object.ts").ObjectType<{
|
|
221
|
+
by: "sw&rpc";
|
|
222
|
+
functionName: "#initialize";
|
|
223
|
+
localStorageData: Record<string, unknown>;
|
|
224
|
+
}, {}> & {
|
|
225
|
+
readonly " brand": [import("arktype/internal/methods/object.ts").ObjectType<{
|
|
226
|
+
by: "sw&rpc";
|
|
227
|
+
functionName: "#initialize";
|
|
228
|
+
localStorageData: Record<string, unknown>;
|
|
229
|
+
}, {}>, "unparsed"];
|
|
230
|
+
};
|
|
203
231
|
} & {}>;
|
|
232
|
+
PayloadInitializeSchema: {
|
|
233
|
+
by: "sw&rpc";
|
|
234
|
+
functionName: "#initialize";
|
|
235
|
+
localStorageData: Record<string, unknown>;
|
|
236
|
+
};
|
|
204
237
|
}, {
|
|
205
238
|
PayloadCoreSchema: import("arktype/internal/scope.ts").bindGenericToScope<import("@ark/schema").GenericAst<[["I", unknown], ["P", unknown], ["S", unknown]], {
|
|
206
239
|
readonly "input?": "I";
|
|
@@ -229,6 +262,17 @@ export declare const PayloadSchema: import("arktype").Generic<[["Name", string],
|
|
|
229
262
|
readonly functionName: "Name";
|
|
230
263
|
readonly requestId: "string >= 1";
|
|
231
264
|
}, {}, {}>;
|
|
265
|
+
PayloadInitializeSchema: import("arktype/internal/methods/object.ts").ObjectType<{
|
|
266
|
+
by: "sw&rpc";
|
|
267
|
+
functionName: "#initialize";
|
|
268
|
+
localStorageData: Record<string, unknown>;
|
|
269
|
+
}, {}> & {
|
|
270
|
+
readonly " brand": [import("arktype/internal/methods/object.ts").ObjectType<{
|
|
271
|
+
by: "sw&rpc";
|
|
272
|
+
functionName: "#initialize";
|
|
273
|
+
localStorageData: Record<string, unknown>;
|
|
274
|
+
}, {}>, "unparsed"];
|
|
275
|
+
};
|
|
232
276
|
} & {}>;
|
|
233
277
|
PayloadHeaderSchema: import("arktype/internal/scope.ts").bindGenericToScope<import("@ark/schema").GenericAst<[["Name", string]], {
|
|
234
278
|
readonly by: "\"sw&rpc\"";
|
|
@@ -251,12 +295,28 @@ export declare const PayloadSchema: import("arktype").Generic<[["Name", string],
|
|
|
251
295
|
readonly functionName: "Name";
|
|
252
296
|
readonly requestId: "string >= 1";
|
|
253
297
|
}, {}, {}>;
|
|
298
|
+
PayloadInitializeSchema: import("arktype/internal/methods/object.ts").ObjectType<{
|
|
299
|
+
by: "sw&rpc";
|
|
300
|
+
functionName: "#initialize";
|
|
301
|
+
localStorageData: Record<string, unknown>;
|
|
302
|
+
}, {}> & {
|
|
303
|
+
readonly " brand": [import("arktype/internal/methods/object.ts").ObjectType<{
|
|
304
|
+
by: "sw&rpc";
|
|
305
|
+
functionName: "#initialize";
|
|
306
|
+
localStorageData: Record<string, unknown>;
|
|
307
|
+
}, {}>, "unparsed"];
|
|
308
|
+
};
|
|
254
309
|
} & {}>;
|
|
310
|
+
PayloadInitializeSchema: {
|
|
311
|
+
by: "sw&rpc";
|
|
312
|
+
functionName: "#initialize";
|
|
313
|
+
localStorageData: Record<string, unknown>;
|
|
314
|
+
};
|
|
255
315
|
}>;
|
|
256
316
|
/**
|
|
257
317
|
* The effective payload as sent by the server to the client
|
|
258
318
|
*/
|
|
259
|
-
export type Payload<PM extends ProceduresMap, Name extends keyof PM = keyof PM> = PayloadHeader<PM, Name> & PayloadCore<PM, Name
|
|
319
|
+
export type Payload<PM extends ProceduresMap, Name extends keyof PM = keyof PM> = (PayloadHeader<PM, Name> & PayloadCore<PM, Name>) | PayloadInitialize;
|
|
260
320
|
/**
|
|
261
321
|
* A procedure's corresponding method on the client instance -- used to call the procedure. If you want to be able to cancel the request, you can use the `cancelable` method instead of running the procedure directly.
|
|
262
322
|
*/
|
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAQ,KAAK,IAAI,EAAE,MAAM,SAAS,CAAA;AACzC,OAAO,
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
|
package/dist/types.js
CHANGED
|
@@ -3,6 +3,11 @@
|
|
|
3
3
|
* @mergeModuleWith <project>
|
|
4
4
|
*/
|
|
5
5
|
import { type } from "arktype";
|
|
6
|
+
export const PayloadInitializeSchema = type({
|
|
7
|
+
by: '"sw&rpc"',
|
|
8
|
+
functionName: '"#initialize"',
|
|
9
|
+
localStorageData: "Record<string, unknown>",
|
|
10
|
+
});
|
|
6
11
|
/**
|
|
7
12
|
* @source
|
|
8
13
|
*/
|
|
@@ -25,11 +30,11 @@ export const PayloadCoreSchema = type("<I, P, S>", {
|
|
|
25
30
|
* @source
|
|
26
31
|
*/
|
|
27
32
|
export const PayloadSchema = type
|
|
28
|
-
.scope({ PayloadCoreSchema, PayloadHeaderSchema })
|
|
33
|
+
.scope({ PayloadCoreSchema, PayloadHeaderSchema, PayloadInitializeSchema })
|
|
29
34
|
.type("<Name extends string, I, P, S>", [
|
|
30
|
-
"PayloadHeaderSchema<Name>",
|
|
31
|
-
"
|
|
32
|
-
"
|
|
35
|
+
["PayloadHeaderSchema<Name>", "&", "PayloadCoreSchema<I, P, S>"],
|
|
36
|
+
"|",
|
|
37
|
+
"PayloadInitializeSchema",
|
|
33
38
|
]);
|
|
34
39
|
/**
|
|
35
40
|
* Symbol used as the key for the procedures map on the server instance
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "swarpc",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.11.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",
|
|
@@ -48,15 +48,15 @@
|
|
|
48
48
|
"nodemon": "^3.1.10",
|
|
49
49
|
"prettier": "^3.6.2",
|
|
50
50
|
"sirv-cli": "^3.0.1",
|
|
51
|
-
"typedoc": "^0.28.
|
|
51
|
+
"typedoc": "^0.28.11",
|
|
52
52
|
"typedoc-material-theme": "^1.4.0",
|
|
53
|
-
"typedoc-plugin-dt-links": "^2.0.
|
|
53
|
+
"typedoc-plugin-dt-links": "^2.0.16",
|
|
54
54
|
"typedoc-plugin-extras": "^4.0.1",
|
|
55
55
|
"typedoc-plugin-inline-sources": "^1.3.0",
|
|
56
|
-
"typedoc-plugin-mdn-links": "^5.0.
|
|
56
|
+
"typedoc-plugin-mdn-links": "^5.0.9",
|
|
57
57
|
"typedoc-plugin-redirect": "^1.2.0",
|
|
58
58
|
"typescript": "^5.9.2",
|
|
59
|
-
"vite": "^7.
|
|
59
|
+
"vite": "^7.1.3",
|
|
60
60
|
"vitest": "^3.2.4"
|
|
61
61
|
},
|
|
62
62
|
"volta": {
|
package/src/client.ts
CHANGED
|
@@ -25,6 +25,20 @@ export type SwarpcClient<Procedures extends ProceduresMap> = {
|
|
|
25
25
|
[F in keyof Procedures]: ClientMethod<Procedures[F]>
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
+
/**
|
|
29
|
+
* Context for passing around data useful for requests
|
|
30
|
+
*/
|
|
31
|
+
type Context<Procedures extends ProceduresMap> = {
|
|
32
|
+
/** A logger, bound to the client */
|
|
33
|
+
logger: Logger
|
|
34
|
+
/** The worker instance to use */
|
|
35
|
+
worker: Worker | SharedWorker | undefined
|
|
36
|
+
/** Hooks defined by the client */
|
|
37
|
+
hooks: Hooks<Procedures>
|
|
38
|
+
/** Local storage data defined by the client for the faux local storage */
|
|
39
|
+
localStorage: Record<string, any>
|
|
40
|
+
}
|
|
41
|
+
|
|
28
42
|
/**
|
|
29
43
|
* Pending requests are stored in a map, where the key is the request ID.
|
|
30
44
|
* Each request has a set of handlers: resolve, reject, and onProgress.
|
|
@@ -45,9 +59,14 @@ let _clientListenerStarted = false
|
|
|
45
59
|
*
|
|
46
60
|
* @param procedures procedures the client will be able to call, see {@link ProceduresMap}
|
|
47
61
|
* @param options various options
|
|
48
|
-
* @param options.worker
|
|
49
|
-
*
|
|
50
|
-
* @
|
|
62
|
+
* @param options.worker The instantiated worker object. If not provided, the client will use the service worker.
|
|
63
|
+
* Example: `new Worker("./worker.js")`
|
|
64
|
+
* See {@link Worker} (used by both dedicated workers and service workers), {@link SharedWorker}, and
|
|
65
|
+
* the different [worker types](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API#worker_types) that exist
|
|
66
|
+
* @param options.hooks Hooks to run on messages received from the server. See {@link Hooks}
|
|
67
|
+
* @param options.loglevel Maximum log level to use, defaults to "debug" (shows everything). "info" will not show debug messages, "warn" will only show warnings and errors, "error" will only show errors.
|
|
68
|
+
* @param options.restartListener If true, will force the listener to restart even if it has already been started. You should probably leave this to false, unless you are testing and want to reset the client state.
|
|
69
|
+
* @param options.localStorage Define a in-memory localStorage with the given key-value pairs. Allows code called on the server to access localStorage (even though SharedWorkers don't have access to the browser's real localStorage)
|
|
51
70
|
* @returns a sw&rpc client instance. Each property of the procedures map will be a method, that accepts an input and an optional onProgress callback, see {@link ClientMethod}
|
|
52
71
|
*
|
|
53
72
|
* An example of defining and using a client:
|
|
@@ -60,11 +79,13 @@ export function Client<Procedures extends ProceduresMap>(
|
|
|
60
79
|
loglevel = "debug",
|
|
61
80
|
restartListener = false,
|
|
62
81
|
hooks = {},
|
|
82
|
+
localStorage = {},
|
|
63
83
|
}: {
|
|
64
|
-
worker?: Worker
|
|
84
|
+
worker?: Worker | SharedWorker
|
|
65
85
|
hooks?: Hooks<Procedures>
|
|
66
86
|
loglevel?: LogLevel
|
|
67
87
|
restartListener?: boolean
|
|
88
|
+
localStorage?: Record<string, any>
|
|
68
89
|
} = {}
|
|
69
90
|
): SwarpcClient<Procedures> {
|
|
70
91
|
const l = createLogger("client", loglevel)
|
|
@@ -90,10 +111,15 @@ export function Client<Procedures extends ProceduresMap>(
|
|
|
90
111
|
msg: PayloadCore<Procedures, typeof functionName>,
|
|
91
112
|
options?: StructuredSerializeOptions
|
|
92
113
|
) => {
|
|
93
|
-
|
|
94
|
-
l,
|
|
114
|
+
const ctx: Context<Procedures> = {
|
|
115
|
+
logger: l,
|
|
95
116
|
worker,
|
|
96
117
|
hooks,
|
|
118
|
+
localStorage,
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return postMessage(
|
|
122
|
+
ctx,
|
|
97
123
|
{
|
|
98
124
|
...msg,
|
|
99
125
|
by: "sw&rpc",
|
|
@@ -133,7 +159,7 @@ export function Client<Procedures extends ProceduresMap>(
|
|
|
133
159
|
|
|
134
160
|
// Post the message to the server
|
|
135
161
|
l.debug(requestId, `Requesting ${functionName} with`, input)
|
|
136
|
-
send(requestId, { input }, { transfer })
|
|
162
|
+
return send(requestId, { input }, { transfer })
|
|
137
163
|
.then(() => {})
|
|
138
164
|
.catch(reject)
|
|
139
165
|
})
|
|
@@ -145,7 +171,7 @@ export function Client<Procedures extends ProceduresMap>(
|
|
|
145
171
|
const requestId = makeRequestId()
|
|
146
172
|
return {
|
|
147
173
|
request: _runProcedure(input, onProgress, requestId),
|
|
148
|
-
|
|
174
|
+
cancel(reason: string) {
|
|
149
175
|
if (!pendingRequests.has(requestId)) {
|
|
150
176
|
l.warn(
|
|
151
177
|
requestId,
|
|
@@ -155,7 +181,12 @@ export function Client<Procedures extends ProceduresMap>(
|
|
|
155
181
|
}
|
|
156
182
|
|
|
157
183
|
l.debug(requestId, `Cancelling ${functionName} with`, reason)
|
|
158
|
-
|
|
184
|
+
postMessageSync(l, worker, {
|
|
185
|
+
by: "sw&rpc",
|
|
186
|
+
requestId,
|
|
187
|
+
functionName,
|
|
188
|
+
abort: { reason },
|
|
189
|
+
})
|
|
159
190
|
pendingRequests.delete(requestId)
|
|
160
191
|
},
|
|
161
192
|
}
|
|
@@ -170,20 +201,56 @@ export function Client<Procedures extends ProceduresMap>(
|
|
|
170
201
|
* @returns the worker to use
|
|
171
202
|
*/
|
|
172
203
|
async function postMessage<Procedures extends ProceduresMap>(
|
|
173
|
-
|
|
174
|
-
worker: Worker | undefined,
|
|
175
|
-
hooks: Hooks<Procedures>,
|
|
204
|
+
ctx: Context<Procedures>,
|
|
176
205
|
message: Payload<Procedures>,
|
|
177
206
|
options?: StructuredSerializeOptions
|
|
178
207
|
) {
|
|
179
|
-
await startClientListener(
|
|
208
|
+
await startClientListener(ctx)
|
|
209
|
+
|
|
210
|
+
const { logger: l, worker } = ctx
|
|
180
211
|
|
|
181
212
|
if (!worker && !navigator.serviceWorker.controller)
|
|
182
213
|
l.warn("", "Service Worker is not controlling the page")
|
|
183
214
|
|
|
184
215
|
// If no worker is provided, we use the service worker
|
|
185
216
|
const w =
|
|
186
|
-
worker
|
|
217
|
+
worker instanceof SharedWorker
|
|
218
|
+
? worker.port
|
|
219
|
+
: worker === undefined
|
|
220
|
+
? await navigator.serviceWorker.ready.then((r) => r.active)
|
|
221
|
+
: worker
|
|
222
|
+
|
|
223
|
+
if (!w) {
|
|
224
|
+
throw new Error("[SWARPC Client] No active service worker found")
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
w.postMessage(message, options)
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* A quicker version of postMessage that does not try to start the client listener, await the service worker, etc.
|
|
232
|
+
* esp. useful for abort logic that needs to not be... put behind everything else on the event loop.
|
|
233
|
+
* @param l
|
|
234
|
+
* @param worker
|
|
235
|
+
* @param message
|
|
236
|
+
* @param options
|
|
237
|
+
*/
|
|
238
|
+
export function postMessageSync<Procedures extends ProceduresMap>(
|
|
239
|
+
l: Logger,
|
|
240
|
+
worker: Worker | SharedWorker | undefined,
|
|
241
|
+
message: Payload<Procedures>,
|
|
242
|
+
options?: StructuredSerializeOptions
|
|
243
|
+
): void {
|
|
244
|
+
if (!worker && !navigator.serviceWorker.controller)
|
|
245
|
+
l.warn("", "Service Worker is not controlling the page")
|
|
246
|
+
|
|
247
|
+
// If no worker is provided, we use the service worker
|
|
248
|
+
const w =
|
|
249
|
+
worker instanceof SharedWorker
|
|
250
|
+
? worker.port
|
|
251
|
+
: worker === undefined
|
|
252
|
+
? navigator.serviceWorker.controller
|
|
253
|
+
: worker
|
|
187
254
|
|
|
188
255
|
if (!w) {
|
|
189
256
|
throw new Error("[SWARPC Client] No active service worker found")
|
|
@@ -194,17 +261,16 @@ async function postMessage<Procedures extends ProceduresMap>(
|
|
|
194
261
|
|
|
195
262
|
/**
|
|
196
263
|
* Starts the client listener, which listens for messages from the sw&rpc server.
|
|
197
|
-
* @param worker if provided, the client will use this worker to listen for messages, instead of using the service worker
|
|
198
|
-
* @param force if true, will force the listener to restart even if it has already been started
|
|
264
|
+
* @param ctx.worker if provided, the client will use this worker to listen for messages, instead of using the service worker
|
|
199
265
|
* @returns
|
|
200
266
|
*/
|
|
201
267
|
export async function startClientListener<Procedures extends ProceduresMap>(
|
|
202
|
-
|
|
203
|
-
worker?: Worker,
|
|
204
|
-
hooks: Hooks<Procedures> = {}
|
|
268
|
+
ctx: Context<Procedures>
|
|
205
269
|
) {
|
|
206
270
|
if (_clientListenerStarted) return
|
|
207
271
|
|
|
272
|
+
const { logger: l, worker } = ctx
|
|
273
|
+
|
|
208
274
|
// Get service worker registration if no worker is provided
|
|
209
275
|
if (!worker) {
|
|
210
276
|
const sw = await navigator.serviceWorker.ready
|
|
@@ -220,8 +286,8 @@ export async function startClientListener<Procedures extends ProceduresMap>(
|
|
|
220
286
|
const w = worker ?? navigator.serviceWorker
|
|
221
287
|
|
|
222
288
|
// Start listening for messages
|
|
223
|
-
l.debug(null, "Starting client listener", {
|
|
224
|
-
|
|
289
|
+
l.debug(null, "Starting client listener", { w, ...ctx })
|
|
290
|
+
const listener = (event: Event): void => {
|
|
225
291
|
// Get the data from the event
|
|
226
292
|
const eventData = (event as MessageEvent).data || {}
|
|
227
293
|
|
|
@@ -229,7 +295,15 @@ export async function startClientListener<Procedures extends ProceduresMap>(
|
|
|
229
295
|
if (eventData?.by !== "sw&rpc") return
|
|
230
296
|
|
|
231
297
|
// We don't use a arktype schema here, we trust the server to send valid data
|
|
232
|
-
const
|
|
298
|
+
const payload = eventData as Payload<Procedures>
|
|
299
|
+
|
|
300
|
+
// Ignore #initialize request, it's client->server only
|
|
301
|
+
if ("localStorageData" in payload) {
|
|
302
|
+
l.warn(null, "Ignoring unexpected #initialize from server", payload)
|
|
303
|
+
return
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
const { requestId, ...data } = payload
|
|
233
307
|
|
|
234
308
|
// Sanity check in case we somehow receive a message without requestId
|
|
235
309
|
if (!requestId) {
|
|
@@ -240,27 +314,41 @@ export async function startClientListener<Procedures extends ProceduresMap>(
|
|
|
240
314
|
const handlers = pendingRequests.get(requestId)
|
|
241
315
|
if (!handlers) {
|
|
242
316
|
throw new Error(
|
|
243
|
-
`[SWARPC Client] ${requestId} has no active request handlers, cannot process ${JSON.stringify(data)}
|
|
317
|
+
`[SWARPC Client] ${requestId} has no active request handlers, cannot process ${JSON.stringify(data)}`
|
|
244
318
|
)
|
|
245
319
|
}
|
|
246
320
|
|
|
247
321
|
// React to the data received: call hook, call handler,
|
|
248
322
|
// and remove the request from pendingRequests (unless it's a progress update)
|
|
249
323
|
if ("error" in data) {
|
|
250
|
-
hooks.error?.(data.functionName, new Error(data.error.message))
|
|
324
|
+
ctx.hooks.error?.(data.functionName, new Error(data.error.message))
|
|
251
325
|
handlers.reject(new Error(data.error.message))
|
|
252
326
|
pendingRequests.delete(requestId)
|
|
253
327
|
} else if ("progress" in data) {
|
|
254
|
-
hooks.progress?.(data.functionName, data.progress)
|
|
328
|
+
ctx.hooks.progress?.(data.functionName, data.progress)
|
|
255
329
|
handlers.onProgress(data.progress)
|
|
256
330
|
} else if ("result" in data) {
|
|
257
|
-
hooks.success?.(data.functionName, data.result)
|
|
331
|
+
ctx.hooks.success?.(data.functionName, data.result)
|
|
258
332
|
handlers.resolve(data.result)
|
|
259
333
|
pendingRequests.delete(requestId)
|
|
260
334
|
}
|
|
261
|
-
}
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
if (w instanceof SharedWorker) {
|
|
338
|
+
w.port.addEventListener("message", listener)
|
|
339
|
+
w.port.start()
|
|
340
|
+
} else {
|
|
341
|
+
w.addEventListener("message", listener)
|
|
342
|
+
}
|
|
262
343
|
|
|
263
344
|
_clientListenerStarted = true
|
|
345
|
+
|
|
346
|
+
// Recursive terminal case is ensured by calling this *after* _clientListenerStarted is set to true: startClientListener() will therefore not be called in postMessage() again.
|
|
347
|
+
await postMessage(ctx, {
|
|
348
|
+
by: "sw&rpc",
|
|
349
|
+
functionName: "#initialize",
|
|
350
|
+
localStorageData: ctx.localStorage,
|
|
351
|
+
})
|
|
264
352
|
}
|
|
265
353
|
|
|
266
354
|
/**
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
export class FauxLocalStorage {
|
|
2
|
+
data: Record<string, any>
|
|
3
|
+
keysOrder: string[]
|
|
4
|
+
|
|
5
|
+
constructor(data: Record<string, any>) {
|
|
6
|
+
this.data = data
|
|
7
|
+
this.keysOrder = Object.keys(data)
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
setItem(key: string, value: string) {
|
|
11
|
+
if (!this.hasItem(key)) this.keysOrder.push(key)
|
|
12
|
+
this.data[key] = value
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
getItem(key: string) {
|
|
16
|
+
return this.data[key]
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
hasItem(key: string) {
|
|
20
|
+
return Object.hasOwn(this.data, key)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
removeItem(key: string) {
|
|
24
|
+
if (!this.hasItem(key)) return
|
|
25
|
+
delete this.data[key]
|
|
26
|
+
this.keysOrder = this.keysOrder.filter((k) => k !== key)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
clear() {
|
|
30
|
+
this.data = {}
|
|
31
|
+
this.keysOrder = []
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
key(index: number) {
|
|
35
|
+
return this.keysOrder[index]
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
get length() {
|
|
39
|
+
return this.keysOrder.length
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
register(subject: WorkerGlobalScope | SharedWorkerGlobalScope) {
|
|
43
|
+
// @ts-expect-error
|
|
44
|
+
subject.localStorage = this
|
|
45
|
+
}
|
|
46
|
+
}
|