remove sw response handling

This commit is contained in:
midzelis
2026-01-23 15:44:20 +00:00
parent aa44f2e1d4
commit c0a3673ecf
2 changed files with 12 additions and 156 deletions

View File

@@ -1,19 +1,10 @@
import { ServiceWorkerMessenger } from './sw-messenger';
const messenger = new ServiceWorkerMessenger();
let isServiceWorkerEnabled = true;
messenger.onAckTimeout(() => {
if (!isServiceWorkerEnabled) {
return;
}
console.error('[ServiceWorker] No communication detected. Auto-disabled service worker.');
isServiceWorkerEnabled = false;
});
const hasServiceWorker = globalThis.isSecureContext && 'serviceWorker' in navigator;
const isValidSwContext = (url: string | undefined | null): url is string => {
return globalThis.isSecureContext && isServiceWorkerEnabled && !!url;
return hasServiceWorker && !!url;
};
export function cancelImageUrl(url: string | undefined | null) {

View File

@@ -1,157 +1,22 @@
/**
* Low-level protocol for communicating with the service worker via postMessage.
*
* Protocol:
* 1. Main thread sends request: { type: string, requestId: string, ...data }
* 2. SW sends ack: { type: 'ack', requestId: string }
* 3. SW sends response (optional): { type: 'response', requestId: string, result?: any, error?: string }
*/
interface PendingRequest {
resolveAck: () => void;
resolveResponse?: (result: unknown) => void;
rejectResponse?: (error: Error) => void;
ackTimeout: ReturnType<typeof setTimeout>;
ackReceived: boolean;
}
export class ServiceWorkerMessenger {
readonly #pendingRequests = new Map<string, PendingRequest>();
readonly #ackTimeoutMs: number;
#requestCounter = 0;
#onTimeout?: (type: string, data: Record<string, unknown>) => void;
#messageHandler?: (event: MessageEvent) => void;
constructor() {}
constructor(ackTimeoutMs = 5000) {
this.#ackTimeoutMs = ackTimeoutMs;
// Listen for messages from the service worker
if ('serviceWorker' in navigator) {
this.#messageHandler = (event) => {
this.#handleMessage(event.data);
};
navigator.serviceWorker.addEventListener('message', this.#messageHandler);
}
}
#handleMessage(data: unknown) {
if (typeof data !== 'object' || data === null) {
return;
#sendInternal(type: string, data: Record<string, unknown>) {
if (!('serviceWorker' in navigator)) {
throw new Error('Service Worker not enabled in this environment ');
}
const message = data as { requestId?: string; type?: string; error?: string; result?: unknown };
const requestId = message.requestId;
if (!requestId) {
return;
}
const pending = this.#pendingRequests.get(requestId);
if (!pending) {
return;
}
if (message.type === 'ack') {
pending.ackReceived = true;
clearTimeout(pending.ackTimeout);
pending.resolveAck();
return;
}
if (message.type === 'response') {
clearTimeout(pending.ackTimeout);
this.#pendingRequests.delete(requestId);
if (message.error) {
pending.rejectResponse?.(new Error(message.error));
return;
}
pending.resolveResponse?.(message.result);
}
}
/**
* Set a callback to be invoked when an ack timeout occurs.
* This can be used to detect and disable faulty service workers.
*/
onAckTimeout(callback: (type: string, data: Record<string, unknown>) => void): void {
this.#onTimeout = callback;
}
/**
* Send a message to the service worker.
* - send(): waits for ack, resolves when acknowledged
* - request(): waits for response, throws on error/timeout
*/
#sendInternal<T>(type: string, data: Record<string, unknown>, waitForResponse: boolean): Promise<T> {
const requestId = `${type}-${++this.#requestCounter}-${Date.now()}`;
const promise = new Promise<T>((resolve, reject) => {
const ackTimeout = setTimeout(() => {
const pending = this.#pendingRequests.get(requestId);
if (pending && !pending.ackReceived) {
this.#pendingRequests.delete(requestId);
console.warn(`[ServiceWorker] ${type} request not acknowledged:`, data);
this.#onTimeout?.(type, data);
// Only reject if we're waiting for a response
if (waitForResponse) {
reject(new Error(`Service worker did not acknowledge ${type} request`));
} else {
resolve(undefined as T);
}
}
}, this.#ackTimeoutMs);
this.#pendingRequests.set(requestId, {
resolveAck: waitForResponse ? () => {} : () => resolve(undefined as T),
resolveResponse: waitForResponse ? (result: unknown) => resolve(result as T) : undefined,
rejectResponse: waitForResponse ? reject : undefined,
ackTimeout,
ackReceived: false,
});
// Send message to the active service worker
// Feature detection is done in constructor and at call sites (sw-messaging.ts:isValidSwContext)
// eslint-disable-next-line compat/compat
navigator.serviceWorker.controller?.postMessage({
type,
requestId,
...data,
});
// eslint-disable-next-line compat/compat
navigator.serviceWorker.controller?.postMessage({
type,
...data,
});
return promise;
}
/**
* Send a one-way message to the service worker.
* Returns a promise that resolves after the service worker acknowledges receipt.
* Resolves even if no ack is received within the timeout period.
*/
send(type: string, data: Record<string, unknown>): Promise<void> {
return this.#sendInternal<void>(type, data, false);
}
/**
* Send a request and wait for ack + response.
* Returns a promise that resolves with the response data or rejects on error/timeout.
*/
request<T = void>(type: string, data: Record<string, unknown>): Promise<T> {
return this.#sendInternal<T>(type, data, true);
}
/**
* Clean up pending requests and remove event listener
*/
close(): void {
for (const pending of this.#pendingRequests.values()) {
clearTimeout(pending.ackTimeout);
}
this.#pendingRequests.clear();
if (this.#messageHandler && 'serviceWorker' in navigator) {
navigator.serviceWorker.removeEventListener('message', this.#messageHandler);
this.#messageHandler = undefined;
}
send(type: string, data: Record<string, unknown>) {
return this.#sendInternal(type, data);
}
}