perf - replace broadcast channel with direct postMessage

This commit is contained in:
midzelis
2026-01-16 04:11:48 +00:00
parent a476c025c9
commit 70df21277e
3 changed files with 37 additions and 25 deletions

View File

@@ -1,5 +1,5 @@
/**
* Low-level protocol for communicating with the service worker via BroadcastChannel.
* Low-level protocol for communicating with the service worker via postMessage.
*
* Protocol:
* 1. Main thread sends request: { type: string, requestId: string, ...data }
@@ -16,19 +16,20 @@ interface PendingRequest {
}
export class ServiceWorkerMessenger {
readonly #broadcast: BroadcastChannel;
readonly #pendingRequests = new Map<string, PendingRequest>();
readonly #ackTimeoutMs: number;
#requestCounter = 0;
#onTimeout?: (type: string, data: Record<string, unknown>) => void;
constructor(channelName: string, ackTimeoutMs = 5000) {
this.#broadcast = new BroadcastChannel(channelName);
constructor(_channelName: string, ackTimeoutMs = 5000) {
this.#ackTimeoutMs = ackTimeoutMs;
this.#broadcast.addEventListener('message', (event) => {
this.#handleMessage(event.data);
});
// Listen for messages from the service worker
if ('serviceWorker' in navigator) {
navigator.serviceWorker.addEventListener('message', (event) => {
this.#handleMessage(event.data);
});
}
}
#handleMessage(data: unknown) {
@@ -107,7 +108,10 @@ export class ServiceWorkerMessenger {
ackReceived: false,
});
this.#broadcast.postMessage({
// 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,
@@ -135,9 +139,12 @@ export class ServiceWorkerMessenger {
}
/**
* Close the broadcast channel
* Clean up pending requests
*/
close(): void {
this.#broadcast.close();
for (const pending of this.#pendingRequests.values()) {
clearTimeout(pending.ackTimeout);
}
this.#pendingRequests.clear();
}
}

View File

@@ -2,7 +2,7 @@
/// <reference no-default-lib="true"/>
/// <reference lib="esnext" />
/// <reference lib="webworker" />
import { installBroadcastChannelListener } from './broadcast-channel';
import { installMessageListener } from './messaging';
import { handleFetch as handleAssetFetch } from './request';
const ASSET_REQUEST_REGEX = /^\/api\/assets\/[a-f0-9-]+\/(original|thumbnail)/;
@@ -34,4 +34,4 @@ const handleFetch = (event: FetchEvent): void => {
sw.addEventListener('install', handleInstall, { passive: true });
sw.addEventListener('activate', handleActivate, { passive: true });
sw.addEventListener('fetch', handleFetch, { passive: true });
installBroadcastChannelListener();
installMessageListener();

View File

@@ -1,10 +1,12 @@
import { handleCancel, handlePrepare } from './request';
const sw = globalThis as unknown as ServiceWorkerGlobalScope;
/**
* Send acknowledgment for a request
*/
function sendAck(broadcast: BroadcastChannel, requestId: string) {
broadcast.postMessage({
function sendAck(client: Client, requestId: string) {
client.postMessage({
type: 'ack',
requestId,
});
@@ -13,23 +15,21 @@ function sendAck(broadcast: BroadcastChannel, requestId: string) {
/**
* Handle 'prepare' request: prepare SW to track this request for cancelation
*/
const handlePrepareRequest = (broadcast: BroadcastChannel, url: URL, requestId: string) => {
sendAck(broadcast, requestId);
const handlePrepareRequest = (client: Client, url: URL, requestId: string) => {
sendAck(client, requestId);
handlePrepare(url);
};
/**
* Handle 'cancel' request: cancel a pending request
*/
const handleCancelRequest = (broadcast: BroadcastChannel, url: URL, requestId: string) => {
sendAck(broadcast, requestId);
const handleCancelRequest = (client: Client, url: URL, requestId: string) => {
sendAck(client, requestId);
handleCancel(url);
};
export const installBroadcastChannelListener = () => {
const broadcast = new BroadcastChannel('immich');
// eslint-disable-next-line unicorn/prefer-add-event-listener
broadcast.onmessage = (event) => {
export const installMessageListener = () => {
sw.addEventListener('message', (event) => {
if (!event.data?.requestId) {
return;
}
@@ -40,16 +40,21 @@ export const installBroadcastChannelListener = () => {
return;
}
const client = event.source as Client;
if (!client) {
return;
}
switch (event.data.type) {
case 'prepare': {
handlePrepareRequest(broadcast, url, requestId);
handlePrepareRequest(client, url, requestId);
break;
}
case 'cancel': {
handleCancelRequest(broadcast, url, requestId);
handleCancelRequest(client, url, requestId);
break;
}
}
};
});
};