@meshwhisper/service-worker 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,56 @@
1
+ "use strict";
2
+ (() => {
3
+ // src/index.ts
4
+ self.addEventListener("push", (event) => {
5
+ let destHash = "";
6
+ if (event.data) {
7
+ try {
8
+ const data = event.data.json();
9
+ if (data.type === "meshwhisper:wake" && data.destHash) {
10
+ destHash = data.destHash;
11
+ }
12
+ } catch {
13
+ }
14
+ }
15
+ event.waitUntil(wakeApp(destHash));
16
+ });
17
+ async function wakeApp(destHash) {
18
+ const clients = await self.clients.matchAll({
19
+ type: "window",
20
+ includeUncontrolled: true
21
+ });
22
+ if (clients.length > 0) {
23
+ for (const client of clients) {
24
+ client.postMessage({ type: "meshwhisper:wake", destHash });
25
+ }
26
+ return;
27
+ }
28
+ await self.registration.showNotification("New message", {
29
+ body: "Open the app to read your messages.",
30
+ tag: "meshwhisper-wake",
31
+ // collapse multiple wakes into one notification
32
+ renotify: false,
33
+ silent: true
34
+ // silent — user will see it when they check their device
35
+ });
36
+ }
37
+ self.addEventListener("notificationclick", (event) => {
38
+ event.notification.close();
39
+ event.waitUntil(
40
+ self.clients.matchAll({ type: "window", includeUncontrolled: true }).then((clients) => {
41
+ const existing = clients.find((c) => c.url && "focus" in c);
42
+ if (existing) {
43
+ return existing.focus();
44
+ }
45
+ return self.clients.openWindow("/");
46
+ })
47
+ );
48
+ });
49
+ self.addEventListener("message", (event) => {
50
+ if (event.data?.type === "meshwhisper:ack") {
51
+ self.registration.getNotifications({ tag: "meshwhisper-wake" }).then((notes) => {
52
+ notes.forEach((n) => n.close());
53
+ });
54
+ }
55
+ });
56
+ })();
package/package.json ADDED
@@ -0,0 +1,18 @@
1
+ {
2
+ "name": "@meshwhisper/service-worker",
3
+ "version": "0.1.0",
4
+ "description": "MeshWhisper service worker — handles Web Push wake events for PWAs",
5
+ "type": "module",
6
+ "main": "dist/meshwhisper-sw.js",
7
+ "files": ["dist", "src"],
8
+ "publishConfig": { "access": "public" },
9
+ "scripts": {
10
+ "prepublishOnly": "npm run build",
11
+ "build": "esbuild src/index.ts --bundle --platform=browser --outfile=dist/meshwhisper-sw.js --format=iife"
12
+ },
13
+ "devDependencies": {
14
+ "esbuild": "^0.20.0",
15
+ "typescript": "^5.7.0"
16
+ },
17
+ "license": "MIT"
18
+ }
package/src/index.ts ADDED
@@ -0,0 +1,93 @@
1
+ // ============================================================
2
+ // MeshWhisper Service Worker
3
+ // Handles Web Push wake events and routes them to open app
4
+ // windows, or shows a generic "new message" notification when
5
+ // the app is closed.
6
+ //
7
+ // Installation (in your app's service worker registration):
8
+ // navigator.serviceWorker.register('/meshwhisper-sw.js');
9
+ //
10
+ // Or if you already have a service worker, import this inline:
11
+ // import '@meshwhisper/service-worker';
12
+ //
13
+ // The SDK auto-registers the push subscription when you pass
14
+ // push: { platform: 'webpush', subscription } to MeshWhisper.init().
15
+ // ============================================================
16
+
17
+ declare const self: ServiceWorkerGlobalScope;
18
+
19
+ // ---- Push event ----
20
+ // The payload is { type: 'meshwhisper:wake', destHash }.
21
+ // E2EE content never passes through here — we just wake the app.
22
+
23
+ self.addEventListener('push', (event: PushEvent) => {
24
+ let destHash = '';
25
+
26
+ if (event.data) {
27
+ try {
28
+ const data = event.data.json() as { type?: string; destHash?: string };
29
+ if (data.type === 'meshwhisper:wake' && data.destHash) {
30
+ destHash = data.destHash;
31
+ }
32
+ } catch {
33
+ // Malformed payload — still wake the app
34
+ }
35
+ }
36
+
37
+ event.waitUntil(wakeApp(destHash));
38
+ });
39
+
40
+ async function wakeApp(destHash: string): Promise<void> {
41
+ const clients = await self.clients.matchAll({
42
+ type: 'window',
43
+ includeUncontrolled: true,
44
+ });
45
+
46
+ if (clients.length > 0) {
47
+ // App is open — tell it to pull new messages
48
+ for (const client of clients) {
49
+ client.postMessage({ type: 'meshwhisper:wake', destHash });
50
+ }
51
+ return;
52
+ }
53
+
54
+ // App is closed — show a generic notification so the user knows
55
+ // to open the app. The actual message content is never shown here
56
+ // because we can't decrypt it without the ratchet state.
57
+ await self.registration.showNotification('New message', {
58
+ body: 'Open the app to read your messages.',
59
+ tag: 'meshwhisper-wake', // collapse multiple wakes into one notification
60
+ renotify: false,
61
+ silent: true, // silent — user will see it when they check their device
62
+ });
63
+ }
64
+
65
+ // ---- Notification click ----
66
+ // Clicking the "New message" notification opens (or focuses) the app.
67
+
68
+ self.addEventListener('notificationclick', (event: NotificationClickEvent) => {
69
+ event.notification.close();
70
+
71
+ event.waitUntil(
72
+ self.clients
73
+ .matchAll({ type: 'window', includeUncontrolled: true })
74
+ .then((clients) => {
75
+ const existing = clients.find((c) => c.url && 'focus' in c);
76
+ if (existing) {
77
+ return (existing as WindowClient).focus();
78
+ }
79
+ return self.clients.openWindow('/');
80
+ }),
81
+ );
82
+ });
83
+
84
+ // ---- Message from app ----
85
+ // The app can confirm wake receipt to cancel the fallback notification.
86
+
87
+ self.addEventListener('message', (event: ExtendableMessageEvent) => {
88
+ if ((event.data as { type?: string })?.type === 'meshwhisper:ack') {
89
+ self.registration.getNotifications({ tag: 'meshwhisper-wake' }).then((notes) => {
90
+ notes.forEach((n) => n.close());
91
+ });
92
+ }
93
+ });