@maravilla-labs/platform 0.1.36 → 0.1.39
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/config.d.ts +192 -0
- package/dist/config.js +8 -0
- package/dist/config.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +140 -1
- package/dist/index.js.map +1 -1
- package/dist/push.d.ts +36 -0
- package/dist/push.js +141 -0
- package/dist/push.js.map +1 -0
- package/package.json +9 -1
- package/src/config.ts +219 -0
- package/src/index.ts +1 -0
- package/src/push.ts +207 -0
- package/tsup.config.ts +1 -1
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Typed schema for `maravilla.config.{ts,yaml,json}` files.
|
|
3
|
+
*
|
|
4
|
+
* Declares your project's auth settings (resources, groups, relations,
|
|
5
|
+
* registration fields, OAuth providers, security policy, branding) alongside
|
|
6
|
+
* your code. The Maravilla adapter reads this at build time and reconciles
|
|
7
|
+
* the settings into delivery on deploy.
|
|
8
|
+
*
|
|
9
|
+
* ```typescript
|
|
10
|
+
* import { defineConfig } from '@maravilla-labs/platform/config';
|
|
11
|
+
*
|
|
12
|
+
* export default defineConfig({
|
|
13
|
+
* auth: {
|
|
14
|
+
* resources: [
|
|
15
|
+
* { name: 'todos', title: 'Todos', actions: ['read', 'write'],
|
|
16
|
+
* policy: 'auth.user_id == node.owner' },
|
|
17
|
+
* ],
|
|
18
|
+
* },
|
|
19
|
+
* });
|
|
20
|
+
* ```
|
|
21
|
+
*
|
|
22
|
+
* Omitted sections leave the DB alone — partial adoption is explicitly
|
|
23
|
+
* supported. List-based sections (`resources`, `groups`, `relations`,
|
|
24
|
+
* `oauth`) are upserted and never auto-delete DB-only entries. Singleton
|
|
25
|
+
* sections (`registration`, `security`, `branding`) are replaced wholesale
|
|
26
|
+
* when declared.
|
|
27
|
+
*/
|
|
28
|
+
/**
|
|
29
|
+
* String value that may either be a literal secret or a reference to an
|
|
30
|
+
* environment variable on the **tenant** (resolved server-side at
|
|
31
|
+
* reconcile time, never shipped plaintext in the manifest).
|
|
32
|
+
*
|
|
33
|
+
* Accepted forms:
|
|
34
|
+
* - `"literal-value"` — inline (not recommended for real secrets)
|
|
35
|
+
* - `"${env.VAR_NAME}"` — string-template form
|
|
36
|
+
* - `{ env: "VAR_NAME" }` — object form
|
|
37
|
+
*/
|
|
38
|
+
type SecretRef = string | {
|
|
39
|
+
env: string;
|
|
40
|
+
};
|
|
41
|
+
interface ResourceDefinition {
|
|
42
|
+
/** URL-safe slug. Used as the resource key in code (e.g. the KV namespace). */
|
|
43
|
+
name: string;
|
|
44
|
+
/** Human-readable title for the admin UI. */
|
|
45
|
+
title: string;
|
|
46
|
+
/** Optional longer description. */
|
|
47
|
+
description?: string;
|
|
48
|
+
/** Actions this resource supports, e.g. `['read', 'write', 'delete']`. */
|
|
49
|
+
actions: string[];
|
|
50
|
+
/**
|
|
51
|
+
* Optional raisin-rel policy expression. Evaluated on every KV/DB/
|
|
52
|
+
* realtime/media op that targets this resource. Leave empty to skip
|
|
53
|
+
* Layer 2 for this resource — tenant + owner isolation still applies.
|
|
54
|
+
*/
|
|
55
|
+
policy?: string;
|
|
56
|
+
}
|
|
57
|
+
interface GroupPermissionDefinition {
|
|
58
|
+
/** Must match a `ResourceDefinition.name`. */
|
|
59
|
+
resource_name: string;
|
|
60
|
+
/** Actions this group is granted on the resource. */
|
|
61
|
+
actions: string[];
|
|
62
|
+
}
|
|
63
|
+
interface GroupDefinition {
|
|
64
|
+
/** Unique group name per tenant. */
|
|
65
|
+
name: string;
|
|
66
|
+
/** Optional description for the admin UI. */
|
|
67
|
+
description?: string;
|
|
68
|
+
/** Resource permissions granted to the group. Replaces the group's current permissions when declared. */
|
|
69
|
+
permissions?: GroupPermissionDefinition[];
|
|
70
|
+
}
|
|
71
|
+
interface RelationTypeDefinition {
|
|
72
|
+
/** Uppercase identifier used in policies (`... VIA 'STEWARDS'`). */
|
|
73
|
+
relation_name: string;
|
|
74
|
+
/** Human-readable title. */
|
|
75
|
+
title: string;
|
|
76
|
+
description?: string;
|
|
77
|
+
/** Grouping for the admin UI (e.g. `"family"`, `"work"`). */
|
|
78
|
+
category?: string;
|
|
79
|
+
icon?: string;
|
|
80
|
+
color?: string;
|
|
81
|
+
/** Name of the inverse relation type, if one exists. */
|
|
82
|
+
inverse_relation_name?: string;
|
|
83
|
+
/** When true, membership in this relation implies stewardship rights. */
|
|
84
|
+
implies_stewardship?: boolean;
|
|
85
|
+
/** When true, the relation can only target users flagged as minors. */
|
|
86
|
+
requires_minor?: boolean;
|
|
87
|
+
/** When true, the relation is symmetric (A→B implies B→A). */
|
|
88
|
+
bidirectional?: boolean;
|
|
89
|
+
}
|
|
90
|
+
interface RegistrationFieldDefinition {
|
|
91
|
+
/** Field key used as the form field name + in profile data. */
|
|
92
|
+
key: string;
|
|
93
|
+
/** Display label. */
|
|
94
|
+
label: string;
|
|
95
|
+
/** One of: text, email, phone, date, number, select, boolean, url, textarea. */
|
|
96
|
+
field_type: string;
|
|
97
|
+
required: boolean;
|
|
98
|
+
show_on_register: boolean;
|
|
99
|
+
/** Optional validation metadata — passed through to the UI. */
|
|
100
|
+
validation?: Record<string, unknown>;
|
|
101
|
+
}
|
|
102
|
+
interface RegistrationConfig {
|
|
103
|
+
/** Ordered list of custom registration fields. Declaring this replaces the full list. */
|
|
104
|
+
fields: RegistrationFieldDefinition[];
|
|
105
|
+
}
|
|
106
|
+
interface OAuthProviderDefinition {
|
|
107
|
+
enabled: boolean;
|
|
108
|
+
client_id: string;
|
|
109
|
+
/** Prefer `{ env: "VAR_NAME" }` or `"${env.VAR_NAME}"`. */
|
|
110
|
+
client_secret: SecretRef;
|
|
111
|
+
scopes: string[];
|
|
112
|
+
/** Only for `custom_oidc`. */
|
|
113
|
+
discovery_url?: string;
|
|
114
|
+
}
|
|
115
|
+
interface OAuthProvidersConfig {
|
|
116
|
+
google?: OAuthProviderDefinition;
|
|
117
|
+
github?: OAuthProviderDefinition;
|
|
118
|
+
okta?: OAuthProviderDefinition;
|
|
119
|
+
custom_oidc?: OAuthProviderDefinition;
|
|
120
|
+
}
|
|
121
|
+
interface PasswordPolicyDefinition {
|
|
122
|
+
min_length: number;
|
|
123
|
+
require_uppercase: boolean;
|
|
124
|
+
require_number: boolean;
|
|
125
|
+
require_special: boolean;
|
|
126
|
+
}
|
|
127
|
+
interface SessionConfigDefinition {
|
|
128
|
+
access_token_ttl_secs: number;
|
|
129
|
+
refresh_token_ttl_secs: number;
|
|
130
|
+
max_sessions_per_user: number;
|
|
131
|
+
require_email_verification: boolean;
|
|
132
|
+
}
|
|
133
|
+
interface SecurityConfig {
|
|
134
|
+
password_policy?: PasswordPolicyDefinition;
|
|
135
|
+
session?: SessionConfigDefinition;
|
|
136
|
+
}
|
|
137
|
+
interface BrandingConfig {
|
|
138
|
+
app_name?: string;
|
|
139
|
+
logo_url?: string;
|
|
140
|
+
primary_color?: string;
|
|
141
|
+
secondary_color?: string;
|
|
142
|
+
welcome_message?: string;
|
|
143
|
+
welcome_subtitle?: string;
|
|
144
|
+
/** `"centered"`, `"split-left"`, `"split-right"`, or `"fullscreen"`. */
|
|
145
|
+
layout?: string;
|
|
146
|
+
background_image_url?: string;
|
|
147
|
+
/** 0–100 percentage. */
|
|
148
|
+
background_focal_point?: {
|
|
149
|
+
x: number;
|
|
150
|
+
y: number;
|
|
151
|
+
};
|
|
152
|
+
background_gradient?: string;
|
|
153
|
+
/** `"light"`, `"dark"`, or `"auto"`. */
|
|
154
|
+
color_mode?: string;
|
|
155
|
+
font_family?: string;
|
|
156
|
+
terms_url?: string;
|
|
157
|
+
privacy_url?: string;
|
|
158
|
+
/** Raw CSS merged into the hosted auth pages. */
|
|
159
|
+
custom_css?: string;
|
|
160
|
+
}
|
|
161
|
+
interface AuthConfigBlock {
|
|
162
|
+
resources?: ResourceDefinition[];
|
|
163
|
+
groups?: GroupDefinition[];
|
|
164
|
+
relations?: RelationTypeDefinition[];
|
|
165
|
+
registration?: RegistrationConfig;
|
|
166
|
+
oauth?: OAuthProvidersConfig;
|
|
167
|
+
security?: SecurityConfig;
|
|
168
|
+
branding?: BrandingConfig;
|
|
169
|
+
}
|
|
170
|
+
interface MaravillaConfig {
|
|
171
|
+
/** All project-level auth settings. Every field is optional — partial adoption is supported. */
|
|
172
|
+
auth?: AuthConfigBlock;
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Identity function that returns the config unchanged — exists purely so the
|
|
176
|
+
* TypeScript compiler can infer `MaravillaConfig` and give you IntelliSense
|
|
177
|
+
* on every field.
|
|
178
|
+
*
|
|
179
|
+
* @example
|
|
180
|
+
* ```typescript
|
|
181
|
+
* import { defineConfig } from '@maravilla-labs/platform/config';
|
|
182
|
+
*
|
|
183
|
+
* export default defineConfig({
|
|
184
|
+
* auth: {
|
|
185
|
+
* resources: [{ name: 'todos', title: 'Todos', actions: ['read', 'write'] }],
|
|
186
|
+
* },
|
|
187
|
+
* });
|
|
188
|
+
* ```
|
|
189
|
+
*/
|
|
190
|
+
declare function defineConfig(config: MaravillaConfig): MaravillaConfig;
|
|
191
|
+
|
|
192
|
+
export { type AuthConfigBlock, type BrandingConfig, type GroupDefinition, type GroupPermissionDefinition, type MaravillaConfig, type OAuthProviderDefinition, type OAuthProvidersConfig, type PasswordPolicyDefinition, type RegistrationConfig, type RegistrationFieldDefinition, type RelationTypeDefinition, type ResourceDefinition, type SecretRef, type SecurityConfig, type SessionConfigDefinition, defineConfig };
|
package/dist/config.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/config.ts"],"sourcesContent":["/**\n * @fileoverview Typed schema for `maravilla.config.{ts,yaml,json}` files.\n *\n * Declares your project's auth settings (resources, groups, relations,\n * registration fields, OAuth providers, security policy, branding) alongside\n * your code. The Maravilla adapter reads this at build time and reconciles\n * the settings into delivery on deploy.\n *\n * ```typescript\n * import { defineConfig } from '@maravilla-labs/platform/config';\n *\n * export default defineConfig({\n * auth: {\n * resources: [\n * { name: 'todos', title: 'Todos', actions: ['read', 'write'],\n * policy: 'auth.user_id == node.owner' },\n * ],\n * },\n * });\n * ```\n *\n * Omitted sections leave the DB alone — partial adoption is explicitly\n * supported. List-based sections (`resources`, `groups`, `relations`,\n * `oauth`) are upserted and never auto-delete DB-only entries. Singleton\n * sections (`registration`, `security`, `branding`) are replaced wholesale\n * when declared.\n */\n\n/**\n * String value that may either be a literal secret or a reference to an\n * environment variable on the **tenant** (resolved server-side at\n * reconcile time, never shipped plaintext in the manifest).\n *\n * Accepted forms:\n * - `\"literal-value\"` — inline (not recommended for real secrets)\n * - `\"${env.VAR_NAME}\"` — string-template form\n * - `{ env: \"VAR_NAME\" }` — object form\n */\nexport type SecretRef = string | { env: string };\n\n// ── Resources + policies ──\n\nexport interface ResourceDefinition {\n /** URL-safe slug. Used as the resource key in code (e.g. the KV namespace). */\n name: string;\n /** Human-readable title for the admin UI. */\n title: string;\n /** Optional longer description. */\n description?: string;\n /** Actions this resource supports, e.g. `['read', 'write', 'delete']`. */\n actions: string[];\n /**\n * Optional raisin-rel policy expression. Evaluated on every KV/DB/\n * realtime/media op that targets this resource. Leave empty to skip\n * Layer 2 for this resource — tenant + owner isolation still applies.\n */\n policy?: string;\n}\n\n// ── Groups ──\n\nexport interface GroupPermissionDefinition {\n /** Must match a `ResourceDefinition.name`. */\n resource_name: string;\n /** Actions this group is granted on the resource. */\n actions: string[];\n}\n\nexport interface GroupDefinition {\n /** Unique group name per tenant. */\n name: string;\n /** Optional description for the admin UI. */\n description?: string;\n /** Resource permissions granted to the group. Replaces the group's current permissions when declared. */\n permissions?: GroupPermissionDefinition[];\n}\n\n// ── Relations ──\n\nexport interface RelationTypeDefinition {\n /** Uppercase identifier used in policies (`... VIA 'STEWARDS'`). */\n relation_name: string;\n /** Human-readable title. */\n title: string;\n description?: string;\n /** Grouping for the admin UI (e.g. `\"family\"`, `\"work\"`). */\n category?: string;\n icon?: string;\n color?: string;\n /** Name of the inverse relation type, if one exists. */\n inverse_relation_name?: string;\n /** When true, membership in this relation implies stewardship rights. */\n implies_stewardship?: boolean;\n /** When true, the relation can only target users flagged as minors. */\n requires_minor?: boolean;\n /** When true, the relation is symmetric (A→B implies B→A). */\n bidirectional?: boolean;\n}\n\n// ── Registration fields ──\n\nexport interface RegistrationFieldDefinition {\n /** Field key used as the form field name + in profile data. */\n key: string;\n /** Display label. */\n label: string;\n /** One of: text, email, phone, date, number, select, boolean, url, textarea. */\n field_type: string;\n required: boolean;\n show_on_register: boolean;\n /** Optional validation metadata — passed through to the UI. */\n validation?: Record<string, unknown>;\n}\n\nexport interface RegistrationConfig {\n /** Ordered list of custom registration fields. Declaring this replaces the full list. */\n fields: RegistrationFieldDefinition[];\n}\n\n// ── OAuth providers ──\n\nexport interface OAuthProviderDefinition {\n enabled: boolean;\n client_id: string;\n /** Prefer `{ env: \"VAR_NAME\" }` or `\"${env.VAR_NAME}\"`. */\n client_secret: SecretRef;\n scopes: string[];\n /** Only for `custom_oidc`. */\n discovery_url?: string;\n}\n\nexport interface OAuthProvidersConfig {\n google?: OAuthProviderDefinition;\n github?: OAuthProviderDefinition;\n okta?: OAuthProviderDefinition;\n custom_oidc?: OAuthProviderDefinition;\n}\n\n// ── Security ──\n\nexport interface PasswordPolicyDefinition {\n min_length: number;\n require_uppercase: boolean;\n require_number: boolean;\n require_special: boolean;\n}\n\nexport interface SessionConfigDefinition {\n access_token_ttl_secs: number;\n refresh_token_ttl_secs: number;\n max_sessions_per_user: number;\n require_email_verification: boolean;\n}\n\nexport interface SecurityConfig {\n password_policy?: PasswordPolicyDefinition;\n session?: SessionConfigDefinition;\n}\n\n// ── Branding ──\n\nexport interface BrandingConfig {\n app_name?: string;\n logo_url?: string;\n primary_color?: string;\n secondary_color?: string;\n welcome_message?: string;\n welcome_subtitle?: string;\n /** `\"centered\"`, `\"split-left\"`, `\"split-right\"`, or `\"fullscreen\"`. */\n layout?: string;\n background_image_url?: string;\n /** 0–100 percentage. */\n background_focal_point?: { x: number; y: number };\n background_gradient?: string;\n /** `\"light\"`, `\"dark\"`, or `\"auto\"`. */\n color_mode?: string;\n font_family?: string;\n terms_url?: string;\n privacy_url?: string;\n /** Raw CSS merged into the hosted auth pages. */\n custom_css?: string;\n}\n\n// ── Top-level shape ──\n\nexport interface AuthConfigBlock {\n resources?: ResourceDefinition[];\n groups?: GroupDefinition[];\n relations?: RelationTypeDefinition[];\n registration?: RegistrationConfig;\n oauth?: OAuthProvidersConfig;\n security?: SecurityConfig;\n branding?: BrandingConfig;\n}\n\nexport interface MaravillaConfig {\n /** All project-level auth settings. Every field is optional — partial adoption is supported. */\n auth?: AuthConfigBlock;\n}\n\n/**\n * Identity function that returns the config unchanged — exists purely so the\n * TypeScript compiler can infer `MaravillaConfig` and give you IntelliSense\n * on every field.\n *\n * @example\n * ```typescript\n * import { defineConfig } from '@maravilla-labs/platform/config';\n *\n * export default defineConfig({\n * auth: {\n * resources: [{ name: 'todos', title: 'Todos', actions: ['read', 'write'] }],\n * },\n * });\n * ```\n */\nexport function defineConfig(config: MaravillaConfig): MaravillaConfig {\n return config;\n}\n"],"mappings":";AAwNO,SAAS,aAAa,QAA0C;AACrE,SAAO;AACT;","names":[]}
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -1207,6 +1207,143 @@ var MediaRoom = class _MediaRoom {
|
|
|
1207
1207
|
}
|
|
1208
1208
|
};
|
|
1209
1209
|
|
|
1210
|
+
// src/push.ts
|
|
1211
|
+
var DEFAULT_BASE_PATH = "/_platform/push";
|
|
1212
|
+
var DEFAULT_SW_PATH = "/_platform/push/sw.js";
|
|
1213
|
+
var VISITOR_STORAGE_KEY = "maravilla.push.visitorId";
|
|
1214
|
+
function assertPushSupported() {
|
|
1215
|
+
if (typeof navigator === "undefined" || !("serviceWorker" in navigator)) {
|
|
1216
|
+
throw new Error("Web Push is not supported: serviceWorker is unavailable");
|
|
1217
|
+
}
|
|
1218
|
+
if (typeof window === "undefined" || !("PushManager" in window)) {
|
|
1219
|
+
throw new Error("Web Push is not supported: PushManager is unavailable");
|
|
1220
|
+
}
|
|
1221
|
+
}
|
|
1222
|
+
function base64UrlToArrayBuffer(input) {
|
|
1223
|
+
const padding = "=".repeat((4 - input.length % 4) % 4);
|
|
1224
|
+
const base64 = (input + padding).replace(/-/g, "+").replace(/_/g, "/");
|
|
1225
|
+
const raw = atob(base64);
|
|
1226
|
+
const buffer = new ArrayBuffer(raw.length);
|
|
1227
|
+
const view = new Uint8Array(buffer);
|
|
1228
|
+
for (let i = 0; i < raw.length; i++) {
|
|
1229
|
+
view[i] = raw.charCodeAt(i);
|
|
1230
|
+
}
|
|
1231
|
+
return buffer;
|
|
1232
|
+
}
|
|
1233
|
+
function arrayBufferToBase64Url(buffer) {
|
|
1234
|
+
if (!buffer) return void 0;
|
|
1235
|
+
const bytes = new Uint8Array(buffer);
|
|
1236
|
+
let binary = "";
|
|
1237
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
1238
|
+
binary += String.fromCharCode(bytes[i]);
|
|
1239
|
+
}
|
|
1240
|
+
return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
1241
|
+
}
|
|
1242
|
+
function randomUuid() {
|
|
1243
|
+
const c = typeof crypto !== "undefined" ? crypto : void 0;
|
|
1244
|
+
if (c && typeof c.randomUUID === "function") {
|
|
1245
|
+
return c.randomUUID();
|
|
1246
|
+
}
|
|
1247
|
+
const bytes = new Uint8Array(16);
|
|
1248
|
+
if (c && typeof c.getRandomValues === "function") {
|
|
1249
|
+
c.getRandomValues(bytes);
|
|
1250
|
+
} else {
|
|
1251
|
+
for (let i = 0; i < 16; i++) bytes[i] = Math.floor(Math.random() * 256);
|
|
1252
|
+
}
|
|
1253
|
+
bytes[6] = bytes[6] & 15 | 64;
|
|
1254
|
+
bytes[8] = bytes[8] & 63 | 128;
|
|
1255
|
+
const hex = Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
|
|
1256
|
+
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
|
|
1257
|
+
}
|
|
1258
|
+
function resolveVisitorId(userId, visitorId) {
|
|
1259
|
+
if (visitorId) return visitorId;
|
|
1260
|
+
if (userId) return null;
|
|
1261
|
+
try {
|
|
1262
|
+
const stored = window.localStorage.getItem(VISITOR_STORAGE_KEY);
|
|
1263
|
+
if (stored) return stored;
|
|
1264
|
+
const fresh = randomUuid();
|
|
1265
|
+
window.localStorage.setItem(VISITOR_STORAGE_KEY, fresh);
|
|
1266
|
+
return fresh;
|
|
1267
|
+
} catch {
|
|
1268
|
+
return randomUuid();
|
|
1269
|
+
}
|
|
1270
|
+
}
|
|
1271
|
+
async function fetchVapidPublicKey(basePath) {
|
|
1272
|
+
const res = await fetch(`${basePath}/vapid-public-key`, {
|
|
1273
|
+
method: "GET",
|
|
1274
|
+
credentials: "same-origin",
|
|
1275
|
+
headers: { Accept: "application/json" }
|
|
1276
|
+
});
|
|
1277
|
+
if (!res.ok) {
|
|
1278
|
+
throw new Error(`Failed to fetch VAPID public key: ${res.status} ${res.statusText}`);
|
|
1279
|
+
}
|
|
1280
|
+
const body = await res.json();
|
|
1281
|
+
if (!body || typeof body.publicKey !== "string" || body.publicKey.length === 0) {
|
|
1282
|
+
throw new Error("VAPID public key response is missing `publicKey`");
|
|
1283
|
+
}
|
|
1284
|
+
return body.publicKey;
|
|
1285
|
+
}
|
|
1286
|
+
function extractKeys(sub) {
|
|
1287
|
+
return {
|
|
1288
|
+
p256dh: arrayBufferToBase64Url(sub.getKey("p256dh")),
|
|
1289
|
+
auth: arrayBufferToBase64Url(sub.getKey("auth"))
|
|
1290
|
+
};
|
|
1291
|
+
}
|
|
1292
|
+
async function registerPush(opts = {}) {
|
|
1293
|
+
assertPushSupported();
|
|
1294
|
+
const basePath = opts.basePath ?? DEFAULT_BASE_PATH;
|
|
1295
|
+
const swPath = opts.swPath ?? DEFAULT_SW_PATH;
|
|
1296
|
+
const topics = opts.topics ?? [];
|
|
1297
|
+
const userId = opts.userId ?? null;
|
|
1298
|
+
const visitorId = resolveVisitorId(userId, opts.visitorId);
|
|
1299
|
+
const publicKey = await fetchVapidPublicKey(basePath);
|
|
1300
|
+
const registration = await navigator.serviceWorker.register(swPath);
|
|
1301
|
+
await navigator.serviceWorker.ready;
|
|
1302
|
+
const existing = await registration.pushManager.getSubscription();
|
|
1303
|
+
const subscription = existing ?? await registration.pushManager.subscribe({
|
|
1304
|
+
userVisibleOnly: true,
|
|
1305
|
+
applicationServerKey: base64UrlToArrayBuffer(publicKey)
|
|
1306
|
+
});
|
|
1307
|
+
const { p256dh, auth } = extractKeys(subscription);
|
|
1308
|
+
const res = await fetch(`${basePath}/subscribe`, {
|
|
1309
|
+
method: "POST",
|
|
1310
|
+
credentials: "same-origin",
|
|
1311
|
+
headers: { "Content-Type": "application/json", Accept: "application/json" },
|
|
1312
|
+
body: JSON.stringify({
|
|
1313
|
+
provider: "web-push",
|
|
1314
|
+
endpoint: subscription.endpoint,
|
|
1315
|
+
p256dh,
|
|
1316
|
+
auth,
|
|
1317
|
+
userId,
|
|
1318
|
+
visitorId,
|
|
1319
|
+
topics
|
|
1320
|
+
})
|
|
1321
|
+
});
|
|
1322
|
+
if (!res.ok) {
|
|
1323
|
+
throw new Error(`Subscribe failed: ${res.status} ${res.statusText}`);
|
|
1324
|
+
}
|
|
1325
|
+
const saved = await res.json();
|
|
1326
|
+
if (!saved || typeof saved.id !== "string" || saved.id.length === 0) {
|
|
1327
|
+
throw new Error("Subscribe response is missing `id`");
|
|
1328
|
+
}
|
|
1329
|
+
return { subscription, subscriptionId: saved.id };
|
|
1330
|
+
}
|
|
1331
|
+
async function unregisterPush(subscriptionId, opts = {}) {
|
|
1332
|
+
if (!subscriptionId) {
|
|
1333
|
+
throw new Error("subscriptionId is required");
|
|
1334
|
+
}
|
|
1335
|
+
const basePath = opts.basePath ?? DEFAULT_BASE_PATH;
|
|
1336
|
+
const res = await fetch(`${basePath}/unsubscribe`, {
|
|
1337
|
+
method: "POST",
|
|
1338
|
+
credentials: "same-origin",
|
|
1339
|
+
headers: { "Content-Type": "application/json" },
|
|
1340
|
+
body: JSON.stringify({ subscriptionId })
|
|
1341
|
+
});
|
|
1342
|
+
if (!res.ok && res.status !== 404) {
|
|
1343
|
+
throw new Error(`Unsubscribe failed: ${res.status} ${res.statusText}`);
|
|
1344
|
+
}
|
|
1345
|
+
}
|
|
1346
|
+
|
|
1210
1347
|
// src/index.ts
|
|
1211
1348
|
var cachedPlatform = void 0;
|
|
1212
1349
|
function getPlatform(options) {
|
|
@@ -1252,8 +1389,10 @@ export {
|
|
|
1252
1389
|
detachTrack,
|
|
1253
1390
|
getOrCreateClientId,
|
|
1254
1391
|
getPlatform,
|
|
1392
|
+
registerPush,
|
|
1255
1393
|
renFetch,
|
|
1256
1394
|
storageDelete,
|
|
1257
|
-
storageUpload
|
|
1395
|
+
storageUpload,
|
|
1396
|
+
unregisterPush
|
|
1258
1397
|
};
|
|
1259
1398
|
//# sourceMappingURL=index.js.map
|