UNPKG

data-transport

Version:
151 lines (139 loc) 4.13 kB
import { TransportOptions, TransferableWorker, ListenerOptions, BaseInteraction, } from '../interface'; import { Transport } from '../transport'; import { detectSafari } from '../utils'; // follow issue: https://github.com/microsoft/TypeScript/issues/20595 // workaround: `tsc --skipLibCheck`. declare var self: ServiceWorkerGlobalScope; interface ServiceWorkerClientId extends TransferableWorker { _clientId?: string; } export interface ServiceWorkerClientTransportOptions extends Partial<TransportOptions<TransferableWorker>> { /** * A service worker instance for data transport. */ worker: ServiceWorker; /** * Compatibility with unstable serialization in Safari */ useOnSafari?: boolean; } export interface ServiceWorkerServiceTransportOptions extends Partial<TransportOptions<ServiceWorkerClientId>> { /** * Compatibility with unstable serialization in Safari */ useOnSafari?: boolean; } const DEFAULT_USE_ON_SAFARI = true; const decode = (data: any, useOnSafari: boolean) => { try { return useOnSafari && detectSafari() ? JSON.stringify(data) : data; } catch (e) { console.error(`Failed to stringify:`, data); throw e; } }; const encode = (data: any, useOnSafari: boolean) => { try { return typeof data === 'string' && useOnSafari && detectSafari() ? JSON.parse(data as any) : data; } catch (e) { console.error(`Failed to parse:`, data); } return data; }; export abstract class ServiceWorkerClientTransport< T extends BaseInteraction = any > extends Transport<T> { constructor(_options: ServiceWorkerClientTransportOptions) { const { worker, useOnSafari = DEFAULT_USE_ON_SAFARI, listener = (callback) => { const handler = ({ data, }: MessageEvent<ListenerOptions<TransferableWorker>>) => { callback(encode(data, useOnSafari)); }; navigator.serviceWorker.addEventListener('message', handler); return () => { navigator.serviceWorker.removeEventListener('message', handler); }; }, sender = (message) => { const transfer = message.transfer ?? []; delete message.transfer; worker.postMessage(message, transfer); }, ...options } = _options; super({ ...options, listener, sender, }); } } export abstract class ServiceWorkerServiceTransport< T extends BaseInteraction = any > extends Transport<T> { constructor(_options: ServiceWorkerServiceTransportOptions = {}) { const { useOnSafari = DEFAULT_USE_ON_SAFARI, listener = (callback) => { const handler = ({ data, source }: ExtendableMessageEvent) => { data._clientId = (source as Client).id as string; callback(data); }; self.addEventListener('message', handler); return () => self.removeEventListener('message', handler); }, sender = async (message) => { const transfer = message.transfer || []; delete message.transfer; const data = decode(message, useOnSafari); if (message._clientId) { const client = await self.clients.get(message._clientId); if (!client) { console.warn(`The client "${message._clientId}" is closed.`); return; } delete message._clientId; client.postMessage(data, transfer); return; } const client = message._extra?._client; if (client) { delete message._extra!._client; client.postMessage(data, transfer); return; } self.clients .matchAll() .then((all) => all.map((client) => client.postMessage(data, transfer)) ); }, ...options } = _options; super({ ...options, listener, sender, }); self.addEventListener('activate', (event) => { event.waitUntil(self.clients.claim()); }); } } export const ServiceWorkerTransport = { Client: ServiceWorkerClientTransport, Service: ServiceWorkerServiceTransport, };