Compare commits
	
		
			6 commits
		
	
	
		
			530a89755c
			...
			fe350532ec
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| fe350532ec | |||
| 
							 | 
						4d4e787b84 | ||
| 
							 | 
						f20b5da8e4 | ||
| 
							 | 
						f0456337d5 | ||
| 
							 | 
						0fca81dc93 | ||
| 
							 | 
						bffa896184 | 
					 10 changed files with 166 additions and 55 deletions
				
			
		
							
								
								
									
										34
									
								
								.forgejo/workflows/checkpr.yml
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								.forgejo/workflows/checkpr.yml
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,34 @@
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					on:
 | 
				
			||||||
 | 
					  pull_request:
 | 
				
			||||||
 | 
					    types: [opened, synchronize, reopened]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					jobs:
 | 
				
			||||||
 | 
					  checkpr:
 | 
				
			||||||
 | 
					    runs-on: fedora-41
 | 
				
			||||||
 | 
					    steps:
 | 
				
			||||||
 | 
					      - name: Checkout
 | 
				
			||||||
 | 
					        uses: actions/checkout@v4
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      - name: Setup Bun
 | 
				
			||||||
 | 
					        uses: oven-sh/setup-bun@v2
 | 
				
			||||||
 | 
					        with:
 | 
				
			||||||
 | 
					          bun-version-file: 'package.json'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      - name: Cache Dependencies
 | 
				
			||||||
 | 
					        id: dependencies-cache
 | 
				
			||||||
 | 
					        uses: actions/cache@v4
 | 
				
			||||||
 | 
					        with:
 | 
				
			||||||
 | 
					          path: ~/.bun/install/cache
 | 
				
			||||||
 | 
					          key: ${{ runner.os }}-bun-${{ hashFiles('**/bun.lockb') }}
 | 
				
			||||||
 | 
					          restore-keys: |
 | 
				
			||||||
 | 
					            ${{ runner.os }}-bun-
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      - name: Install Dependencies
 | 
				
			||||||
 | 
					        run: bun install
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      - name: Validate types
 | 
				
			||||||
 | 
					        run: bun typecheck
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      - name: Build Dist
 | 
				
			||||||
 | 
					        run: VITE_CODE_VERSION=$GITHUB_SHA bun dist
 | 
				
			||||||
