atagen

malice

public
Star0
Watch0
984f84f9bf6c54c04625b38858111a7760036f51
2
ssh://brewery@pls.lobotomise.me:33/atagen/malice.git

Blame: web/src/sw-push.ts

235ce394f90datagen2026-10-26 06:54 UTC1
import { PUSH_CHANGED_MESSAGE, PUSH_CHANNEL, parsePush, parseResolved, shouldSilence, subscriptionKeys, type PushDisplay } from './lib/push-model';
235ce394f90datagen2026-10-26 06:54 UTC2
235ce394f90datagen2026-10-26 06:54 UTC3
const RESOLVE_TIMEOUT_MS = 3000;
235ce394f90datagen2026-10-26 06:54 UTC4
235ce394f90datagen2026-10-26 06:54 UTC5
declare const __MALICE_DEVICE__: boolean;
235ce394f90datagen2026-10-26 06:54 UTC6
235ce394f90datagen2026-10-26 06:54 UTC7
interface BadgeNavigator {
235ce394f90datagen2026-10-26 06:54 UTC8
  setAppBadge?: (n: number) => Promise<void>;
235ce394f90datagen2026-10-26 06:54 UTC9
  clearAppBadge?: () => Promise<void>;
235ce394f90datagen2026-10-26 06:54 UTC10
}
235ce394f90datagen2026-10-26 06:54 UTC11
235ce394f90datagen2026-10-26 06:54 UTC12
let channel: BroadcastChannel | undefined;
235ce394f90datagen2026-10-26 06:54 UTC13
let nextRequestId = 0;
235ce394f90datagen2026-10-26 06:54 UTC14
const pending = new Map<string, (reply: { json?: unknown; error?: unknown }) => void>();
235ce394f90datagen2026-10-26 06:54 UTC15
235ce394f90datagen2026-10-26 06:54 UTC16
function resolveChannel(): BroadcastChannel {
235ce394f90datagen2026-10-26 06:54 UTC17
  if (!channel) {
235ce394f90datagen2026-10-26 06:54 UTC18
    channel = new BroadcastChannel(PUSH_CHANNEL);
235ce394f90datagen2026-10-26 06:54 UTC19
    channel.onmessage = (ev: MessageEvent) => {
235ce394f90datagen2026-10-26 06:54 UTC20
      const msg = ev.data as { id?: unknown; json?: unknown; error?: unknown } | null;
235ce394f90datagen2026-10-26 06:54 UTC21
      if (typeof msg?.id !== 'string') return;
235ce394f90datagen2026-10-26 06:54 UTC22
      const done = pending.get(msg.id);
235ce394f90datagen2026-10-26 06:54 UTC23
      if (!done) return;
235ce394f90datagen2026-10-26 06:54 UTC24
      pending.delete(msg.id);
235ce394f90datagen2026-10-26 06:54 UTC25
      done(msg);
235ce394f90datagen2026-10-26 06:54 UTC26
    };
235ce394f90datagen2026-10-26 06:54 UTC27
  }
235ce394f90datagen2026-10-26 06:54 UTC28
  return channel;
235ce394f90datagen2026-10-26 06:54 UTC29
}
235ce394f90datagen2026-10-26 06:54 UTC30
235ce394f90datagen2026-10-26 06:54 UTC31
function requestResolve(roomId: string, eventId: string): Promise<unknown | null> {
235ce394f90datagen2026-10-26 06:54 UTC32
  const id = `${Date.now()}-${nextRequestId++}`;
235ce394f90datagen2026-10-26 06:54 UTC33
  return new Promise((resolve) => {
235ce394f90datagen2026-10-26 06:54 UTC34
    const timer = setTimeout(() => {
235ce394f90datagen2026-10-26 06:54 UTC35
      pending.delete(id);
235ce394f90datagen2026-10-26 06:54 UTC36
      resolve(null);
235ce394f90datagen2026-10-26 06:54 UTC37
    }, RESOLVE_TIMEOUT_MS);
235ce394f90datagen2026-10-26 06:54 UTC38
    pending.set(id, (reply) => {
235ce394f90datagen2026-10-26 06:54 UTC39
      clearTimeout(timer);
235ce394f90datagen2026-10-26 06:54 UTC40
      resolve(reply.error ? null : (reply.json ?? null));
235ce394f90datagen2026-10-26 06:54 UTC41
    });
235ce394f90datagen2026-10-26 06:54 UTC42
    resolveChannel().postMessage({ id, kind: 'resolve', room_id: roomId, event_id: eventId });
235ce394f90datagen2026-10-26 06:54 UTC43
  });
235ce394f90datagen2026-10-26 06:54 UTC44
}
235ce394f90datagen2026-10-26 06:54 UTC45
235ce394f90datagen2026-10-26 06:54 UTC46
function show(sw: ServiceWorkerGlobalScope, display: PushDisplay, title: string, body: string, silent: boolean): Promise<void> {
235ce394f90datagen2026-10-26 06:54 UTC47
  const options: NotificationOptions & { renotify?: boolean } = {
235ce394f90datagen2026-10-26 06:54 UTC48
    body,
235ce394f90datagen2026-10-26 06:54 UTC49
    tag: display.tag,
235ce394f90datagen2026-10-26 06:54 UTC50
    silent,
235ce394f90datagen2026-10-26 06:54 UTC51
    renotify: display.mention && !silent,
235ce394f90datagen2026-10-26 06:54 UTC52
    data: { roomId: display.roomId, navigate: display.navigate, eventId: display.eventId }
235ce394f90datagen2026-10-26 06:54 UTC53
  };
235ce394f90datagen2026-10-26 06:54 UTC54
  return sw.registration.showNotification(title, options);
235ce394f90datagen2026-10-26 06:54 UTC55
}
235ce394f90datagen2026-10-26 06:54 UTC56
235ce394f90datagen2026-10-26 06:54 UTC57
function setBadge(sw: ServiceWorkerGlobalScope, count: number | null): void {
235ce394f90datagen2026-10-26 06:54 UTC58
  if (count === null) return;
235ce394f90datagen2026-10-26 06:54 UTC59
  const nav = sw.navigator as unknown as BadgeNavigator;
235ce394f90datagen2026-10-26 06:54 UTC60
  const call = count > 0 ? nav.setAppBadge?.(count) : nav.clearAppBadge?.();
235ce394f90datagen2026-10-26 06:54 UTC61
  void call?.catch(() => undefined);
235ce394f90datagen2026-10-26 06:54 UTC62
}
235ce394f90datagen2026-10-26 06:54 UTC63
235ce394f90datagen2026-10-26 06:54 UTC64
async function handle(sw: ServiceWorkerGlobalScope, event: PushEvent): Promise<void> {
235ce394f90datagen2026-10-26 06:54 UTC65
  const display = parsePush(event.data?.text() ?? null, sw.location.origin);
235ce394f90datagen2026-10-26 06:54 UTC66
  const windows = await sw.clients.matchAll({ type: 'window', includeUncontrolled: true });
235ce394f90datagen2026-10-26 06:54 UTC67
  const focused = windows.some((client) => client.focused && client.visibilityState === 'visible');
235ce394f90datagen2026-10-26 06:54 UTC68
  await show(sw, display, display.title, display.body, shouldSilence(display, focused));
235ce394f90datagen2026-10-26 06:54 UTC69
  setBadge(sw, display.badge);
235ce394f90datagen2026-10-26 06:54 UTC70
  if (!__MALICE_DEVICE__ || windows.length === 0 || !display.roomId || !display.eventId) return;
235ce394f90datagen2026-10-26 06:54 UTC71
  const resolved = parseResolved(await requestResolve(display.roomId, display.eventId), display);
235ce394f90datagen2026-10-26 06:54 UTC72
  if (resolved) await show(sw, display, resolved.title, resolved.body, true);
235ce394f90datagen2026-10-26 06:54 UTC73
}
235ce394f90datagen2026-10-26 06:54 UTC74
235ce394f90datagen2026-10-26 06:54 UTC75
/** Browsers revoke the subscription when a push shows nothing, so every failure still shows a generic notification. */
235ce394f90datagen2026-10-26 06:54 UTC76
export function onPush(sw: ServiceWorkerGlobalScope, event: PushEvent): void {
235ce394f90datagen2026-10-26 06:54 UTC77
  event.waitUntil(
235ce394f90datagen2026-10-26 06:54 UTC78
    handle(sw, event).catch(() =>
235ce394f90datagen2026-10-26 06:54 UTC79
      sw.registration.showNotification('New message', { tag: 'malice', data: {} }).catch(() => undefined)
235ce394f90datagen2026-10-26 06:54 UTC80
    )
235ce394f90datagen2026-10-26 06:54 UTC81
  );
235ce394f90datagen2026-10-26 06:54 UTC82
}
235ce394f90datagen2026-10-26 06:54 UTC83
235ce394f90datagen2026-10-26 06:54 UTC84
export function onSubscriptionChange(sw: ServiceWorkerGlobalScope, event: Event): void {
235ce394f90datagen2026-10-26 06:54 UTC85
  const change = event as Event & { oldSubscription?: PushSubscription | null; newSubscription?: PushSubscription | null };
235ce394f90datagen2026-10-26 06:54 UTC86
  (event as ExtendableEvent).waitUntil(
235ce394f90datagen2026-10-26 06:54 UTC87
    (async () => {
235ce394f90datagen2026-10-26 06:54 UTC88
      let sub = change.newSubscription ?? (await sw.registration.pushManager.getSubscription());
235ce394f90datagen2026-10-26 06:54 UTC89
      const key = change.oldSubscription?.options.applicationServerKey;
235ce394f90datagen2026-10-26 06:54 UTC90
      if (!sub && key) sub = await sw.registration.pushManager.subscribe({ userVisibleOnly: true, applicationServerKey: key });
235ce394f90datagen2026-10-26 06:54 UTC91
      const keys = sub ? subscriptionKeys(sub) : null;
235ce394f90datagen2026-10-26 06:54 UTC92
      if (keys && !__MALICE_DEVICE__) {
235ce394f90datagen2026-10-26 06:54 UTC93
        await fetch('/api/push/subscribe', {
235ce394f90datagen2026-10-26 06:54 UTC94
          method: 'POST',
235ce394f90datagen2026-10-26 06:54 UTC95
          credentials: 'same-origin',
235ce394f90datagen2026-10-26 06:54 UTC96
          headers: { 'content-type': 'application/json' },
235ce394f90datagen2026-10-26 06:54 UTC97
          body: JSON.stringify({ endpoint: keys.endpoint, keys: { p256dh: keys.p256dh, auth: keys.auth } })
235ce394f90datagen2026-10-26 06:54 UTC98
        }).catch(() => undefined);
235ce394f90datagen2026-10-26 06:54 UTC99
      }
235ce394f90datagen2026-10-26 06:54 UTC100
      for (const client of await sw.clients.matchAll({ type: 'window', includeUncontrolled: true })) {
235ce394f90datagen2026-10-26 06:54 UTC101
        client.postMessage({ type: PUSH_CHANGED_MESSAGE });
235ce394f90datagen2026-10-26 06:54 UTC102
      }
235ce394f90datagen2026-10-26 06:54 UTC103
    })().catch(() => undefined)
235ce394f90datagen2026-10-26 06:54 UTC104
  );
235ce394f90datagen2026-10-26 06:54 UTC105
}