serviceworker: move rpc-relateds to workerrpc
This commit is contained in:
		
							parent
							
								
									559a352bc1
								
							
						
					
					
						commit
						6284c7d18e
					
				
					 4 changed files with 235 additions and 193 deletions
				
			
		| 
						 | 
				
			
			@ -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…
	
	Add table
		Add a link
		
	
		Reference in a new issue