| 
						 | 
					@ -30,6 +30,9 @@ jobs:
 | 
				
			||||||
      - name: Install Dependencies
 | 
					      - name: Install Dependencies
 | 
				
			||||||
        run: bun install
 | 
					        run: bun install
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      - name: Validate types
 | 
				
			||||||
 | 
					        run: bun typecheck
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      - name: Build Dist (Staging)
 | 
					      - name: Build Dist (Staging)
 | 
				
			||||||
        run: VITE_CODE_VERSION=$GITHUB_SHA bun dist -m staging
 | 
					        run: VITE_CODE_VERSION=$GITHUB_SHA bun dist -m staging
 | 
				
			||||||
        if: env.GITHUB_REF_NAME == 'master'
 | 
					        if: env.GITHUB_REF_NAME == 'master'
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -9,7 +9,8 @@
 | 
				
			||||||
    "dev": "vite --host 0.0.0.0",
 | 
					    "dev": "vite --host 0.0.0.0",
 | 
				
			||||||
    "preview": "vite preview",
 | 
					    "preview": "vite preview",
 | 
				
			||||||
    "dist": "vite build",
 | 
					    "dist": "vite build",
 | 
				
			||||||
    "count-source-lines": "exec scripts/src-lc.sh"
 | 
					    "count-source-lines": "exec scripts/src-lc.sh",
 | 
				
			||||||
 | 
					    "typecheck": "tsc --noEmit --skipLibCheck"
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  "keywords": [],
 | 
					  "keywords": [],
 | 
				
			||||||
  "author": "Rubicon",
 | 
					  "author": "Rubicon",
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -15,3 +15,24 @@ if (typeof window.crypto.randomUUID === "undefined") {
 | 
				
			||||||
      ) as `${string}-${string}-${string}-${string}-${string}`;
 | 
					      ) as `${string}-${string}-${string}-${string}-${string}`;
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if (typeof Promise.withResolvers === "undefined") {
 | 
				
			||||||
 | 
					  // Chrome/Edge 119, Firefox 121, Safari/iOS 17.4
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // Promise.withResolvers is generic and works with subclasses - the typescript built-in decl
 | 
				
			||||||
 | 
					  // could not handle the subclassing case.
 | 
				
			||||||
 | 
					  Promise.withResolvers = function <T>(this: AnyPromiseConstructor<T>) {
 | 
				
			||||||
 | 
					    let resolve!: PromiseWithResolvers<T>["resolve"], reject!: PromiseWithResolvers<T>["reject"];
 | 
				
			||||||
 | 
					    // These variables are expected to be set after `new this()`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const promise = new this((resolve0, reject0) => {
 | 
				
			||||||
 | 
					      resolve = resolve0;
 | 
				
			||||||
 | 
					      reject = reject0;
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return {
 | 
				
			||||||
 | 
					      promise, resolve, reject
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -4,23 +4,27 @@ import { dispatchCall, isJSONRPCCall, type Call } from "./workerrpc";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function isServiceWorker(
 | 
					function isServiceWorker(
 | 
				
			||||||
  self: WorkerGlobalScope,
 | 
					  self: WorkerGlobalScope,
 | 
				
			||||||
 | 
					  // @ts-ignore: workaround for workbox logger.d.ts decl
 | 
				
			||||||
): self is ServiceWorkerGlobalScope {
 | 
					): self is ServiceWorkerGlobalScope {
 | 
				
			||||||
  return !!(self as unknown as ServiceWorkerGlobalScope).registration;
 | 
					  return (
 | 
				
			||||||
 | 
					    (self as unknown as Record<string, unknown>)["serviceWorker"] instanceof
 | 
				
			||||||
 | 
					    ServiceWorker
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
if (isServiceWorker(self)) {
 | 
					if (!isServiceWorker(self)) {
 | 
				
			||||||
  cleanupOutdatedCaches();
 | 
					 | 
				
			||||||
  precacheAndRoute(self.__WB_MANIFEST, {
 | 
					 | 
				
			||||||
    cleanURLs: false,
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // auto update
 | 
					 | 
				
			||||||
  self.skipWaiting();
 | 
					 | 
				
			||||||
  clientsClaim();
 | 
					 | 
				
			||||||
} else {
 | 
					 | 
				
			||||||
  throw new TypeError("This entry point must be run in a service worker");
 | 
					  throw new TypeError("This entry point must be run in a service worker");
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					cleanupOutdatedCaches();
 | 
				
			||||||
 | 
					precacheAndRoute(self.__WB_MANIFEST, {
 | 
				
			||||||
 | 
					  cleanURLs: false,
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// auto update
 | 
				
			||||||
 | 
					self.skipWaiting();
 | 
				
			||||||
 | 
					clientsClaim();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const Service = {
 | 
					export const Service = {
 | 
				
			||||||
  ping() {},
 | 
					  ping() {},
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
| 
						 | 
					@ -29,6 +33,9 @@ self.addEventListener("message", (event: MessageEvent<unknown>) => {
 | 
				
			||||||
  const payload = event.data;
 | 
					  const payload = event.data;
 | 
				
			||||||
  if (typeof payload !== "object") return;
 | 
					  if (typeof payload !== "object") return;
 | 
				
			||||||
  if (isJSONRPCCall(payload as Record<string, unknown>)) {
 | 
					  if (isJSONRPCCall(payload as Record<string, unknown>)) {
 | 
				
			||||||
    dispatchCall(Service, event as MessageEvent<Call<unknown>>);
 | 
					    dispatchCall(
 | 
				
			||||||
 | 
					      Service,
 | 
				
			||||||
 | 
					      event as MessageEvent<Call<unknown>> & { source: MessageEventSource },
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,5 +1,7 @@
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
  "compilerOptions": {
 | 
					  "compilerOptions": {
 | 
				
			||||||
    "lib": ["ESNext", "WebWorker"],
 | 
					    "lib": ["WebWorker", "ESNext"],
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
 | 
					  "extends": ["../../tsconfig.super.json"],
 | 
				
			||||||
 | 
					  "include": ["./**/*.ts"],
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -46,7 +46,7 @@ export type Result<T, E> = JSONRPC & { id: string | number } & (
 | 
				
			||||||
export function isJSONRPCResult(
 | 
					export function isJSONRPCResult(
 | 
				
			||||||
  object: Record<string, unknown>,
 | 
					  object: Record<string, unknown>,
 | 
				
			||||||
): object is Result<unknown, unknown> {
 | 
					): object is Result<unknown, unknown> {
 | 
				
			||||||
  return object["jsonrpc"] === "2.0" && object["id"] && !object["method"];
 | 
					  return !!(object["jsonrpc"] === "2.0" && object["id"] && !object["method"]);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function isJSONRPCCall(
 | 
					export function isJSONRPCCall(
 | 
				
			||||||
| 
						 | 
					@ -114,6 +114,9 @@ export class ResultDispatcher {
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
      const callback = this.map.get(id);
 | 
					      const callback = this.map.get(id);
 | 
				
			||||||
      if (!callback) return;
 | 
					      if (!callback) return;
 | 
				
			||||||
 | 
					      // Now the callback is not undefined
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      // Fast path
 | 
				
			||||||
      if (typeof callback !== "boolean") {
 | 
					      if (typeof callback !== "boolean") {
 | 
				
			||||||
        callback(message);
 | 
					        callback(message);
 | 
				
			||||||
        this.map.delete(id);
 | 
					        this.map.delete(id);
 | 
				
			||||||
| 
						 | 
					@ -121,28 +124,30 @@ export class ResultDispatcher {
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return new Promise<void>((resolve) => {
 | 
					    const { promise, resolve } = Promise.withResolvers<undefined>();
 | 
				
			||||||
      let retried = 0;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
      const checkAndDispatch = () => {
 | 
					    let retried = 0;
 | 
				
			||||||
        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
 | 
					    const checkAndDispatch = () => {
 | 
				
			||||||
      checkAndDispatch();
 | 
					      const callback = this.map.get(id);
 | 
				
			||||||
    });
 | 
					      if (typeof callback !== "boolean") {
 | 
				
			||||||
 | 
					        callback!(message); // the nullability is already checked before
 | 
				
			||||||
 | 
					        this.map.delete(id);
 | 
				
			||||||
 | 
					        resolve(undefined);
 | 
				
			||||||
 | 
					        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();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return promise;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  createTypedCall<
 | 
					  createTypedCall<
 | 
				
			||||||
| 
						 | 
					@ -159,14 +164,16 @@ export class ResultDispatcher {
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type AnyService = Record<string, ((...args: unknown[]) => unknown) | undefined>
 | 
					type AnyService = Record<string, (...args: unknown[]) => unknown>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export async function dispatchCall<
 | 
					export async function dispatchCall<S extends Partial<AnyService>>(
 | 
				
			||||||
  S extends AnyService,
 | 
					  service: S,
 | 
				
			||||||
>(service: S, event: MessageEvent<Call<unknown>>) {
 | 
					  event: MessageEvent<Call<unknown>> & { source: MessageEventSource },
 | 
				
			||||||
 | 
					) {
 | 
				
			||||||
  try {
 | 
					  try {
 | 
				
			||||||
    const fn = service[event.data.method];
 | 
					    const fn = service[event.data.method];
 | 
				
			||||||
    if (!fn) {
 | 
					    if (!fn) {
 | 
				
			||||||
 | 
					      console.warn("requested unknown method", event.data.method, event.data);
 | 
				
			||||||
      if (event.data.id)
 | 
					      if (event.data.id)
 | 
				
			||||||
        return event.source.postMessage({
 | 
					        return event.source.postMessage({
 | 
				
			||||||
          jsonrpc: "2.0",
 | 
					          jsonrpc: "2.0",
 | 
				
			||||||
| 
						 | 
					@ -176,10 +183,12 @@ export async function dispatchCall<
 | 
				
			||||||
            message: "Method not found",
 | 
					            message: "Method not found",
 | 
				
			||||||
          },
 | 
					          },
 | 
				
			||||||
        } as Result<void, void>);
 | 
					        } as Result<void, void>);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      return;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    try {
 | 
					    try {
 | 
				
			||||||
      const result = await fn(...event.data.params as unknown[]);
 | 
					      const result = await fn(...(event.data.params as unknown[]));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      if (!event.data.id) return;
 | 
					      if (!event.data.id) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,20 +1,11 @@
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
  "compilerOptions": {
 | 
					  "compilerOptions": {
 | 
				
			||||||
    "strict": true,
 | 
					 | 
				
			||||||
    "target": "ESNext",
 | 
					 | 
				
			||||||
    "module": "esnext",
 | 
					 | 
				
			||||||
    "moduleResolution": "node",
 | 
					 | 
				
			||||||
    "allowSyntheticDefaultImports": true,
 | 
					 | 
				
			||||||
    "esModuleInterop": true,
 | 
					 | 
				
			||||||
    "jsx": "preserve",
 | 
					    "jsx": "preserve",
 | 
				
			||||||
    "jsxImportSource": "solid-js",
 | 
					    "jsxImportSource": "solid-js",
 | 
				
			||||||
    "types": ["vite/client", "vite-plugin-pwa/solid"],
 | 
					    "types": ["vite/client", "vite-plugin-pwa/solid"],
 | 
				
			||||||
    "noEmit": true,
 | 
					    "lib": ["ESNext", "DOM", "DOM.Iterable"]
 | 
				
			||||||
    "isolatedModules": true,
 | 
					  },
 | 
				
			||||||
    "resolveJsonModule": true,
 | 
					  "include": ["./src/**/*.ts", "./src/**/*.tsx", "./*.ts", "./types/**.ts"],
 | 
				
			||||||
    "paths": {
 | 
					  "exclude": ["./src/serviceworker/**"],
 | 
				
			||||||
      "~platform/*": ["./src/platform/*"],
 | 
					  "extends": ["./tsconfig.super.json"]
 | 
				
			||||||
      "~material/*": ["./src/material/*"]
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										17
									
								
								tsconfig.super.json
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								tsconfig.super.json
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,17 @@
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					  "compilerOptions": {
 | 
				
			||||||
 | 
					    "strict": true,
 | 
				
			||||||
 | 
					    "target": "ESNext",
 | 
				
			||||||
 | 
					    "module": "esnext",
 | 
				
			||||||
 | 
					    "moduleResolution": "node",
 | 
				
			||||||
 | 
					    "allowSyntheticDefaultImports": true,
 | 
				
			||||||
 | 
					    "esModuleInterop": true,
 | 
				
			||||||
 | 
					    "noEmit": true,
 | 
				
			||||||
 | 
					    "isolatedModules": true,
 | 
				
			||||||
 | 
					    "resolveJsonModule": true,
 | 
				
			||||||
 | 
					    "paths": {
 | 
				
			||||||
 | 
					      "~platform/*": ["./src/platform/*"],
 | 
				
			||||||
 | 
					      "~material/*": ["./src/material/*"]
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										26
									
								
								types/lib.esnext.promise.d.ts
									
										
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								types/lib.esnext.promise.d.ts
									
										
									
									
										vendored
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,26 @@
 | 
				
			||||||
 | 
					interface AnyPromiseWithResolvers<T, Instance> {
 | 
				
			||||||
 | 
					  promise: Instance;
 | 
				
			||||||
 | 
					  resolve: (value: T | PromiseLike<T>) => void;
 | 
				
			||||||
 | 
					  reject: (reason?: any) => void;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type AnyPromiseConstructor<T> = new (
 | 
				
			||||||
 | 
					  executor: (
 | 
				
			||||||
 | 
					    resolve: PromiseWithResolvers<T>["resolve"],
 | 
				
			||||||
 | 
					    reject: PromiseWithResolvers<T>["reject"],
 | 
				
			||||||
 | 
					  ) => void,
 | 
				
			||||||
 | 
					) => Promise<T>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					interface PromiseConstructor {
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					   * Creates a new Promise and returns it in an object, along with its resolve and reject functions.
 | 
				
			||||||
 | 
					   * @returns An object with the properties `promise`, `resolve`, and `reject`.
 | 
				
			||||||
 | 
					   *
 | 
				
			||||||
 | 
					   * ```ts
 | 
				
			||||||
 | 
					   * const { promise, resolve, reject } = Promise.withResolvers<T>();
 | 
				
			||||||
 | 
					   * ```
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					  withResolvers<T, K extends AnyPromiseConstructor<T>>(
 | 
				
			||||||
 | 
					    this: K,
 | 
				
			||||||
 | 
					  ): AnyPromiseWithResolvers<T, InstanceType<K>>;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue