@maravilla-labs/platform 0.1.41 → 0.1.44
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/index.d.ts +216 -2
- package/dist/index.js +65 -33
- package/dist/index.js.map +1 -1
- package/dist/push.d.ts +34 -3
- package/dist/push.js +65 -33
- package/dist/push.js.map +1 -1
- package/package.json +1 -1
- package/src/push.ts +110 -37
- package/src/types.ts +234 -0
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { LocalParticipant } from 'livekit-client';
|
|
2
|
-
export { RegisterPushOptions, RegisterPushResult, registerPush, unregisterPush } from './push.js';
|
|
2
|
+
export { RegisterPushOptions, RegisterPushResult, offsetBefore, registerPush, unregisterPush } from './push.js';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Media service for video/audio room management.
|
|
@@ -947,6 +947,213 @@ interface PolicyService {
|
|
|
947
947
|
/** `true` when Layer 2 is active for this request. */
|
|
948
948
|
isEnabled(): boolean;
|
|
949
949
|
}
|
|
950
|
+
/**
|
|
951
|
+
* Target selector for Web Push sends. Combine fields to narrow — all
|
|
952
|
+
* specified conditions must match for a subscription to receive the push.
|
|
953
|
+
*
|
|
954
|
+
* @example
|
|
955
|
+
* ```typescript
|
|
956
|
+
* // Every subscription tagged with "waitlist"
|
|
957
|
+
* await platform.push.send({ topic: 'waitlist' }, notification);
|
|
958
|
+
*
|
|
959
|
+
* // Every device belonging to one authenticated user
|
|
960
|
+
* await platform.push.send({ userId: 'u_42' }, notification);
|
|
961
|
+
*
|
|
962
|
+
* // "This specific user's subscription for this specific invite"
|
|
963
|
+
* await platform.push.send({ userId: 'u_42', topic: 'invite:abc:rsvp' }, notification);
|
|
964
|
+
* ```
|
|
965
|
+
*/
|
|
966
|
+
interface PushTarget {
|
|
967
|
+
userId?: string;
|
|
968
|
+
visitorId?: string;
|
|
969
|
+
topic?: string;
|
|
970
|
+
userIds?: string[];
|
|
971
|
+
topics?: string[];
|
|
972
|
+
onlyActive?: boolean;
|
|
973
|
+
}
|
|
974
|
+
/** Shape of a Web Push notification payload. */
|
|
975
|
+
interface NotificationPayload {
|
|
976
|
+
/** Required — the notification headline shown on lock screens and the notification shade. */
|
|
977
|
+
title: string;
|
|
978
|
+
body?: string;
|
|
979
|
+
icon?: string;
|
|
980
|
+
badge?: string;
|
|
981
|
+
image?: string;
|
|
982
|
+
/** Browsers dedupe notifications sharing a tag. */
|
|
983
|
+
tag?: string;
|
|
984
|
+
/** Where to navigate when the notification is clicked. */
|
|
985
|
+
url?: string;
|
|
986
|
+
/** Arbitrary JSON delivered to the service worker alongside the notification. */
|
|
987
|
+
data?: Record<string, unknown>;
|
|
988
|
+
/** Seconds the push service holds the message if the device is offline. */
|
|
989
|
+
ttl?: number;
|
|
990
|
+
urgency?: 'very-low' | 'low' | 'normal' | 'high';
|
|
991
|
+
}
|
|
992
|
+
/**
|
|
993
|
+
* Options for `platform.push.schedule(...)`.
|
|
994
|
+
*
|
|
995
|
+
* @example
|
|
996
|
+
* ```typescript
|
|
997
|
+
* // Remind every RSVP'd guest one hour before the event.
|
|
998
|
+
* await platform.push.schedule(
|
|
999
|
+
* { topic: `invite:${invite.id}` },
|
|
1000
|
+
* { title: invite.title, body: 'Your event is in one hour' },
|
|
1001
|
+
* {
|
|
1002
|
+
* at: offsetBefore(invite.event_date, '1h'),
|
|
1003
|
+
* key: `invite:${invite.id}:reminder-1h`,
|
|
1004
|
+
* }
|
|
1005
|
+
* );
|
|
1006
|
+
* ```
|
|
1007
|
+
*/
|
|
1008
|
+
interface ScheduleOptions {
|
|
1009
|
+
/**
|
|
1010
|
+
* When to send. Absolute `Date` or ISO-8601 string; the server treats
|
|
1011
|
+
* bare (no-offset) strings as UTC.
|
|
1012
|
+
*/
|
|
1013
|
+
at: Date | string;
|
|
1014
|
+
/**
|
|
1015
|
+
* Idempotency key scoped to your project. Re-calling `schedule` with the
|
|
1016
|
+
* same key atomically replaces the prior pending job — safe to call on
|
|
1017
|
+
* every save of an invite whose event date may change.
|
|
1018
|
+
*/
|
|
1019
|
+
key: string;
|
|
1020
|
+
/** Maximum delivery attempts before the job is marked failed. Defaults to 3. */
|
|
1021
|
+
maxAttempts?: number;
|
|
1022
|
+
/**
|
|
1023
|
+
* If set, the job re-queues after every successful send and fires again
|
|
1024
|
+
* this many seconds later. Use for daily digests (`86400`), weekly
|
|
1025
|
+
* updates (`604800`), or any fixed-interval loop. `cancelScheduled(key)`
|
|
1026
|
+
* stops the loop.
|
|
1027
|
+
*/
|
|
1028
|
+
everySeconds?: number;
|
|
1029
|
+
}
|
|
1030
|
+
/** A single scheduled push job as returned by `listScheduled` / `getScheduled`. */
|
|
1031
|
+
interface ScheduledJob {
|
|
1032
|
+
jobId: string;
|
|
1033
|
+
key?: string;
|
|
1034
|
+
/** Next fire time — unix seconds. Convert with `new Date(scheduledFor * 1000)`. */
|
|
1035
|
+
scheduledFor: number;
|
|
1036
|
+
status: 'pending' | 'running' | 'succeeded' | 'failed';
|
|
1037
|
+
attempts: number;
|
|
1038
|
+
maxAttempts: number;
|
|
1039
|
+
lastError?: string;
|
|
1040
|
+
createdAt: number;
|
|
1041
|
+
updatedAt: number;
|
|
1042
|
+
/** Populated once the job has fired at least once. */
|
|
1043
|
+
sentAt?: number;
|
|
1044
|
+
/** Set when the job recurs — `schedule()` was called with `everySeconds`. */
|
|
1045
|
+
recurringIntervalSecs?: number;
|
|
1046
|
+
}
|
|
1047
|
+
/** Filter passed to `listScheduled`. */
|
|
1048
|
+
interface ListScheduledFilter {
|
|
1049
|
+
status?: 'pending' | 'running' | 'succeeded' | 'failed';
|
|
1050
|
+
limit?: number;
|
|
1051
|
+
offset?: number;
|
|
1052
|
+
}
|
|
1053
|
+
/** Counts by status, as returned by `queueStats`. */
|
|
1054
|
+
interface QueueStats {
|
|
1055
|
+
pending: number;
|
|
1056
|
+
running: number;
|
|
1057
|
+
succeeded: number;
|
|
1058
|
+
failed: number;
|
|
1059
|
+
}
|
|
1060
|
+
/** Outcome of a single `platform.push.send(...)` fan-out. */
|
|
1061
|
+
interface SendReport {
|
|
1062
|
+
attempted: number;
|
|
1063
|
+
succeeded: number;
|
|
1064
|
+
gone: number;
|
|
1065
|
+
failed: number;
|
|
1066
|
+
errors?: Array<{
|
|
1067
|
+
subscriptionId: string;
|
|
1068
|
+
message: string;
|
|
1069
|
+
}>;
|
|
1070
|
+
}
|
|
1071
|
+
/** Shape of a stored Web Push subscription. */
|
|
1072
|
+
interface StoredPushSubscription {
|
|
1073
|
+
id: string;
|
|
1074
|
+
provider: 'web-push' | 'apns' | 'fcm';
|
|
1075
|
+
endpoint: string;
|
|
1076
|
+
p256dh?: string | null;
|
|
1077
|
+
auth?: string | null;
|
|
1078
|
+
userId?: string | null;
|
|
1079
|
+
visitorId?: string | null;
|
|
1080
|
+
userAgent?: string | null;
|
|
1081
|
+
topics: string[];
|
|
1082
|
+
createdAt: number;
|
|
1083
|
+
lastSeenAt?: number | null;
|
|
1084
|
+
expiresAt?: number | null;
|
|
1085
|
+
isActive: boolean;
|
|
1086
|
+
}
|
|
1087
|
+
/** Aggregate counts across every subscription in the project. */
|
|
1088
|
+
interface SubscriptionCounts {
|
|
1089
|
+
total: number;
|
|
1090
|
+
byTopic: Array<[string, number]>;
|
|
1091
|
+
byProvider: Array<[string, number]>;
|
|
1092
|
+
}
|
|
1093
|
+
/** Public VAPID config for the current project. */
|
|
1094
|
+
interface PublicPushConfig {
|
|
1095
|
+
vapidPublic: string;
|
|
1096
|
+
contactEmail: string;
|
|
1097
|
+
updatedAt: number;
|
|
1098
|
+
}
|
|
1099
|
+
/**
|
|
1100
|
+
* Server-side Web Push service. Access via `platform.push` inside your
|
|
1101
|
+
* runtime code.
|
|
1102
|
+
*/
|
|
1103
|
+
interface PushService {
|
|
1104
|
+
/**
|
|
1105
|
+
* Fan out a notification to every active subscription matching `target`.
|
|
1106
|
+
* Blocks until every device has been tried.
|
|
1107
|
+
*/
|
|
1108
|
+
send(target: PushTarget, notification: NotificationPayload): Promise<SendReport>;
|
|
1109
|
+
/**
|
|
1110
|
+
* Fire-and-forget variant. The request handler can return immediately;
|
|
1111
|
+
* the dispatch continues in the background. Best-effort — a delivery
|
|
1112
|
+
* restart mid-dispatch loses in-flight sends.
|
|
1113
|
+
*/
|
|
1114
|
+
sendBackground(target: PushTarget, notification: NotificationPayload): Promise<void>;
|
|
1115
|
+
/**
|
|
1116
|
+
* Queue a notification for a future time. Idempotent by `key` — repeated
|
|
1117
|
+
* calls with the same key replace the prior pending job. Set `everySeconds`
|
|
1118
|
+
* for recurring digests.
|
|
1119
|
+
*/
|
|
1120
|
+
schedule(target: PushTarget, notification: NotificationPayload, opts: ScheduleOptions): Promise<{
|
|
1121
|
+
jobId: string;
|
|
1122
|
+
}>;
|
|
1123
|
+
/** Cancel the pending scheduled job with this idempotency key. */
|
|
1124
|
+
cancelScheduled(key: string): Promise<{
|
|
1125
|
+
canceled: number;
|
|
1126
|
+
}>;
|
|
1127
|
+
/** List scheduled jobs for this project, optionally filtered by status. */
|
|
1128
|
+
listScheduled(filter?: ListScheduledFilter): Promise<ScheduledJob[]>;
|
|
1129
|
+
/** Look up a single scheduled job by idempotency key. */
|
|
1130
|
+
getScheduled(key: string): Promise<ScheduledJob | null>;
|
|
1131
|
+
/** Per-status counts for the scheduled queue. */
|
|
1132
|
+
queueStats(): Promise<QueueStats>;
|
|
1133
|
+
/** List subscriptions — useful for admin UIs and debugging. */
|
|
1134
|
+
list(filter?: {
|
|
1135
|
+
topic?: string;
|
|
1136
|
+
userId?: string;
|
|
1137
|
+
visitorId?: string;
|
|
1138
|
+
onlyActive?: boolean;
|
|
1139
|
+
limit?: number;
|
|
1140
|
+
offset?: number;
|
|
1141
|
+
}): Promise<StoredPushSubscription[]>;
|
|
1142
|
+
/** Aggregate counts grouped by topic and provider. */
|
|
1143
|
+
counts(): Promise<SubscriptionCounts>;
|
|
1144
|
+
/** Remove a subscription by id. */
|
|
1145
|
+
unsubscribe(subscriptionId: string): Promise<void>;
|
|
1146
|
+
/** Remove a subscription by its push service endpoint URL. */
|
|
1147
|
+
unsubscribeByEndpoint(endpoint: string): Promise<void>;
|
|
1148
|
+
/** Fetch the project's current VAPID public key + contact email. */
|
|
1149
|
+
getVapidConfig(): Promise<PublicPushConfig>;
|
|
1150
|
+
/**
|
|
1151
|
+
* Rotate the project's VAPID keypair. **Every existing subscription
|
|
1152
|
+
* silently stops working** — browsers bind subscriptions to the key they
|
|
1153
|
+
* saw at subscribe time. Confirm with your users before calling.
|
|
1154
|
+
*/
|
|
1155
|
+
rotateVapidKeys(): Promise<PublicPushConfig>;
|
|
1156
|
+
}
|
|
950
1157
|
interface Platform {
|
|
951
1158
|
/** Environment containing all available platform services */
|
|
952
1159
|
env: PlatformEnv;
|
|
@@ -958,6 +1165,13 @@ interface Platform {
|
|
|
958
1165
|
auth: AuthService;
|
|
959
1166
|
/** Per-request Layer 2 policy toggle (Layer 1 isolation always applies) */
|
|
960
1167
|
policy: PolicyService;
|
|
1168
|
+
/**
|
|
1169
|
+
* Web Push — send, schedule, or query browser push notifications for
|
|
1170
|
+
* logged-in users and anonymous visitors. Available when Push is enabled
|
|
1171
|
+
* in project settings; `undefined` when running against the dev-server
|
|
1172
|
+
* fallback that doesn't proxy to delivery.
|
|
1173
|
+
*/
|
|
1174
|
+
push?: PushService;
|
|
961
1175
|
}
|
|
962
1176
|
|
|
963
1177
|
interface RenEvent {
|
|
@@ -1320,4 +1534,4 @@ declare function getPlatform(options?: {
|
|
|
1320
1534
|
*/
|
|
1321
1535
|
declare function clearPlatformCache(): void;
|
|
1322
1536
|
|
|
1323
|
-
export { type AuthCaller, type AuthField, type AuthService, type AuthSession, type AuthUser, type Database, type DbFindOptions, type KvListResult, type KvNamespace, type LoginOptions, MediaLocalParticipant, type MediaParticipant, type MediaParticipantInfo, MediaRoom, MediaRoomEvent, type MediaRoomInfo, type MediaRoomInfoSettings, type MediaRoomOptions, type MediaService, type MediaTokenResult, type MediaTrackPublication, type Platform, type PlatformEnv, type PolicyService, type PresenceMember, type PresenceService, RealtimeClient, type RealtimeClientOptions, type RealtimeEvent, type RealtimeService, type RegisterOptions, RemoteMediaService, RenClient, type RenClientOptions, type RenEvent, type Storage$1 as Storage, type StoragePutStreamSource, type TrackKind, type TrackSource, type UpdateUserOptions, type UserListFilter, type UserListResponse, type VideoResolution, attachTrack, clearPlatformCache, detachTrack, getOrCreateClientId, getPlatform, renFetch, storageDelete, storageUpload };
|
|
1537
|
+
export { type AuthCaller, type AuthField, type AuthService, type AuthSession, type AuthUser, type Database, type DbFindOptions, type KvListResult, type KvNamespace, type ListScheduledFilter, type LoginOptions, MediaLocalParticipant, type MediaParticipant, type MediaParticipantInfo, MediaRoom, MediaRoomEvent, type MediaRoomInfo, type MediaRoomInfoSettings, type MediaRoomOptions, type MediaService, type MediaTokenResult, type MediaTrackPublication, type NotificationPayload, type Platform, type PlatformEnv, type PolicyService, type PresenceMember, type PresenceService, type PublicPushConfig, type PushService, type PushTarget, type QueueStats, RealtimeClient, type RealtimeClientOptions, type RealtimeEvent, type RealtimeService, type RegisterOptions, RemoteMediaService, RenClient, type RenClientOptions, type RenEvent, type ScheduleOptions, type ScheduledJob, type SendReport, type Storage$1 as Storage, type StoragePutStreamSource, type StoredPushSubscription, type SubscriptionCounts, type TrackKind, type TrackSource, type UpdateUserOptions, type UserListFilter, type UserListResponse, type VideoResolution, attachTrack, clearPlatformCache, detachTrack, getOrCreateClientId, getPlatform, renFetch, storageDelete, storageUpload };
|
package/dist/index.js
CHANGED
|
@@ -1208,9 +1208,10 @@ var MediaRoom = class _MediaRoom {
|
|
|
1208
1208
|
};
|
|
1209
1209
|
|
|
1210
1210
|
// src/push.ts
|
|
1211
|
-
var DEFAULT_BASE_PATH = "/
|
|
1212
|
-
var DEFAULT_SW_PATH = "/
|
|
1211
|
+
var DEFAULT_BASE_PATH = "/_rt/push";
|
|
1212
|
+
var DEFAULT_SW_PATH = "/_rt/push/sw.js";
|
|
1213
1213
|
var VISITOR_STORAGE_KEY = "maravilla.push.visitorId";
|
|
1214
|
+
var REGISTER_TIMEOUT_MS = 1e4;
|
|
1214
1215
|
function assertPushSupported() {
|
|
1215
1216
|
if (typeof navigator === "undefined" || !("serviceWorker" in navigator)) {
|
|
1216
1217
|
throw new Error("Web Push is not supported: serviceWorker is unavailable");
|
|
@@ -1296,37 +1297,45 @@ async function registerPush(opts = {}) {
|
|
|
1296
1297
|
const topics = opts.topics ?? [];
|
|
1297
1298
|
const userId = opts.userId ?? null;
|
|
1298
1299
|
const visitorId = resolveVisitorId(userId, opts.visitorId);
|
|
1299
|
-
const
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1300
|
+
const timeout = new Promise(
|
|
1301
|
+
(_, reject) => setTimeout(
|
|
1302
|
+
() => reject(new Error(`registerPush timed out after ${REGISTER_TIMEOUT_MS}ms`)),
|
|
1303
|
+
REGISTER_TIMEOUT_MS
|
|
1304
|
+
)
|
|
1305
|
+
);
|
|
1306
|
+
const flow = (async () => {
|
|
1307
|
+
const publicKey = await fetchVapidPublicKey(basePath);
|
|
1308
|
+
const registration = await navigator.serviceWorker.register(swPath);
|
|
1309
|
+
const existing = await registration.pushManager.getSubscription();
|
|
1310
|
+
const subscription = existing ?? await registration.pushManager.subscribe({
|
|
1311
|
+
userVisibleOnly: true,
|
|
1312
|
+
applicationServerKey: base64UrlToArrayBuffer(publicKey)
|
|
1313
|
+
});
|
|
1314
|
+
const { p256dh, auth } = extractKeys(subscription);
|
|
1315
|
+
const res = await fetch(`${basePath}/subscribe`, {
|
|
1316
|
+
method: "POST",
|
|
1317
|
+
credentials: "same-origin",
|
|
1318
|
+
headers: { "Content-Type": "application/json", Accept: "application/json" },
|
|
1319
|
+
body: JSON.stringify({
|
|
1320
|
+
provider: "web-push",
|
|
1321
|
+
endpoint: subscription.endpoint,
|
|
1322
|
+
p256dh,
|
|
1323
|
+
auth,
|
|
1324
|
+
userId,
|
|
1325
|
+
visitorId,
|
|
1326
|
+
topics
|
|
1327
|
+
})
|
|
1328
|
+
});
|
|
1329
|
+
if (!res.ok) {
|
|
1330
|
+
throw new Error(`Subscribe failed: ${res.status} ${res.statusText}`);
|
|
1331
|
+
}
|
|
1332
|
+
const saved = await res.json();
|
|
1333
|
+
if (!saved || typeof saved.id !== "string" || saved.id.length === 0) {
|
|
1334
|
+
throw new Error("Subscribe response is missing `id`");
|
|
1335
|
+
}
|
|
1336
|
+
return { subscription, subscriptionId: saved.id };
|
|
1337
|
+
})();
|
|
1338
|
+
return Promise.race([flow, timeout]);
|
|
1330
1339
|
}
|
|
1331
1340
|
async function unregisterPush(subscriptionId, opts = {}) {
|
|
1332
1341
|
if (!subscriptionId) {
|
|
@@ -1343,6 +1352,28 @@ async function unregisterPush(subscriptionId, opts = {}) {
|
|
|
1343
1352
|
throw new Error(`Unsubscribe failed: ${res.status} ${res.statusText}`);
|
|
1344
1353
|
}
|
|
1345
1354
|
}
|
|
1355
|
+
function offsetBefore(anchor, offset) {
|
|
1356
|
+
const anchorDate = anchor instanceof Date ? anchor : new Date(anchor);
|
|
1357
|
+
if (Number.isNaN(anchorDate.getTime())) {
|
|
1358
|
+
throw new Error(`offsetBefore: invalid anchor "${String(anchor)}"`);
|
|
1359
|
+
}
|
|
1360
|
+
const match = /^(\d+)\s*(s|m|h|d|w)$/i.exec(offset.trim());
|
|
1361
|
+
if (!match) {
|
|
1362
|
+
throw new Error(
|
|
1363
|
+
`offsetBefore: invalid offset "${offset}" \u2014 expected something like "30m", "1h", "2d", "1w"`
|
|
1364
|
+
);
|
|
1365
|
+
}
|
|
1366
|
+
const amount = Number(match[1]);
|
|
1367
|
+
const unit = match[2].toLowerCase();
|
|
1368
|
+
const UNIT_MS = {
|
|
1369
|
+
s: 1e3,
|
|
1370
|
+
m: 6e4,
|
|
1371
|
+
h: 36e5,
|
|
1372
|
+
d: 864e5,
|
|
1373
|
+
w: 6048e5
|
|
1374
|
+
};
|
|
1375
|
+
return new Date(anchorDate.getTime() - amount * UNIT_MS[unit]);
|
|
1376
|
+
}
|
|
1346
1377
|
|
|
1347
1378
|
// src/index.ts
|
|
1348
1379
|
var cachedPlatform = void 0;
|
|
@@ -1389,6 +1420,7 @@ export {
|
|
|
1389
1420
|
detachTrack,
|
|
1390
1421
|
getOrCreateClientId,
|
|
1391
1422
|
getPlatform,
|
|
1423
|
+
offsetBefore,
|
|
1392
1424
|
registerPush,
|
|
1393
1425
|
renFetch,
|
|
1394
1426
|
storageDelete,
|