@businessdash/sdk 0.9.60 → 0.9.80
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/README.md +991 -295
- package/assets/biab-sw.js +92 -0
- package/dist/account-core/index.d.ts +126 -0
- package/dist/account-core/index.d.ts.map +1 -0
- package/dist/account-core/index.js +191 -0
- package/dist/account-core/index.js.map +1 -0
- package/dist/account.d.ts +11 -0
- package/dist/account.d.ts.map +1 -0
- package/dist/account.js +11 -0
- package/dist/account.js.map +1 -0
- package/dist/auth-handler.d.ts +1 -1
- package/dist/auth-links.d.ts +33 -0
- package/dist/auth-links.d.ts.map +1 -0
- package/dist/auth-links.js +64 -0
- package/dist/auth-links.js.map +1 -0
- package/dist/blog-core/index.d.ts +112 -0
- package/dist/blog-core/index.d.ts.map +1 -0
- package/dist/blog-core/index.js +330 -0
- package/dist/blog-core/index.js.map +1 -0
- package/dist/blog.d.ts +11 -0
- package/dist/blog.d.ts.map +1 -0
- package/dist/blog.js +11 -0
- package/dist/blog.js.map +1 -0
- package/dist/chat-core/index.d.ts +97 -0
- package/dist/chat-core/index.d.ts.map +1 -0
- package/dist/chat-core/index.js +237 -0
- package/dist/chat-core/index.js.map +1 -0
- package/dist/chat.d.ts +11 -0
- package/dist/chat.d.ts.map +1 -0
- package/dist/chat.js +11 -0
- package/dist/chat.js.map +1 -0
- package/dist/checkout-core/index.d.ts +65 -0
- package/dist/checkout-core/index.d.ts.map +1 -0
- package/dist/checkout-core/index.js +159 -0
- package/dist/checkout-core/index.js.map +1 -0
- package/dist/checkout.d.ts +10 -0
- package/dist/checkout.d.ts.map +1 -0
- package/dist/checkout.js +10 -0
- package/dist/checkout.js.map +1 -0
- package/dist/cli.js +133 -25
- package/dist/cli.js.map +1 -1
- package/dist/client.d.ts +622 -1
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +888 -1
- package/dist/client.js.map +1 -1
- package/dist/consent-core/index.cjs +107 -0
- package/dist/consent-core/index.d.ts +103 -0
- package/dist/consent-core/index.d.ts.map +1 -0
- package/dist/consent-core/index.js +157 -0
- package/dist/consent-core/index.js.map +1 -0
- package/dist/contracts.d.ts +3526 -767
- package/dist/contracts.d.ts.map +1 -1
- package/dist/contracts.js +571 -4
- package/dist/contracts.js.map +1 -1
- package/dist/data-model-schema.d.ts +1 -1
- package/dist/data-model-schema.d.ts.map +1 -1
- package/dist/element.d.ts +30 -0
- package/dist/element.d.ts.map +1 -1
- package/dist/element.js +56 -0
- package/dist/element.js.map +1 -1
- package/dist/embed-protocol.d.ts +86 -0
- package/dist/embed-protocol.d.ts.map +1 -0
- package/dist/embed-protocol.js +60 -0
- package/dist/embed-protocol.js.map +1 -0
- package/dist/framework-support.d.ts +122 -0
- package/dist/framework-support.d.ts.map +1 -0
- package/dist/framework-support.js +323 -0
- package/dist/framework-support.js.map +1 -0
- package/dist/index.cjs +1355 -24
- package/dist/legal-core/index.cjs +115 -0
- package/dist/legal-core/index.d.ts +160 -0
- package/dist/legal-core/index.d.ts.map +1 -0
- package/dist/legal-core/index.js +220 -0
- package/dist/legal-core/index.js.map +1 -0
- package/dist/marketing-core/index.d.ts +66 -0
- package/dist/marketing-core/index.d.ts.map +1 -0
- package/dist/marketing-core/index.js +154 -0
- package/dist/marketing-core/index.js.map +1 -0
- package/dist/marketing.d.ts +7 -0
- package/dist/marketing.d.ts.map +1 -0
- package/dist/marketing.js +7 -0
- package/dist/marketing.js.map +1 -0
- package/dist/mcp-adapters/index.d.ts +135 -0
- package/dist/mcp-adapters/index.d.ts.map +1 -0
- package/dist/mcp-adapters/index.js +187 -0
- package/dist/mcp-adapters/index.js.map +1 -0
- package/dist/mcp-adapters.d.ts +7 -0
- package/dist/mcp-adapters.d.ts.map +1 -0
- package/dist/mcp-adapters.js +7 -0
- package/dist/mcp-adapters.js.map +1 -0
- package/dist/model3d-core/index.cjs +95 -0
- package/dist/model3d-core/index.d.ts +167 -0
- package/dist/model3d-core/index.d.ts.map +1 -0
- package/dist/model3d-core/index.js +158 -0
- package/dist/model3d-core/index.js.map +1 -0
- package/dist/notifications-core/index.cjs +315 -0
- package/dist/notifications-core/index.d.ts +229 -0
- package/dist/notifications-core/index.d.ts.map +1 -0
- package/dist/notifications-core/index.js +445 -0
- package/dist/notifications-core/index.js.map +1 -0
- package/dist/portal-core/index.d.ts +451 -0
- package/dist/portal-core/index.d.ts.map +1 -0
- package/dist/portal-core/index.js +819 -0
- package/dist/portal-core/index.js.map +1 -0
- package/dist/portal.d.ts +7 -0
- package/dist/portal.d.ts.map +1 -0
- package/dist/portal.js +7 -0
- package/dist/portal.js.map +1 -0
- package/dist/push-core/index.cjs +171 -0
- package/dist/push-core/index.d.ts +149 -0
- package/dist/push-core/index.d.ts.map +1 -0
- package/dist/push-core/index.js +231 -0
- package/dist/push-core/index.js.map +1 -0
- package/dist/react-marketing.d.ts +1 -17
- package/dist/react-marketing.d.ts.map +1 -1
- package/dist/react-marketing.js +4 -0
- package/dist/react-marketing.js.map +1 -1
- package/dist/react.d.ts +294 -101
- package/dist/react.d.ts.map +1 -1
- package/dist/react.js +302 -246
- package/dist/react.js.map +1 -1
- package/dist/seed-core/index.d.ts +354 -0
- package/dist/seed-core/index.d.ts.map +1 -0
- package/dist/seed-core/index.js +420 -0
- package/dist/seed-core/index.js.map +1 -0
- package/dist/seed.d.ts +7 -0
- package/dist/seed.d.ts.map +1 -0
- package/dist/seed.js +7 -0
- package/dist/seed.js.map +1 -0
- package/dist/seo-core/blog.d.ts +94 -0
- package/dist/seo-core/blog.d.ts.map +1 -0
- package/dist/seo-core/blog.js +149 -0
- package/dist/seo-core/blog.js.map +1 -0
- package/dist/seo-core/commerce.d.ts +64 -0
- package/dist/seo-core/commerce.d.ts.map +1 -0
- package/dist/seo-core/commerce.js +87 -0
- package/dist/seo-core/commerce.js.map +1 -0
- package/dist/seo-core/index.cjs +751 -0
- package/dist/seo-core/index.d.ts +226 -0
- package/dist/seo-core/index.d.ts.map +1 -0
- package/dist/seo-core/index.js +455 -0
- package/dist/seo-core/index.js.map +1 -0
- package/dist/seo-core/local.d.ts +181 -0
- package/dist/seo-core/local.d.ts.map +1 -0
- package/dist/seo-core/local.js +193 -0
- package/dist/seo-core/local.js.map +1 -0
- package/dist/sitemap-core/index.cjs +167 -0
- package/dist/sitemap-core/index.d.ts +155 -0
- package/dist/sitemap-core/index.d.ts.map +1 -0
- package/dist/sitemap-core/index.js +195 -0
- package/dist/sitemap-core/index.js.map +1 -0
- package/dist/social-core/index.d.ts +74 -0
- package/dist/social-core/index.d.ts.map +1 -0
- package/dist/social-core/index.js +72 -0
- package/dist/social-core/index.js.map +1 -0
- package/dist/social.d.ts +7 -0
- package/dist/social.d.ts.map +1 -0
- package/dist/social.js +7 -0
- package/dist/social.js.map +1 -0
- package/dist/solid-compat.d.ts +204 -0
- package/dist/solid-compat.d.ts.map +1 -0
- package/dist/solid-compat.js +221 -0
- package/dist/solid-compat.js.map +1 -0
- package/dist/store-core/index.d.ts +141 -0
- package/dist/store-core/index.d.ts.map +1 -0
- package/dist/store-core/index.js +277 -0
- package/dist/store-core/index.js.map +1 -0
- package/dist/store.d.ts +12 -0
- package/dist/store.d.ts.map +1 -0
- package/dist/store.js +12 -0
- package/dist/store.js.map +1 -0
- package/dist/vanilla.d.ts +111 -0
- package/dist/vanilla.d.ts.map +1 -1
- package/dist/vanilla.js +206 -0
- package/dist/vanilla.js.map +1 -1
- package/dist/verify-core/index.d.ts +98 -0
- package/dist/verify-core/index.d.ts.map +1 -0
- package/dist/verify-core/index.js +186 -0
- package/dist/verify-core/index.js.map +1 -0
- package/dist/verify.d.ts +8 -0
- package/dist/verify.d.ts.map +1 -0
- package/dist/verify.js +8 -0
- package/dist/verify.js.map +1 -0
- package/openapi.json +4516 -1311
- package/package.json +128 -11
- package/src/account-core/index.ts +309 -0
- package/src/account.ts +11 -0
- package/src/angular/headless.ts +519 -0
- package/src/angular/index.ts +3 -0
- package/src/auth-links.ts +76 -0
- package/src/blog-core/index.ts +423 -0
- package/src/blog.ts +11 -0
- package/src/chat-core/index.ts +335 -0
- package/src/chat.ts +11 -0
- package/src/checkout-core/index.ts +225 -0
- package/src/checkout.ts +10 -0
- package/src/cli.ts +184 -28
- package/src/client.ts +1097 -0
- package/src/consent-core/index.ts +213 -0
- package/src/contracts.ts +768 -11
- package/src/data-model-schema.ts +1 -1
- package/src/element.ts +102 -0
- package/src/embed-protocol.ts +100 -0
- package/src/framework-support.ts +440 -0
- package/src/legal-core/index.ts +276 -0
- package/src/marketing-core/index.ts +209 -0
- package/src/marketing.ts +7 -0
- package/src/mcp-adapters/index.ts +264 -0
- package/src/mcp-adapters.ts +7 -0
- package/src/model3d-core/index.ts +248 -0
- package/src/notifications-core/index.ts +650 -0
- package/src/portal-core/index.ts +1564 -0
- package/src/portal.ts +7 -0
- package/src/push-core/index.ts +335 -0
- package/src/qwik.tsx +602 -2
- package/src/react-marketing.tsx +5 -0
- package/src/react.tsx +434 -319
- package/src/seed-core/index.ts +735 -0
- package/src/seed.ts +7 -0
- package/src/seo-core/blog.ts +216 -0
- package/src/seo-core/commerce.ts +138 -0
- package/src/seo-core/index.ts +605 -0
- package/src/seo-core/local.ts +333 -0
- package/src/sitemap-core/index.ts +296 -0
- package/src/social-core/index.ts +103 -0
- package/src/social.ts +7 -0
- package/src/solid-compat.tsx +347 -0
- package/src/solid-surfaces.tsx +1311 -0
- package/src/solid.tsx +82 -60
- package/src/store-core/index.ts +426 -0
- package/src/store.ts +12 -0
- package/src/svelte/SocialLinks.svelte +84 -0
- package/src/svelte/headless.ts +435 -0
- package/src/svelte/index.ts +9 -0
- package/src/vanilla.ts +367 -0
- package/src/verify-core/index.ts +288 -0
- package/src/verify.ts +8 -0
- package/src/vue/headless.ts +527 -0
- package/src/vue/index.ts +3 -0
package/src/portal.ts
ADDED
|
@@ -0,0 +1,335 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@businessdash/sdk/push-core` — browser push, from the org's own site.
|
|
3
|
+
*
|
|
4
|
+
* ## The one architectural fact that shapes all of this
|
|
5
|
+
*
|
|
6
|
+
* A push subscription belongs to an **origin**. The service worker must be
|
|
7
|
+
* served from the domain the visitor is actually on, so a subscription minted
|
|
8
|
+
* on the platform's domain cannot deliver to someone browsing the org's site.
|
|
9
|
+
* That is why this lives in the SDK rather than in the dashboard: only code
|
|
10
|
+
* running on the org's own origin can register it.
|
|
11
|
+
*
|
|
12
|
+
* What does NOT have to be per-origin is the signing key. `applicationServerKey`
|
|
13
|
+
* only has to match whatever signs the push, so **one platform VAPID pair
|
|
14
|
+
* serves every org** — no per-org registration, no per-org credentials, nothing
|
|
15
|
+
* for an org to configure.
|
|
16
|
+
*
|
|
17
|
+
* ## Safari
|
|
18
|
+
*
|
|
19
|
+
* Safari 16+ (macOS Ventura) and iOS 16.4+ implement the standard Web Push API
|
|
20
|
+
* with VAPID — the same one used here. Apple's old Safari Push flow (a Website
|
|
21
|
+
* Push ID registered in the developer portal, a signed push package listing
|
|
22
|
+
* allowed domains) applies to macOS Safari 15 and earlier and is not used.
|
|
23
|
+
*
|
|
24
|
+
* **iOS has a real constraint though:** push only works when the site has been
|
|
25
|
+
* added to the Home Screen. A plain Safari tab on iPhone cannot receive push at
|
|
26
|
+
* all, whatever anyone configures. `pushSupport()` reports that case
|
|
27
|
+
* specifically so a UI can explain it rather than showing a button that
|
|
28
|
+
* silently fails.
|
|
29
|
+
*
|
|
30
|
+
* ## Permission is a one-shot resource
|
|
31
|
+
*
|
|
32
|
+
* A browser asks once per origin. A refusal is sticky and expensive to undo —
|
|
33
|
+
* the visitor has to find it in site settings — so `subscribeToPush()` must be
|
|
34
|
+
* called from a user gesture, after the person has been told what they are
|
|
35
|
+
* agreeing to. Prompting on page load burns the permission for that origin,
|
|
36
|
+
* permanently, for everyone who says no reflexively.
|
|
37
|
+
*
|
|
38
|
+
* @module
|
|
39
|
+
*/
|
|
40
|
+
|
|
41
|
+
/** What this browser can actually do. */
|
|
42
|
+
export type PushSupport =
|
|
43
|
+
| { supported: true }
|
|
44
|
+
| {
|
|
45
|
+
supported: false;
|
|
46
|
+
/**
|
|
47
|
+
* `unsupported` — no service worker or Push API at all.
|
|
48
|
+
* `ios-needs-home-screen` — iOS Safari in a normal tab; the site must
|
|
49
|
+
* be added to the Home Screen first. Worth its own case because it
|
|
50
|
+
* is fixable by the visitor, unlike the others.
|
|
51
|
+
* `insecure-context` — not HTTPS (localhost aside).
|
|
52
|
+
* `server` — no `window`; nothing to do.
|
|
53
|
+
*/
|
|
54
|
+
reason:
|
|
55
|
+
| "unsupported"
|
|
56
|
+
| "ios-needs-home-screen"
|
|
57
|
+
| "insecure-context"
|
|
58
|
+
| "server";
|
|
59
|
+
message: string;
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
/** The bits of a `PushSubscription` the server needs, already base64url. */
|
|
63
|
+
export type PushSubscriptionPayload = {
|
|
64
|
+
endpoint: string;
|
|
65
|
+
p256dh: string;
|
|
66
|
+
auth: string;
|
|
67
|
+
userAgent?: string;
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
/** A browser this person has registered. */
|
|
71
|
+
export type PushDevice = {
|
|
72
|
+
id: string;
|
|
73
|
+
endpoint: string;
|
|
74
|
+
userAgent: string | null;
|
|
75
|
+
label: string | null;
|
|
76
|
+
categories: string[] | null;
|
|
77
|
+
lastSeenAt: string | null;
|
|
78
|
+
createdAt: string;
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
export type PushConfig = {
|
|
82
|
+
vapidPublicKey: string | null;
|
|
83
|
+
enabled: boolean;
|
|
84
|
+
devices: PushDevice[];
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Can this browser receive push, and if not, why?
|
|
89
|
+
*
|
|
90
|
+
* Checked before rendering anything, so a visitor is never offered a button
|
|
91
|
+
* that cannot work. The iOS case in particular deserves an explanation rather
|
|
92
|
+
* than silence — "add this to your Home Screen first" is actionable, a dead
|
|
93
|
+
* button is not.
|
|
94
|
+
*/
|
|
95
|
+
export function pushSupport(): PushSupport {
|
|
96
|
+
if (typeof window === "undefined") {
|
|
97
|
+
return { supported: false, reason: "server", message: "Not in a browser." };
|
|
98
|
+
}
|
|
99
|
+
if (!window.isSecureContext) {
|
|
100
|
+
return {
|
|
101
|
+
supported: false,
|
|
102
|
+
reason: "insecure-context",
|
|
103
|
+
message: "Push needs HTTPS.",
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
if (!("serviceWorker" in navigator) || !("PushManager" in window)) {
|
|
107
|
+
// iOS Safari in a normal tab reports exactly this, so distinguish it —
|
|
108
|
+
// the visitor can fix theirs, and telling them "unsupported" is wrong.
|
|
109
|
+
if (isIosSafari() && !isStandalone()) {
|
|
110
|
+
return {
|
|
111
|
+
supported: false,
|
|
112
|
+
reason: "ios-needs-home-screen",
|
|
113
|
+
message:
|
|
114
|
+
"On iPhone and iPad, add this site to your Home Screen to turn on notifications.",
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
return {
|
|
118
|
+
supported: false,
|
|
119
|
+
reason: "unsupported",
|
|
120
|
+
message: "This browser doesn't support push notifications.",
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
if (isIosSafari() && !isStandalone()) {
|
|
124
|
+
return {
|
|
125
|
+
supported: false,
|
|
126
|
+
reason: "ios-needs-home-screen",
|
|
127
|
+
message:
|
|
128
|
+
"On iPhone and iPad, add this site to your Home Screen to turn on notifications.",
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
return { supported: true };
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function isIosSafari(): boolean {
|
|
135
|
+
const ua = navigator.userAgent;
|
|
136
|
+
// iPadOS reports as Macintosh, so touch points are the tell.
|
|
137
|
+
const iOS =
|
|
138
|
+
/iPad|iPhone|iPod/.test(ua) ||
|
|
139
|
+
(/Macintosh/.test(ua) && navigator.maxTouchPoints > 1);
|
|
140
|
+
return iOS && !/CriOS|FxiOS|EdgiOS/.test(ua);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function isStandalone(): boolean {
|
|
144
|
+
return (
|
|
145
|
+
window.matchMedia?.("(display-mode: standalone)").matches === true ||
|
|
146
|
+
(navigator as { standalone?: boolean }).standalone === true
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/** The permission state, without asking for it. */
|
|
151
|
+
export function pushPermission(): NotificationPermission | "unsupported" {
|
|
152
|
+
if (typeof Notification === "undefined") return "unsupported";
|
|
153
|
+
return Notification.permission;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* VAPID keys are base64url; `applicationServerKey` wants raw bytes.
|
|
158
|
+
*
|
|
159
|
+
* Backed by an explicit `ArrayBuffer` rather than `new Uint8Array(length)`:
|
|
160
|
+
* TypeScript's DOM types want a `BufferSource`, and a `Uint8Array` over the
|
|
161
|
+
* generic `ArrayBufferLike` (which could be a `SharedArrayBuffer`) does not
|
|
162
|
+
* satisfy it.
|
|
163
|
+
*/
|
|
164
|
+
function urlBase64ToUint8Array(base64: string): Uint8Array<ArrayBuffer> {
|
|
165
|
+
const padding = "=".repeat((4 - (base64.length % 4)) % 4);
|
|
166
|
+
const normalised = (base64 + padding).replace(/-/g, "+").replace(/_/g, "/");
|
|
167
|
+
const raw = atob(normalised);
|
|
168
|
+
const out = new Uint8Array(new ArrayBuffer(raw.length));
|
|
169
|
+
for (let i = 0; i < raw.length; i++) out[i] = raw.charCodeAt(i);
|
|
170
|
+
return out;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/** …and the reverse, for the keys the browser hands back. */
|
|
174
|
+
function arrayBufferToBase64Url(buffer: ArrayBuffer | null): string {
|
|
175
|
+
if (!buffer) return "";
|
|
176
|
+
const bytes = new Uint8Array(buffer);
|
|
177
|
+
let binary = "";
|
|
178
|
+
for (const b of bytes) binary += String.fromCharCode(b);
|
|
179
|
+
return btoa(binary)
|
|
180
|
+
.replace(/\+/g, "-")
|
|
181
|
+
.replace(/\//g, "_")
|
|
182
|
+
.replace(/=+$/, "");
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/** Flatten a `PushSubscription` into what the API stores. */
|
|
186
|
+
export function toPayload(
|
|
187
|
+
subscription: PushSubscription,
|
|
188
|
+
): PushSubscriptionPayload {
|
|
189
|
+
return {
|
|
190
|
+
endpoint: subscription.endpoint,
|
|
191
|
+
p256dh: arrayBufferToBase64Url(subscription.getKey("p256dh")),
|
|
192
|
+
auth: arrayBufferToBase64Url(subscription.getKey("auth")),
|
|
193
|
+
...(typeof navigator === "undefined"
|
|
194
|
+
? {}
|
|
195
|
+
: { userAgent: navigator.userAgent }),
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
export type SubscribeOptions = {
|
|
200
|
+
/** From `GET customer-portal/push`. */
|
|
201
|
+
vapidPublicKey: string;
|
|
202
|
+
/**
|
|
203
|
+
* Path to the service worker ON YOUR ORIGIN. Defaults to `/biab-sw.js`.
|
|
204
|
+
*
|
|
205
|
+
* It must be served from your own domain — a worker on someone else's
|
|
206
|
+
* cannot receive your visitors' push. Copy the one this SDK ships.
|
|
207
|
+
*/
|
|
208
|
+
serviceWorkerPath?: string;
|
|
209
|
+
/**
|
|
210
|
+
* Scope the worker controls. Defaults to `/`.
|
|
211
|
+
*
|
|
212
|
+
* A worker served from a subdirectory can only claim that subdirectory
|
|
213
|
+
* unless the response carries `Service-Worker-Allowed`, which is why the
|
|
214
|
+
* default file sits at the root.
|
|
215
|
+
*/
|
|
216
|
+
scope?: string;
|
|
217
|
+
};
|
|
218
|
+
|
|
219
|
+
export type SubscribeResult =
|
|
220
|
+
| { ok: true; subscription: PushSubscriptionPayload }
|
|
221
|
+
| {
|
|
222
|
+
ok: false;
|
|
223
|
+
/**
|
|
224
|
+
* `denied` is the expensive one: the browser will not ask again, and
|
|
225
|
+
* the visitor has to undo it in site settings. Say so rather than
|
|
226
|
+
* offering the button a second time.
|
|
227
|
+
*/
|
|
228
|
+
reason: "unsupported" | "denied" | "dismissed" | "failed";
|
|
229
|
+
message: string;
|
|
230
|
+
};
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Register the service worker, ask for permission, and subscribe.
|
|
234
|
+
*
|
|
235
|
+
* **Call this from a click**, never on load. Permission is asked once per
|
|
236
|
+
* origin and a refusal is sticky, so prompting before the visitor knows what
|
|
237
|
+
* they are agreeing to spends the only chance you get.
|
|
238
|
+
*
|
|
239
|
+
* The result is a payload to POST to `customer-portal/push`; this function
|
|
240
|
+
* deliberately does not send it, so the caller keeps control of the request
|
|
241
|
+
* (its own auth, its own error handling, its own retry).
|
|
242
|
+
*/
|
|
243
|
+
export async function subscribeToPush(
|
|
244
|
+
options: SubscribeOptions,
|
|
245
|
+
): Promise<SubscribeResult> {
|
|
246
|
+
const support = pushSupport();
|
|
247
|
+
if (!support.supported) {
|
|
248
|
+
return { ok: false, reason: "unsupported", message: support.message };
|
|
249
|
+
}
|
|
250
|
+
if (Notification.permission === "denied") {
|
|
251
|
+
return {
|
|
252
|
+
ok: false,
|
|
253
|
+
reason: "denied",
|
|
254
|
+
message:
|
|
255
|
+
"Notifications are blocked for this site. They have to be re-enabled in the browser's site settings.",
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
try {
|
|
260
|
+
const registration = await navigator.serviceWorker.register(
|
|
261
|
+
options.serviceWorkerPath ?? "/biab-sw.js",
|
|
262
|
+
{ scope: options.scope ?? "/" },
|
|
263
|
+
);
|
|
264
|
+
// A worker that is installing cannot take a subscription yet.
|
|
265
|
+
await navigator.serviceWorker.ready;
|
|
266
|
+
|
|
267
|
+
const permission = await Notification.requestPermission();
|
|
268
|
+
if (permission !== "granted") {
|
|
269
|
+
return {
|
|
270
|
+
ok: false,
|
|
271
|
+
// "default" means dismissed — the browser WILL ask again, so this
|
|
272
|
+
// is recoverable and should read differently from a block.
|
|
273
|
+
reason: permission === "denied" ? "denied" : "dismissed",
|
|
274
|
+
message:
|
|
275
|
+
permission === "denied"
|
|
276
|
+
? "Notifications were blocked for this site."
|
|
277
|
+
: "No answer given — you can turn notifications on any time.",
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// An existing subscription is reused rather than replaced: re-subscribing
|
|
282
|
+
// mints a new endpoint and orphans the old row.
|
|
283
|
+
const existing = await registration.pushManager.getSubscription();
|
|
284
|
+
const subscription =
|
|
285
|
+
existing ??
|
|
286
|
+
(await registration.pushManager.subscribe({
|
|
287
|
+
// Required by every browser now — a subscription without it is
|
|
288
|
+
// rejected outright rather than delivered to nobody.
|
|
289
|
+
userVisibleOnly: true,
|
|
290
|
+
applicationServerKey: urlBase64ToUint8Array(options.vapidPublicKey),
|
|
291
|
+
}));
|
|
292
|
+
|
|
293
|
+
return { ok: true, subscription: toPayload(subscription) };
|
|
294
|
+
} catch (error) {
|
|
295
|
+
return {
|
|
296
|
+
ok: false,
|
|
297
|
+
reason: "failed",
|
|
298
|
+
message:
|
|
299
|
+
error instanceof Error ? error.message : "Could not subscribe to push.",
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
/**
|
|
305
|
+
* Tear the local subscription down and return its endpoint.
|
|
306
|
+
*
|
|
307
|
+
* The endpoint is what the server keys on, and a browser unsubscribing itself
|
|
308
|
+
* knows its endpoint rather than our row id — so hand this to
|
|
309
|
+
* `DELETE customer-portal/push` to remove the matching row. Dropping only the
|
|
310
|
+
* local one leaves the server pushing into a dead endpoint until the push
|
|
311
|
+
* service reports it gone.
|
|
312
|
+
*/
|
|
313
|
+
export async function unsubscribeFromPush(): Promise<{
|
|
314
|
+
endpoint: string | null;
|
|
315
|
+
}> {
|
|
316
|
+
if (typeof navigator === "undefined" || !("serviceWorker" in navigator)) {
|
|
317
|
+
return { endpoint: null };
|
|
318
|
+
}
|
|
319
|
+
const registration = await navigator.serviceWorker.getRegistration();
|
|
320
|
+
const subscription = await registration?.pushManager.getSubscription();
|
|
321
|
+
if (!subscription) return { endpoint: null };
|
|
322
|
+
const { endpoint } = subscription;
|
|
323
|
+
await subscription.unsubscribe();
|
|
324
|
+
return { endpoint };
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
/** The current subscription, if this browser already has one. */
|
|
328
|
+
export async function currentPushSubscription(): Promise<PushSubscriptionPayload | null> {
|
|
329
|
+
if (typeof navigator === "undefined" || !("serviceWorker" in navigator)) {
|
|
330
|
+
return null;
|
|
331
|
+
}
|
|
332
|
+
const registration = await navigator.serviceWorker.getRegistration();
|
|
333
|
+
const subscription = await registration?.pushManager.getSubscription();
|
|
334
|
+
return subscription ? toPayload(subscription) : null;
|
|
335
|
+
}
|