serviceworker: move rpc-relateds to workerrpc
This commit is contained in:
parent
559a352bc1
commit
6284c7d18e
4 changed files with 235 additions and 193 deletions
|
@ -23,7 +23,10 @@ import {
|
|||
isJSONRPCResult,
|
||||
ResultDispatcher,
|
||||
type JSONRPC,
|
||||
} from "./serviceworker/services.js";
|
||||
} from "./serviceworker/workerrpc.js";
|
||||
import {
|
||||
Service
|
||||
} from "./serviceworker/services.js"
|
||||
import { makeEventListener } from "@solid-primitives/event-listener";
|
||||
import { ServiceWorkerProvider } from "./platform/host.js";
|
||||
|
||||
|
@ -75,7 +78,7 @@ const App: Component = () => {
|
|||
worker: ServiceWorker,
|
||||
expectedAge: number,
|
||||
) => {
|
||||
const [call, ret] = dispatcher.createTypedCall("ping", undefined);
|
||||
const [call, ret] = dispatcher.createTypedCall<Service>("ping");
|
||||
worker.postMessage(await call);
|
||||
const result = await ret;
|
||||
console.assert(!result.error, result);
|
||||
|
|
|
@ -1,17 +1,17 @@
|
|||
import { cleanupOutdatedCaches, precacheAndRoute } from "workbox-precaching";
|
||||
import { clientsClaim } from "workbox-core";
|
||||
import type { AnyCall } from "./services";
|
||||
import { dispatchCall, isJSONRPCCall, type Call } from "./workerrpc";
|
||||
|
||||
function checkServiceWorker(
|
||||
function isServiceWorker(
|
||||
self: WorkerGlobalScope,
|
||||
): self is ServiceWorkerGlobalScope {
|
||||
return !!(self as unknown as ServiceWorkerGlobalScope).registration;
|
||||
}
|
||||
|
||||
if (checkServiceWorker(self)) {
|
||||
if (isServiceWorker(self)) {
|
||||
cleanupOutdatedCaches();
|
||||
precacheAndRoute(self.__WB_MANIFEST, {
|
||||
cleanURLs: false
|
||||
cleanURLs: false,
|
||||
});
|
||||
|
||||
// auto update
|
||||
|
@ -21,15 +21,14 @@ if (checkServiceWorker(self)) {
|
|||
throw new TypeError("This entry point must be run in a service worker");
|
||||
}
|
||||
|
||||
self.addEventListener("message", (event: MessageEvent<AnyCall>) => {
|
||||
if (event.data.method === "ping") {
|
||||
event.source.postMessage({id: event.data.id, jsonrpc: "2.0", result: undefined})
|
||||
} else {
|
||||
event.source.postMessage({
|
||||
error: {
|
||||
code: -32601,
|
||||
message: "Method not found"
|
||||
export const Service = {
|
||||
ping() {},
|
||||
};
|
||||
|
||||
self.addEventListener("message", (event: MessageEvent<unknown>) => {
|
||||
const payload = event.data;
|
||||
if (typeof payload !== "object") return;
|
||||
if (isJSONRPCCall(payload as Record<string, unknown>)) {
|
||||
dispatchCall(Service, event as MessageEvent<Call<unknown>>);
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
});
|
||||
|
|
|
@ -1,177 +1,3 @@
|
|||
export type JSONRPC = {
|
||||
jsonrpc: "2.0";
|
||||
id?: string | number;
|
||||
export type Service = {
|
||||
ping(): void
|
||||
};
|
||||
|
||||
export type Call<T = undefined> = JSONRPC & {
|
||||
method: string;
|
||||
params: T;
|
||||
};
|
||||
|
||||
export type RemoteError<E = undefined> = { data: E } & (
|
||||
| {
|
||||
code: number;
|
||||
message: string;
|
||||
}
|
||||
| {
|
||||
code: -32700;
|
||||
message: "Parse Error";
|
||||
}
|
||||
| {
|
||||
code: -32600;
|
||||
message: "Invalid Request";
|
||||
}
|
||||
| {
|
||||
code: -32601;
|
||||
message: "Method not found";
|
||||
}
|
||||
| {
|
||||
code: -32602;
|
||||
message: "Invalid params";
|
||||
}
|
||||
| {
|
||||
code: -32603;
|
||||
message: "Internal error";
|
||||
}
|
||||
);
|
||||
|
||||
export type Result<T, E> = JSONRPC & { id: string | number } & (
|
||||
| {
|
||||
result: T;
|
||||
error: undefined;
|
||||
}
|
||||
| { error: RemoteError<E>; result: undefined }
|
||||
);
|
||||
|
||||
export function isJSONRPCResult(
|
||||
object: Record<string, unknown>,
|
||||
): object is Result<unknown, unknown> {
|
||||
return object["jsonrpc"] === "2.0" && object["id"] && !object["method"];
|
||||
}
|
||||
|
||||
export function isJSONRPCCall(
|
||||
object: Record<string, unknown>,
|
||||
): object is Call<unknown> {
|
||||
return object["jsonrpc"] === "2.0" && !!object["method"];
|
||||
}
|
||||
|
||||
export class ResultDispatcher {
|
||||
private map: Map<
|
||||
number | string,
|
||||
((value: Result<unknown, unknown>) => void) | true // `true` = a call is generated, but the promise not created
|
||||
>;
|
||||
private nextId: number = Number.MIN_SAFE_INTEGER;
|
||||
|
||||
constructor() {
|
||||
this.map = new Map();
|
||||
}
|
||||
|
||||
private rollId() {
|
||||
let id = 0;
|
||||
while (this.map.get((id = this.nextId++))) {
|
||||
if (this.nextId >= Number.MAX_SAFE_INTEGER) {
|
||||
this.nextId = Number.MIN_SAFE_INTEGER;
|
||||
}
|
||||
}
|
||||
return id;
|
||||
}
|
||||
|
||||
createCall<T>(
|
||||
method: string,
|
||||
params: T,
|
||||
): [Promise<Call<T>>, Promise<Result<unknown, unknown>>] {
|
||||
const id = this.rollId();
|
||||
const p = new Promise<Result<unknown, unknown>>((resolve) =>
|
||||
this.map.set(id, resolve),
|
||||
);
|
||||
this.map.set(id, true);
|
||||
const call: Call<T> = {
|
||||
jsonrpc: "2.0",
|
||||
id,
|
||||
method,
|
||||
params,
|
||||
};
|
||||
|
||||
return [
|
||||
new Promise((resolve) => {
|
||||
const waitUntilTheIdSet = () => {
|
||||
// We must do this check to make sure the id is set before the call made.
|
||||
// or the dispatching may lost the callback
|
||||
if (this.map.get(id)) {
|
||||
resolve(call);
|
||||
} else {
|
||||
setTimeout(waitUntilTheIdSet, 0);
|
||||
}
|
||||
};
|
||||
|
||||
waitUntilTheIdSet();
|
||||
}),
|
||||
p,
|
||||
];
|
||||
}
|
||||
|
||||
dispatch(id: string | number, message: Result<unknown, unknown>) {
|
||||
{
|
||||
const callback = this.map.get(id);
|
||||
if (!callback) return;
|
||||
if (typeof callback !== "boolean") {
|
||||
callback(message);
|
||||
this.map.delete(id);
|
||||
return Promise.resolve();
|
||||
}
|
||||
}
|
||||
|
||||
return new Promise<void>((resolve) => {
|
||||
let retried = 0;
|
||||
|
||||
const checkAndDispatch = () => {
|
||||
const callback = this.map.get(id);
|
||||
if (typeof callback !== "boolean") {
|
||||
callback(message);
|
||||
this.map.delete(id);
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
setTimeout(checkAndDispatch, 0);
|
||||
if (++retried > 3) {
|
||||
console.warn(
|
||||
`retried ${retried} time(s) but the callback is still disappeared, id is "${id}"`,
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
// start the loop
|
||||
checkAndDispatch();
|
||||
});
|
||||
}
|
||||
|
||||
createTypedCall<
|
||||
K extends keyof RequestParams,
|
||||
P extends RequestParams[K],
|
||||
R extends ResponseResults[K],
|
||||
E extends ResponseErrorDatas[K],
|
||||
>(method: K, params: P): [Promise<Call<P>>, Promise<Result<R, E>>] {
|
||||
return this.createCall(method, params) as [
|
||||
Promise<Call<P>>,
|
||||
Promise<Result<R, E>>,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
interface RequestParams {
|
||||
ping: void;
|
||||
}
|
||||
|
||||
interface ResponseResults {
|
||||
ping: void;
|
||||
}
|
||||
|
||||
interface ResponseErrorDatas {
|
||||
[key: string]: void;
|
||||
}
|
||||
|
||||
export type AnyCall<K extends keyof RequestParams = keyof RequestParams> =
|
||||
JSONRPC & {
|
||||
method: K;
|
||||
params: RequestParams[K];
|
||||
};
|
||||
|
|
214
src/serviceworker/workerrpc.ts
Normal file
214
src/serviceworker/workerrpc.ts
Normal file
|
@ -0,0 +1,214 @@
|
|||
export type JSONRPC = {
|
||||
jsonrpc: "2.0";
|
||||
id?: string | number;
|
||||
};
|
||||
|
||||
export type Call<T = undefined> = JSONRPC & {
|
||||
method: string;
|
||||
params: T;
|
||||
};
|
||||
|
||||
export type RemoteError<E = undefined> = { data: E } & (
|
||||
| {
|
||||
code: number;
|
||||
message: string;
|
||||
}
|
||||
| {
|
||||
code: -32700;
|
||||
message: "Parse Error";
|
||||
}
|
||||
| {
|
||||
code: -32600;
|
||||
message: "Invalid Request";
|
||||
}
|
||||
| {
|
||||
code: -32601;
|
||||
message: "Method not found";
|
||||
}
|
||||
| {
|
||||
code: -32602;
|
||||
message: "Invalid params";
|
||||
}
|
||||
| {
|
||||
code: -32603;
|
||||
message: "Internal error";
|
||||
}
|
||||
);
|
||||
|
||||
export type Result<T, E> = JSONRPC & { id: string | number } & (
|
||||
| {
|
||||
result: T;
|
||||
error: undefined;
|
||||
}
|
||||
| { error: RemoteError<E>; result: undefined }
|
||||
);
|
||||
|
||||
export function isJSONRPCResult(
|
||||
object: Record<string, unknown>,
|
||||
): object is Result<unknown, unknown> {
|
||||
return object["jsonrpc"] === "2.0" && object["id"] && !object["method"];
|
||||
}
|
||||
|
||||
export function isJSONRPCCall(
|
||||
object: Record<string, unknown>,
|
||||
): object is Call<unknown> {
|
||||
return object["jsonrpc"] === "2.0" && !!object["method"];
|
||||
}
|
||||
|
||||
export class ResultDispatcher {
|
||||
private map: Map<
|
||||
number | string,
|
||||
((value: Result<unknown, unknown>) => void) | true // `true` = a call is generated, but the promise not created
|
||||
>;
|
||||
private nextId: number = Number.MIN_SAFE_INTEGER;
|
||||
|
||||
constructor() {
|
||||
this.map = new Map();
|
||||
}
|
||||
|
||||
private rollId() {
|
||||
let id = 0;
|
||||
while (this.map.get((id = this.nextId++))) {
|
||||
if (this.nextId >= Number.MAX_SAFE_INTEGER) {
|
||||
this.nextId = Number.MIN_SAFE_INTEGER;
|
||||
}
|
||||
}
|
||||
return id;
|
||||
}
|
||||
|
||||
createCall<T>(
|
||||
method: string,
|
||||
params: T,
|
||||
): [Promise<Call<T>>, Promise<Result<unknown, unknown>>] {
|
||||
const id = this.rollId();
|
||||
const p = new Promise<Result<unknown, unknown>>((resolve) =>
|
||||
this.map.set(id, resolve),
|
||||
);
|
||||
this.map.set(id, true);
|
||||
const call: Call<T> = {
|
||||
jsonrpc: "2.0",
|
||||
id,
|
||||
method,
|
||||
params,
|
||||
};
|
||||
|
||||
return [
|
||||
new Promise((resolve) => {
|
||||
const waitUntilTheIdSet = () => {
|
||||
// We must do this check to make sure the id is set before the call made.
|
||||
// or the dispatching may lost the callback
|
||||
if (this.map.get(id)) {
|
||||
resolve(call);
|
||||
} else {
|
||||
setTimeout(waitUntilTheIdSet, 0);
|
||||
}
|
||||
};
|
||||
|
||||
waitUntilTheIdSet();
|
||||
}),
|
||||
p,
|
||||
];
|
||||
}
|
||||
|
||||
dispatch(id: string | number, message: Result<unknown, unknown>) {
|
||||
{
|
||||
const callback = this.map.get(id);
|
||||
if (!callback) return;
|
||||
if (typeof callback !== "boolean") {
|
||||
callback(message);
|
||||
this.map.delete(id);
|
||||
return Promise.resolve();
|
||||
}
|
||||
}
|
||||
|
||||
return new Promise<void>((resolve) => {
|
||||
let retried = 0;
|
||||
|
||||
const checkAndDispatch = () => {
|
||||
const callback = this.map.get(id);
|
||||
if (typeof callback !== "boolean") {
|
||||
callback(message);
|
||||
this.map.delete(id);
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
setTimeout(checkAndDispatch, 0);
|
||||
if (++retried > 3) {
|
||||
console.warn(
|
||||
`retried ${retried} time(s) but the callback is still disappeared, id is "${id}"`,
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
// start the loop
|
||||
checkAndDispatch();
|
||||
});
|
||||
}
|
||||
|
||||
createTypedCall<
|
||||
S extends AnyService,
|
||||
E = unknown,
|
||||
K extends keyof S = keyof S,
|
||||
P extends Parameters<S[K]> = Parameters<S[K]>,
|
||||
R extends ReturnType<S[K]> = ReturnType<S[K]>,
|
||||
>(method: K, ...params: P): [Promise<Call<P>>, Promise<Result<R, E>>] {
|
||||
return this.createCall(method as string, params) as [
|
||||
Promise<Call<P>>,
|
||||
Promise<Result<R, E>>,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
type AnyService = Record<string, ((...args: unknown[]) => unknown) | undefined>
|
||||
|
||||
export async function dispatchCall<
|
||||
S extends AnyService,
|
||||
>(service: S, event: MessageEvent<Call<unknown>>) {
|
||||
try {
|
||||
const fn = service[event.data.method];
|
||||
if (!fn) {
|
||||
if (event.data.id)
|
||||
return event.source.postMessage({
|
||||
jsonrpc: "2.0",
|
||||
id: event.data.id,
|
||||
error: {
|
||||
code: -30601,
|
||||
message: "Method not found",
|
||||
},
|
||||
} as Result<void, void>);
|
||||
}
|
||||
|
||||
try {
|
||||
const result = await fn(...event.data.params as unknown[]);
|
||||
|
||||
if (!event.data.id) return;
|
||||
|
||||
event.source.postMessage({
|
||||
jsonrpc: "2.0",
|
||||
id: event.data.id,
|
||||
result: result,
|
||||
} as Result<unknown, void>);
|
||||
} catch (reason) {
|
||||
event.source.postMessage({
|
||||
jsonrpc: "2.0",
|
||||
id: event.data.id,
|
||||
error: {
|
||||
code: 0,
|
||||
message: String(reason),
|
||||
data: reason,
|
||||
},
|
||||
} as Result<unknown, unknown>);
|
||||
}
|
||||
} catch (reason) {
|
||||
if (event.data.id)
|
||||
event.source.postMessage({
|
||||
jsonrpc: "2.0",
|
||||
id: event.data.id,
|
||||
error: {
|
||||
code: -32603,
|
||||
message: "Internal error",
|
||||
data: reason,
|
||||
},
|
||||
} as Result<void, unknown>);
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue