Files
clawtap/src/sw.ts
T
kuannnn 42861ea7fa feat: ClawTap v0.1.0 — initial release
Multi-adapter mobile UI for AI coding assistants.
Supports Claude Code, Codex CLI, and Gemini CLI through one interface.

Features:
- Real-time bidirectional sync via tmux + WebSocket
- Cross-AI review (send one AI's output to another for review)
- Multi-review tabs with minimize/expand
- Push notifications (PWA) with smart session-aware filtering
- Three-channel event system (hooks, file watcher, pane monitor)
- Voice input, image paste, draft persistence
- Terminal-native design (JetBrains Mono, dark theme, pixel art claw)
- CLI with --adapter flag on every command
- Zero-overhead fire-and-forget hooks
2026-03-26 10:40:26 +08:00

60 lines
2.1 KiB
TypeScript

/// <reference lib="webworker" />
import { precacheAndRoute } from 'workbox-precaching';
declare const self: ServiceWorkerGlobalScope;
// Precache static assets (injected by vite-plugin-pwa at build time)
precacheAndRoute(self.__WB_MANIFEST);
// Push notification handler — server already filters by clientCount,
// so we always show the notification if one is received.
self.addEventListener('push', (event) => {
if (!event.data) return;
const payload = event.data.json();
event.waitUntil((async () => {
// Update badge (handle 0 explicitly to clear)
const badgeValue = payload.data?.badge;
if (typeof badgeValue === 'number') {
badgeValue > 0
? await navigator.setAppBadge?.(badgeValue)
: await navigator.clearAppBadge?.();
}
// Forward to app clients for real-time UI updates (e.g. pending badges)
const allClients = await self.clients.matchAll({ type: 'window', includeUncontrolled: true });
for (const c of allClients) {
c.postMessage({ type: 'PUSH_RECEIVED', sessionId: payload.data?.sessionId, badge: badgeValue });
}
// Silent push (no title) — badge-only update, don't show notification
if (!payload.title) return;
// Always show notification — server already filtered by clientCount
return self.registration.showNotification(payload.title, {
body: payload.body,
icon: '/pwa-192x192.png',
badge: '/pwa-192x192.png',
tag: payload.tag || payload.data?.sessionId || 'default',
data: payload.data,
});
})());
});
// Notification click — open app and navigate to session
self.addEventListener('notificationclick', (event) => {
event.notification.close();
const sessionId = event.notification.data?.sessionId;
const url = sessionId ? `/?session=${sessionId}` : '/';
event.waitUntil(
self.clients.matchAll({ type: 'window', includeUncontrolled: true }).then(clients => {
for (const c of clients) {
if ('focus' in c) {
c.postMessage({ type: 'OPEN_SESSION', sessionId });
return (c as WindowClient).focus();
}
}
return self.clients.openWindow(url);
})
);
});