@swiftbrowser/web 0.17.7 → 0.17.8
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/node/dist/node/apps.d.ts +5 -0
- package/node/dist/node/apps.d.ts.map +1 -1
- package/node/dist/node/apps.js +3 -0
- package/node/dist/node/apps.js.map +1 -1
- package/node/dist/node/config.d.ts +2 -0
- package/node/dist/node/config.d.ts.map +1 -1
- package/node/dist/node/config.js +2 -0
- package/node/dist/node/config.js.map +1 -1
- package/package.json +1 -1
- package/public/_headers +5 -0
- package/src/hostRequests.ts +12 -1
- package/src/main.ts +6 -1
- package/src/notifications.ts +134 -1
- package/src/protocol.ts +3 -1
- package/src/vite-env.d.ts +2 -0
package/node/dist/node/apps.d.ts
CHANGED
|
@@ -51,6 +51,11 @@ export interface AppSpec {
|
|
|
51
51
|
* page's callback page (docs/ops-protocol.md, "Signing in").
|
|
52
52
|
*/
|
|
53
53
|
urlSchemes?: string[];
|
|
54
|
+
/**
|
|
55
|
+
* The Web Push application server key (a VAPID public key, base64url) the
|
|
56
|
+
* app's `registerForRemoteNotifications` subscribes with (src/notifications.ts).
|
|
57
|
+
*/
|
|
58
|
+
webPushKey?: string;
|
|
54
59
|
/**
|
|
55
60
|
* A CORS proxy (packages/cors-proxy): the page retries a cross-origin GET or
|
|
56
61
|
* HEAD the browser refused through `<corsProxy>?url=<target>` (src/corsProxy.ts).
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"apps.d.ts","sourceRoot":"","sources":["../../apps.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAIH,MAAM,WAAW,OAAO;IACtB;;;OAGG;IACH,IAAI,EAAE,MAAM,CAAC;IACb,6EAA6E;IAC7E,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,0CAA0C;IAC1C,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;OAIG;IACH,IAAI,EAAE,MAAM,CAAC;IACb,gHAAgH;IAChH,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,0FAA0F;IAC1F,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IACjB;;;;OAIG;IACH,KAAK,CAAC,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5B,gHAAgH;IAChH,IAAI,CAAC,EAAE,MAAM,CAAC;IACd;;;;OAIG;IACH,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;IACrB;;;;OAIG;IACH,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB;;;;OAIG;IACH,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;IACtB;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;OAGG;IACH,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;CACzB;AAED,yEAAyE;AACzE,eAAO,MAAM,QAAQ,QAAqB,CAAC;AAE3C,sCAAsC;AACtC,wBAAgB,aAAa,CAAC,IAAI,EAAE,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,aAAa,CAAC,GAAG,MAAM,CAEjF;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,SAAS,OAAO,EAAE,GAAG,IAAI,
|
|
1
|
+
{"version":3,"file":"apps.d.ts","sourceRoot":"","sources":["../../apps.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAIH,MAAM,WAAW,OAAO;IACtB;;;OAGG;IACH,IAAI,EAAE,MAAM,CAAC;IACb,6EAA6E;IAC7E,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,0CAA0C;IAC1C,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;OAIG;IACH,IAAI,EAAE,MAAM,CAAC;IACb,gHAAgH;IAChH,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,0FAA0F;IAC1F,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IACjB;;;;OAIG;IACH,KAAK,CAAC,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5B,gHAAgH;IAChH,IAAI,CAAC,EAAE,MAAM,CAAC;IACd;;;;OAIG;IACH,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;IACrB;;;;OAIG;IACH,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB;;;;OAIG;IACH,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;IACtB;;;OAGG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;OAGG;IACH,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;CACzB;AAED,yEAAyE;AACzE,eAAO,MAAM,QAAQ,QAAqB,CAAC;AAE3C,sCAAsC;AACtC,wBAAgB,aAAa,CAAC,IAAI,EAAE,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,aAAa,CAAC,GAAG,MAAM,CAEjF;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,SAAS,OAAO,EAAE,GAAG,IAAI,CAkDhE"}
|
package/node/dist/node/apps.js
CHANGED
|
@@ -58,6 +58,9 @@ export function validateAppSpecs(specs) {
|
|
|
58
58
|
if (spec.corsProxy !== undefined && (typeof spec.corsProxy !== 'string' || !/^https?:\/\/[^\s]+$/.test(spec.corsProxy))) {
|
|
59
59
|
throw new Error(`${label}: "corsProxy" must be an http(s) URL`);
|
|
60
60
|
}
|
|
61
|
+
if (spec.webPushKey !== undefined && (typeof spec.webPushKey !== 'string' || !/^[A-Za-z0-9_-]{80,96}$/.test(spec.webPushKey))) {
|
|
62
|
+
throw new Error(`${label}: "webPushKey" must be a VAPID public key (base64url, 65 bytes)`);
|
|
63
|
+
}
|
|
61
64
|
if (spec.blockedHosts !== undefined && (!Array.isArray(spec.blockedHosts) || !spec.blockedHosts.every((h) => typeof h === 'string' && /^[A-Za-z0-9.-]+$/.test(h)))) {
|
|
62
65
|
throw new Error(`${label}: "blockedHosts" must be an array of host names`);
|
|
63
66
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"apps.js","sourceRoot":"","sources":["../../apps.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,IAAI,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"apps.js","sourceRoot":"","sources":["../../apps.ts"],"names":[],"mappings":"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"}
|
|
@@ -67,6 +67,8 @@ export interface LandingApp {
|
|
|
67
67
|
urlSchemes?: string[];
|
|
68
68
|
/** The app's CORS proxy (`AppSpec.corsProxy`); the page retries refused cross-origin reads through it. */
|
|
69
69
|
corsProxy?: string;
|
|
70
|
+
/** The app's Web Push application server key (`AppSpec.webPushKey`); the page passes it to the app as `SB_WEB_PUSH_KEY`. */
|
|
71
|
+
webPushKey?: string;
|
|
70
72
|
/** Hosts the app's requests may not reach (`AppSpec.blockedHosts`). */
|
|
71
73
|
blockedHosts?: string[];
|
|
72
74
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../config.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAKH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,MAAM,CAAC;AAKvC,OAAO,EAAmD,KAAK,UAAU,EAAE,KAAK,YAAY,EAAE,MAAM,2BAA2B,CAAC;AAChI,OAAO,EAAE,KAAK,iBAAiB,EAAyC,MAAM,0BAA0B,CAAC;AAEzG,OAAO,EAAmC,KAAK,OAAO,EAAE,MAAM,WAAW,CAAC;AAE1E,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,OAAO,EAAE,CAAC;IAChB,iFAAiF;IACjF,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,+EAA+E;IAC/E,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;;OAIG;IACH,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IACjB;;;OAGG;IACH,MAAM,CAAC,EAAE,UAAU,CAAC;IACpB,sGAAsG;IACtG,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;;;OAIG;IACH,QAAQ,CAAC,EAAE,YAAY,CAAC;IACxB;;;OAGG;IACH,UAAU,CAAC,EAAE,iBAAiB,CAAC;IAC/B;;;OAGG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,kEAAkE;AAClE,wBAAgB,UAAU,CAAC,OAAO,EAAE,IAAI,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,GAAG,QAAQ,CAAC,GAAG,UAAU,CAG9F;AAED,qGAAqG;AACrG,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,wDAAwD;IACxD,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,oHAAoH;IACpH,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,yGAAyG;IACzG,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;IACtB,0GAA0G;IAC1G,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,uEAAuE;IACvE,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;CACzB;AAOD;;;;;GAKG;AACH,wBAAgB,OAAO,IAAI,MAAM,CAoBhC;AAED,gDAAgD;AAChD,wBAAgB,WAAW,CAAC,KAAK,EAAE,SAAS,OAAO,EAAE,GAAG,UAAU,EAAE,
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../config.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAKH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,MAAM,CAAC;AAKvC,OAAO,EAAmD,KAAK,UAAU,EAAE,KAAK,YAAY,EAAE,MAAM,2BAA2B,CAAC;AAChI,OAAO,EAAE,KAAK,iBAAiB,EAAyC,MAAM,0BAA0B,CAAC;AAEzG,OAAO,EAAmC,KAAK,OAAO,EAAE,MAAM,WAAW,CAAC;AAE1E,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,OAAO,EAAE,CAAC;IAChB,iFAAiF;IACjF,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,+EAA+E;IAC/E,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;;OAIG;IACH,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IACjB;;;OAGG;IACH,MAAM,CAAC,EAAE,UAAU,CAAC;IACpB,sGAAsG;IACtG,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;;;OAIG;IACH,QAAQ,CAAC,EAAE,YAAY,CAAC;IACxB;;;OAGG;IACH,UAAU,CAAC,EAAE,iBAAiB,CAAC;IAC/B;;;OAGG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,kEAAkE;AAClE,wBAAgB,UAAU,CAAC,OAAO,EAAE,IAAI,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,GAAG,QAAQ,CAAC,GAAG,UAAU,CAG9F;AAED,qGAAqG;AACrG,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,wDAAwD;IACxD,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,oHAAoH;IACpH,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,yGAAyG;IACzG,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;IACtB,0GAA0G;IAC1G,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,4HAA4H;IAC5H,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,uEAAuE;IACvE,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;CACzB;AAOD;;;;;GAKG;AACH,wBAAgB,OAAO,IAAI,MAAM,CAoBhC;AAED,gDAAgD;AAChD,wBAAgB,WAAW,CAAC,KAAK,EAAE,SAAS,OAAO,EAAE,GAAG,UAAU,EAAE,CAYnE;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,WAAW,GAAG,UAAU,CAoEjE"}
|
package/node/dist/node/config.js
CHANGED
|
@@ -76,6 +76,8 @@ export function landingApps(specs) {
|
|
|
76
76
|
entry.urlSchemes = [...spec.urlSchemes];
|
|
77
77
|
if (spec.corsProxy)
|
|
78
78
|
entry.corsProxy = spec.corsProxy;
|
|
79
|
+
if (spec.webPushKey)
|
|
80
|
+
entry.webPushKey = spec.webPushKey;
|
|
79
81
|
if (spec.blockedHosts?.length)
|
|
80
82
|
entry.blockedHosts = [...spec.blockedHosts];
|
|
81
83
|
return entry;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.js","sourceRoot":"","sources":["../../config.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACnD,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAEzC,OAAO,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAC3D,OAAO,EAAE,aAAa,EAAE,MAAM,8BAA8B,CAAC;AAC7D,OAAO,EAAE,eAAe,EAAE,MAAM,gCAAgC,CAAC;AACjE,OAAO,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,8BAA8B,CAAC;AAC3E,OAAO,EAAE,cAAc,EAAE,gBAAgB,EAAqD,MAAM,2BAA2B,CAAC;AAChI,OAAO,EAA0B,iBAAiB,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AACzG,OAAO,EAAE,SAAS,EAAE,MAAM,0BAA0B,CAAC;AACrD,OAAO,EAAE,aAAa,EAAE,gBAAgB,EAAgB,MAAM,WAAW,CAAC;AAuC1E,kEAAkE;AAClE,MAAM,UAAU,UAAU,CAAC,OAAuD;IAChF,IAAI,OAAO,CAAC,MAAM;QAAE,OAAO,OAAO,CAAC,MAAM,CAAC;IAC1C,OAAO,OAAO,CAAC,IAAI,CAAC,MAAM,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC;AACjF,CAAC;
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../../config.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
package/public/_headers
CHANGED
|
@@ -28,3 +28,8 @@
|
|
|
28
28
|
# plugins/asset-catalogs.ts). Not content-hashed: always revalidate.
|
|
29
29
|
/catalog/*
|
|
30
30
|
Cache-Control: no-cache
|
|
31
|
+
|
|
32
|
+
# The service worker (Web Push): always revalidate, and its scope is the site.
|
|
33
|
+
/sw.js
|
|
34
|
+
Cache-Control: no-cache
|
|
35
|
+
Service-Worker-Allowed: /
|
package/src/hostRequests.ts
CHANGED
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
*/
|
|
19
19
|
|
|
20
20
|
import { base64Decode, base64Encode } from './base64';
|
|
21
|
-
import { browserNotificationHost, performNotification, type NotificationHost, type NotificationResult } from './notifications';
|
|
21
|
+
import { browserNotificationHost, browserPushHost, performNotification, type NotificationHost, type NotificationResult } from './notifications';
|
|
22
22
|
import type {
|
|
23
23
|
BitmapRecipe,
|
|
24
24
|
FetchRequestParams,
|
|
@@ -178,6 +178,17 @@ function downloadBlob(blob: Blob, filename: string): void {
|
|
|
178
178
|
}
|
|
179
179
|
|
|
180
180
|
const notificationHost = browserNotificationHost();
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Gives the notifications Web Push through the service worker at `workerURL`
|
|
184
|
+
* with `scope` (the site's base, which the page knows: main.ts), where the
|
|
185
|
+
* browser has a push service.
|
|
186
|
+
*/
|
|
187
|
+
export function installPushService(workerURL: string, scope: string): void {
|
|
188
|
+
if (!notificationHost || notificationHost.push) return;
|
|
189
|
+
const push = browserPushHost(workerURL, scope);
|
|
190
|
+
if (push) notificationHost.push = push;
|
|
191
|
+
}
|
|
181
192
|
const scannerHost = browserScannerHost();
|
|
182
193
|
const webAuthHost = browserWebAuthHost();
|
|
183
194
|
const mediaHost = browserMediaHost(typeof document !== 'undefined' ? downloadBlob : undefined);
|
package/src/main.ts
CHANGED
|
@@ -18,7 +18,7 @@ import { BrowserHistory } from './browserHistory';
|
|
|
18
18
|
import { Renderer, RendererError } from './renderer';
|
|
19
19
|
import { withBlockedHosts } from './blockedHosts';
|
|
20
20
|
import { withCORSProxy } from './corsProxy';
|
|
21
|
-
import { browserEnvironment } from './hostRequests';
|
|
21
|
+
import { browserEnvironment, installPushService } from './hostRequests';
|
|
22
22
|
import { locateBundleFiles } from './media';
|
|
23
23
|
import { callbackPageURL } from './webAuth';
|
|
24
24
|
import { bitmapStore } from './bitmaps';
|
|
@@ -394,6 +394,11 @@ function launchEnvironment(): string[] {
|
|
|
394
394
|
const schemes = registeredApp()?.urlSchemes;
|
|
395
395
|
if (schemes?.length) env.push(`SB_URL_SCHEMES=${schemes.join(',')}`);
|
|
396
396
|
env.push(`SB_WEB_AUTH_CALLBACK=${callbackPageURL(new URL(import.meta.env.BASE_URL, location.href).href)}`);
|
|
397
|
+
// Web Push (notifications.ts): the service worker next to the page, and
|
|
398
|
+
// the key `registerForRemoteNotifications` subscribes with.
|
|
399
|
+
installPushService(`${import.meta.env.BASE_URL}sw.js`, import.meta.env.BASE_URL);
|
|
400
|
+
const webPushKey = registeredApp()?.webPushKey;
|
|
401
|
+
if (webPushKey) env.push(`SB_WEB_PUSH_KEY=${webPushKey}`);
|
|
397
402
|
return env;
|
|
398
403
|
}
|
|
399
404
|
|
package/src/notifications.ts
CHANGED
|
@@ -4,6 +4,13 @@
|
|
|
4
4
|
* comes due; the page only answers for the permission and shows what it is
|
|
5
5
|
* told to, with the browser's Notification API. Nothing is scheduled here, so
|
|
6
6
|
* nothing fires once the page is closed.
|
|
7
|
+
*
|
|
8
|
+
* Remote notifications are Web Push ("Push notifications" there): the app's
|
|
9
|
+
* `registerForRemoteNotifications` becomes `pushSubscribe`, which registers
|
|
10
|
+
* the site's service worker (public/sw.js) and subscribes through it; the
|
|
11
|
+
* subscription is the app's device token. The worker shows each push and
|
|
12
|
+
* reports pushes and clicks to the page, which hands them to the app one
|
|
13
|
+
* `receive` request at a time.
|
|
7
14
|
*/
|
|
8
15
|
|
|
9
16
|
import type { NotificationRequestParams } from './protocol';
|
|
@@ -11,11 +18,26 @@ import type { NotificationRequestParams } from './protocol';
|
|
|
11
18
|
/** `NotificationPermission`, or no Notification API at all. */
|
|
12
19
|
export type NotificationPermissionState = NotificationPermission | 'unsupported';
|
|
13
20
|
|
|
21
|
+
/** A push subscription as `PushSubscription.toJSON()` gives it: what a server sends to. */
|
|
22
|
+
export interface PushSubscriptionInfo {
|
|
23
|
+
endpoint: string;
|
|
24
|
+
keys: { p256dh: string; auth: string };
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** What the service worker reports: a push that arrived, or a click on a notification it showed. */
|
|
28
|
+
export type PushEvent = { type: 'push'; payload: unknown } | { type: 'click'; payload: unknown; action: string };
|
|
29
|
+
|
|
14
30
|
export interface NotificationResult {
|
|
15
31
|
/** `status` and `request`. */
|
|
16
32
|
permission?: NotificationPermissionState;
|
|
17
33
|
/** `show`: whether a notification appeared. */
|
|
18
34
|
shown?: boolean;
|
|
35
|
+
/** `pushSubscribe`. */
|
|
36
|
+
subscription?: PushSubscriptionInfo;
|
|
37
|
+
/** `receive`: the event's fields. */
|
|
38
|
+
type?: PushEvent['type'];
|
|
39
|
+
payload?: unknown;
|
|
40
|
+
action?: string;
|
|
19
41
|
}
|
|
20
42
|
|
|
21
43
|
/** The browser's notification facilities; tests substitute them. */
|
|
@@ -28,6 +50,17 @@ export interface NotificationHost {
|
|
|
28
50
|
close(tags: readonly string[]): void;
|
|
29
51
|
/** `navigator.setAppBadge` (0 clears), where the browser has it. */
|
|
30
52
|
setBadge?(count: number): Promise<void>;
|
|
53
|
+
/** Web Push through the service worker, where the browser has it. */
|
|
54
|
+
push?: PushHost;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** The push service, as the service worker reaches it; tests substitute it. */
|
|
58
|
+
export interface PushHost {
|
|
59
|
+
/** Subscribes with the key (registering the worker first); the subscription. */
|
|
60
|
+
subscribe(applicationServerKey: string): Promise<PushSubscriptionInfo>;
|
|
61
|
+
unsubscribe(): Promise<void>;
|
|
62
|
+
/** Resolves with the next push or click the worker reported. */
|
|
63
|
+
receive(): Promise<PushEvent>;
|
|
31
64
|
}
|
|
32
65
|
|
|
33
66
|
export async function performNotification(params: NotificationRequestParams, host: NotificationHost | undefined): Promise<NotificationResult> {
|
|
@@ -55,13 +88,29 @@ export async function performNotification(params: NotificationRequestParams, hos
|
|
|
55
88
|
case 'badge':
|
|
56
89
|
await host?.setBadge?.(params.badge ?? 0).catch(() => undefined);
|
|
57
90
|
return {};
|
|
91
|
+
case 'pushSubscribe': {
|
|
92
|
+
if (!host?.push) throw new Error('unsupported: this browser has no push service');
|
|
93
|
+
if (!params.applicationServerKey) throw new Error('push: no application server key (webPushKey in swiftbrowser.json)');
|
|
94
|
+
// A subscription needs the permission: asked for here when it has not been.
|
|
95
|
+
const granted = permission === 'default' ? await host.requestPermission() : permission;
|
|
96
|
+
if (granted !== 'granted') throw new Error('denied: notifications are not allowed');
|
|
97
|
+
return { subscription: await host.push.subscribe(params.applicationServerKey) };
|
|
98
|
+
}
|
|
99
|
+
case 'pushUnsubscribe':
|
|
100
|
+
await host?.push?.unsubscribe();
|
|
101
|
+
return {};
|
|
102
|
+
case 'receive': {
|
|
103
|
+
if (!host?.push) throw new Error('unsupported: this browser has no push service');
|
|
104
|
+
const event = await host.push.receive();
|
|
105
|
+
return event.type === 'click' ? { type: 'click', payload: event.payload, action: event.action } : { type: 'push', payload: event.payload };
|
|
106
|
+
}
|
|
58
107
|
default:
|
|
59
108
|
throw new Error(`unsupported: notification action ${String((params as { action: unknown }).action)}`);
|
|
60
109
|
}
|
|
61
110
|
}
|
|
62
111
|
|
|
63
112
|
/** The Notification API, or nothing where the browser has none (an iPhone tab not on the Home Screen). */
|
|
64
|
-
export function browserNotificationHost(): NotificationHost | undefined {
|
|
113
|
+
export function browserNotificationHost(push?: PushHost): NotificationHost | undefined {
|
|
65
114
|
if (typeof window === 'undefined' || typeof Notification === 'undefined') return undefined;
|
|
66
115
|
const open = new Map<string, Notification>();
|
|
67
116
|
const badgeNavigator = navigator as Navigator & { setAppBadge?: (n?: number) => Promise<void>; clearAppBadge?: () => Promise<void> };
|
|
@@ -105,5 +154,89 @@ export function browserNotificationHost(): NotificationHost | undefined {
|
|
|
105
154
|
count > 0 ? badgeNavigator.setAppBadge!(count) : (badgeNavigator.clearAppBadge?.() ?? Promise.resolve()),
|
|
106
155
|
}
|
|
107
156
|
: {}),
|
|
157
|
+
...(push ? { push } : {}),
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/** The message types the service worker (public/sw.js) posts to the page. */
|
|
162
|
+
export const PUSH_MESSAGE = 'swiftbrowser:push';
|
|
163
|
+
export const CLICK_MESSAGE = 'swiftbrowser:notificationclick';
|
|
164
|
+
|
|
165
|
+
/** The push event a service worker message carries, or null when `data` is not one. */
|
|
166
|
+
export function pushEventOf(data: unknown): PushEvent | null {
|
|
167
|
+
if (typeof data !== 'object' || data === null) return null;
|
|
168
|
+
const message = data as { type?: unknown; payload?: unknown; action?: unknown };
|
|
169
|
+
if (message.type === PUSH_MESSAGE) return { type: 'push', payload: message.payload ?? {} };
|
|
170
|
+
if (message.type === CLICK_MESSAGE) return { type: 'click', payload: message.payload ?? {}, action: typeof message.action === 'string' ? message.action : '' };
|
|
171
|
+
return null;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/** A VAPID public key (base64url, as servers print it) as the bytes `subscribe` takes. */
|
|
175
|
+
export function applicationServerKeyBytes(key: string): Uint8Array<ArrayBuffer> {
|
|
176
|
+
const base64 = key.replace(/-/g, '+').replace(/_/g, '/');
|
|
177
|
+
const padded = base64 + '='.repeat((4 - (base64.length % 4)) % 4);
|
|
178
|
+
const binary = atob(padded);
|
|
179
|
+
const bytes = new Uint8Array(new ArrayBuffer(binary.length));
|
|
180
|
+
for (let i = 0; i < binary.length; i += 1) bytes[i] = binary.charCodeAt(i);
|
|
181
|
+
return bytes;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/** The subscription's `toJSON()`, checked for the fields a server needs. */
|
|
185
|
+
function subscriptionInfo(subscription: PushSubscription): PushSubscriptionInfo {
|
|
186
|
+
const json = subscription.toJSON();
|
|
187
|
+
const keys = json.keys ?? {};
|
|
188
|
+
if (!json.endpoint || typeof keys['p256dh'] !== 'string' || typeof keys['auth'] !== 'string') throw new Error('push: the subscription has no keys');
|
|
189
|
+
return { endpoint: json.endpoint, keys: { p256dh: keys['p256dh'], auth: keys['auth'] } };
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Web Push through the service worker at `workerURL` with `scope` (the
|
|
194
|
+
* site's base), or nothing where the browser has no push service (Safari
|
|
195
|
+
* before 16.4; an iPhone tab not on the Home Screen). Listens for the
|
|
196
|
+
* worker's messages from the start: a click that opened the page is posted
|
|
197
|
+
* before the app asks for it.
|
|
198
|
+
*/
|
|
199
|
+
export function browserPushHost(workerURL: string, scope: string): PushHost | undefined {
|
|
200
|
+
if (typeof navigator === 'undefined' || !('serviceWorker' in navigator) || !navigator.serviceWorker || typeof PushManager === 'undefined') return undefined;
|
|
201
|
+
const container = navigator.serviceWorker;
|
|
202
|
+
const queue: PushEvent[] = [];
|
|
203
|
+
const waiting: ((event: PushEvent) => void)[] = [];
|
|
204
|
+
container.addEventListener('message', (message: MessageEvent) => {
|
|
205
|
+
const event = pushEventOf(message.data);
|
|
206
|
+
if (!event) return;
|
|
207
|
+
const wake = waiting.shift();
|
|
208
|
+
if (wake) wake(event);
|
|
209
|
+
else queue.push(event);
|
|
210
|
+
});
|
|
211
|
+
const registration = async (): Promise<ServiceWorkerRegistration> => {
|
|
212
|
+
const existing = await container.getRegistration(scope);
|
|
213
|
+
if (existing?.active && existing.scope === new URL(scope, location.href).href) return existing;
|
|
214
|
+
return container.register(workerURL, { scope });
|
|
215
|
+
};
|
|
216
|
+
return {
|
|
217
|
+
subscribe: async (key) => {
|
|
218
|
+
const worker = await registration();
|
|
219
|
+
await container.ready;
|
|
220
|
+
const options: PushSubscriptionOptionsInit = { userVisibleOnly: true, applicationServerKey: applicationServerKeyBytes(key) };
|
|
221
|
+
const current = await worker.pushManager.getSubscription();
|
|
222
|
+
try {
|
|
223
|
+
return subscriptionInfo(current ?? (await worker.pushManager.subscribe(options)));
|
|
224
|
+
} catch (error) {
|
|
225
|
+
// A subscription under another key stands in the way: replaced.
|
|
226
|
+
if (!current) throw error;
|
|
227
|
+
await current.unsubscribe().catch(() => undefined);
|
|
228
|
+
return subscriptionInfo(await worker.pushManager.subscribe(options));
|
|
229
|
+
}
|
|
230
|
+
},
|
|
231
|
+
unsubscribe: async () => {
|
|
232
|
+
const worker = await container.getRegistration(scope);
|
|
233
|
+
const current = await worker?.pushManager.getSubscription();
|
|
234
|
+
await current?.unsubscribe();
|
|
235
|
+
},
|
|
236
|
+
receive: () => {
|
|
237
|
+
const queued = queue.shift();
|
|
238
|
+
if (queued) return Promise.resolve(queued);
|
|
239
|
+
return new Promise((resolve) => waiting.push(resolve));
|
|
240
|
+
},
|
|
108
241
|
};
|
|
109
242
|
}
|
package/src/protocol.ts
CHANGED
|
@@ -1229,7 +1229,7 @@ export interface PickFilesRequestParams {
|
|
|
1229
1229
|
* response value is a `NotificationResult`.
|
|
1230
1230
|
*/
|
|
1231
1231
|
export interface NotificationRequestParams {
|
|
1232
|
-
action: 'status' | 'request' | 'show' | 'close' | 'badge';
|
|
1232
|
+
action: 'status' | 'request' | 'show' | 'close' | 'badge' | 'pushSubscribe' | 'pushUnsubscribe' | 'receive';
|
|
1233
1233
|
/** `show`: the request's identifier, the notification's `tag`. */
|
|
1234
1234
|
id?: string;
|
|
1235
1235
|
title?: string;
|
|
@@ -1240,6 +1240,8 @@ export interface NotificationRequestParams {
|
|
|
1240
1240
|
badge?: number;
|
|
1241
1241
|
/** `close`: the identifiers to close. */
|
|
1242
1242
|
ids?: string[];
|
|
1243
|
+
/** `pushSubscribe`: the Web Push application server key (a VAPID public key, base64url). */
|
|
1244
|
+
applicationServerKey?: string;
|
|
1243
1245
|
}
|
|
1244
1246
|
|
|
1245
1247
|
/**
|
package/src/vite-env.d.ts
CHANGED
|
@@ -18,6 +18,8 @@ interface SBLandingApp {
|
|
|
18
18
|
urlSchemes?: string[];
|
|
19
19
|
/** A CORS proxy (packages/cors-proxy) through which a refused cross-origin GET is retried (src/corsProxy.ts). */
|
|
20
20
|
corsProxy?: string;
|
|
21
|
+
/** The Web Push application server key the app subscribes with (`SB_WEB_PUSH_KEY`, src/notifications.ts). */
|
|
22
|
+
webPushKey?: string;
|
|
21
23
|
/** Hosts the app's requests may not reach (src/blockedHosts.ts). */
|
|
22
24
|
blockedHosts?: string[];
|
|
23
25
|
}
|