@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.
- package/dist/meshwhisper-sw.js +56 -0
- package/package.json +18 -0
- package/src/index.ts +93 -0
|
@@ -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
|
+
});
|