@voltro/plugin-notifications 0.86.0 → 0.87.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/CHANGELOG.md +5321 -0
- package/THIRD-PARTY-NOTICES.md +1132 -13
- package/dist/index.d.ts +18 -0
- package/dist/index.js +137 -136
- package/dist/rpc.d.ts +17 -1
- package/dist/rpc.js +27 -21
- package/dist/service.js +60 -26
- package/package.json +4 -4
package/dist/index.d.ts
CHANGED
|
@@ -65,6 +65,7 @@ export declare const buildNotificationService: (deps: {
|
|
|
65
65
|
* a specific `channels:` list coalesce into one digest per subject per window.
|
|
66
66
|
* 0 (default) disables digesting — every send goes out immediately. */
|
|
67
67
|
readonly digestWindowMs?: number;
|
|
68
|
+
readonly deliveryGuards?: Readonly<Record<string, NotificationDeliveryGuard>>;
|
|
68
69
|
}) => NotificationServiceShape;
|
|
69
70
|
|
|
70
71
|
/** A delivery channel. `id` is the channel key prefs target (e.g. 'email',
|
|
@@ -410,6 +411,20 @@ export declare const minuteOfDayInZone: (at: Date, tz: string) => number;
|
|
|
410
411
|
|
|
411
412
|
export declare const NOTIFICATION_SEND_EFFECT = "voltro.notification.send";
|
|
412
413
|
|
|
414
|
+
export declare type NotificationDeliveryGuard = (input: NotificationDeliveryGuardInput) => boolean | Promise<boolean>;
|
|
415
|
+
|
|
416
|
+
export declare interface NotificationDeliveryGuardInput {
|
|
417
|
+
readonly recipientId: string;
|
|
418
|
+
readonly tenantId: string | null;
|
|
419
|
+
readonly payload: Readonly<Record<string, unknown>>;
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
/** Persisted authorization reference; never forwarded to delivery channels. */
|
|
423
|
+
export declare interface NotificationDeliveryGuardRef {
|
|
424
|
+
readonly id: string;
|
|
425
|
+
readonly payload: Readonly<Record<string, unknown>>;
|
|
426
|
+
}
|
|
427
|
+
|
|
413
428
|
export declare interface NotificationDescriptor<Audience = unknown, Payload = unknown> {
|
|
414
429
|
readonly _tag: 'VoltroNotification';
|
|
415
430
|
readonly name: string;
|
|
@@ -533,6 +548,8 @@ export declare interface NotificationsPluginOptions {
|
|
|
533
548
|
* boundary by the scheduled flush). A send that forces its own `channels:`
|
|
534
549
|
* bypasses the digest. 0 (default) = every send delivers immediately. */
|
|
535
550
|
readonly digestWindowMs?: number;
|
|
551
|
+
/** Re-evaluated before actual delivery, including persisted quiet/digest holds. Unknown guards refuse. */
|
|
552
|
+
readonly deliveryGuards?: Readonly<Record<string, NotificationDeliveryGuard>>;
|
|
536
553
|
/** How often the scheduled flush drains due digest windows + quiet-hours
|
|
537
554
|
* deferrals. Default 30s. Only runs once the store is bound at boot. */
|
|
538
555
|
readonly flushIntervalMs?: number;
|
|
@@ -839,6 +856,7 @@ export declare const richPushOf: (data: Readonly<Record<string, unknown>> | unde
|
|
|
839
856
|
|
|
840
857
|
/** What a caller asks to send. `channels` overrides the default routing. */
|
|
841
858
|
export declare interface SendInput {
|
|
859
|
+
readonly deliveryGuard?: NotificationDeliveryGuardRef;
|
|
842
860
|
readonly to: string;
|
|
843
861
|
readonly category: string;
|
|
844
862
|
readonly title: string;
|
package/dist/index.js
CHANGED
|
@@ -2,76 +2,76 @@ import { PushTokenRejected as e } from "./errors.js";
|
|
|
2
2
|
import { consoleChannel as t, customChannel as n, emailChannel as r, pushChannel as i, resolveChannels as a, richPushOf as o, smsChannel as s, webhookChannel as c } from "./channels.js";
|
|
3
3
|
import { dataStoreNotificationStore as l, memoryNotificationStore as u } from "./store.js";
|
|
4
4
|
import { buildNotificationService as d, inAppChannel as f, inQuietHours as p, minuteOfDayInZone as m, quietHoursEnd as h } from "./service.js";
|
|
5
|
-
import { BASE_NAME as g, archiveDescriptor as
|
|
6
|
-
import { clickTokenMatches as
|
|
7
|
-
import { assertWebPushConfigured as
|
|
8
|
-
import { defineNotification as
|
|
9
|
-
import { sendNotification as
|
|
10
|
-
import { NOTIFICATION_SEND_EFFECT as
|
|
11
|
-
import { APNS_HOSTS as
|
|
12
|
-
import { FCM_SCOPE as
|
|
13
|
-
import { EXPO_PUSH_ENDPOINT as
|
|
14
|
-
import { Context as
|
|
15
|
-
import { boolean as
|
|
16
|
-
import { definePlugin as
|
|
5
|
+
import { BASE_NAME as g, archiveDescriptor as ee, clearQuietHoursDescriptor as te, inboxDescriptor as ne, inboxFeed as re, markAllReadDescriptor as ie, markReadDescriptor as ae, markUnreadDescriptor as oe, notificationsRpcClientImports as _, preferencesDescriptor as se, preferencesFeed as ce, setPreferenceDescriptor as le, setQuietHoursDescriptor as v, subscribeDescriptor as ue, unarchiveDescriptor as de, unreadCountDescriptor as fe, unsubscribeDescriptor as pe, webPushPublicKeyDescriptor as me, webPushRpcClientImports as he, webPushStatusDescriptor as ge, webPushSubscribeDescriptor as _e, webPushUnsubscribeDescriptor as ve } from "./rpc.js";
|
|
6
|
+
import { clickTokenMatches as ye, decryptWebPushPayload as y, encryptWebPushPayload as b, generateVapidPrivateKey as x, sendWebPush as S, vapidAuthorization as C, vapidPublicKeyFor as w } from "./webPushProtocol.js";
|
|
7
|
+
import { assertWebPushConfigured as be, bindWebPushStore as xe, dataStorePushSubscriptionStore as T, endpointHashOf as E, memoryPushSubscriptionStore as D, pushSubscriptionsTable as O, resetWebPushStoreForTest as k, webPushCell as A, webPushChannel as j, webPushPublicKey as M } from "./webPush.js";
|
|
8
|
+
import { defineNotification as Se } from "./define.js";
|
|
9
|
+
import { sendNotification as Ce } from "./sendNotification.js";
|
|
10
|
+
import { NOTIFICATION_SEND_EFFECT as we, enqueueNotification as Te, notificationOutboxHandler as Ee } from "./durable.js";
|
|
11
|
+
import { APNS_HOSTS as De, APNS_TOKEN_REJECTIONS as Oe, apnsBody as ke, apnsProviderToken as Ae, apnsTransport as je } from "./apnsPush.js";
|
|
12
|
+
import { FCM_SCOPE as Me, FCM_TOKEN_REJECTIONS as N, GOOGLE_TOKEN_URI as Ne, fcmAssertion as Pe, fcmMessage as Fe, fcmSendEndpoint as Ie, fcmTransport as Le, parseServiceAccount as Re } from "./fcmPush.js";
|
|
13
|
+
import { EXPO_PUSH_ENDPOINT as ze, expoPushTransport as Be, isExpoPushToken as Ve } from "./expoPush.js";
|
|
14
|
+
import { Context as He, Effect as P, Layer as Ue } from "effect";
|
|
15
|
+
import { boolean as We, id as F, integer as I, isNotNull as Ge, json as L, registerRetention as R, retentionTtlMsFromEnv as z, table as B, text as V, timestamp as H } from "@voltro/database";
|
|
16
|
+
import { definePlugin as Ke, pluginInstanceName as qe, publishReactivity as U } from "@voltro/protocol";
|
|
17
17
|
//#region src/index.ts
|
|
18
|
-
var W =
|
|
19
|
-
id:
|
|
20
|
-
subjectId:
|
|
21
|
-
category:
|
|
22
|
-
title:
|
|
23
|
-
body:
|
|
24
|
-
data:
|
|
25
|
-
readAt:
|
|
26
|
-
archivedAt:
|
|
27
|
-
createdAt:
|
|
28
|
-
tenantId:
|
|
29
|
-
}).renamedFrom("notification_inbox").index("byInboxSubject", ["subjectId"]).index("byInboxSubjectCreatedAt", ["subjectId", "createdAt"]).index("byInboxSubjectUnread", ["subjectId"], { where: "\"readAt\" IS NULL" }), G =
|
|
30
|
-
id:
|
|
31
|
-
subjectId:
|
|
32
|
-
category:
|
|
33
|
-
channel:
|
|
34
|
-
enabled:
|
|
18
|
+
var W = B("_voltro_notification_inbox", {
|
|
19
|
+
id: F({ prefix: "notif" }),
|
|
20
|
+
subjectId: V(),
|
|
21
|
+
category: V(),
|
|
22
|
+
title: V(),
|
|
23
|
+
body: V(),
|
|
24
|
+
data: L(),
|
|
25
|
+
readAt: H().nullable(),
|
|
26
|
+
archivedAt: H().nullable(),
|
|
27
|
+
createdAt: H().default("now"),
|
|
28
|
+
tenantId: V().nullable()
|
|
29
|
+
}).renamedFrom("notification_inbox").index("byInboxSubject", ["subjectId"]).index("byInboxSubjectCreatedAt", ["subjectId", "createdAt"]).index("byInboxSubjectUnread", ["subjectId"], { where: "\"readAt\" IS NULL" }), G = B("_voltro_notification_preferences", {
|
|
30
|
+
id: F({ prefix: "notifpref" }),
|
|
31
|
+
subjectId: V(),
|
|
32
|
+
category: V(),
|
|
33
|
+
channel: V(),
|
|
34
|
+
enabled: We().default(!0)
|
|
35
35
|
}).renamedFrom("notification_preferences").unique("byPreferencesSubjectCategoryChannel", [
|
|
36
36
|
"subjectId",
|
|
37
37
|
"category",
|
|
38
38
|
"channel"
|
|
39
|
-
]), K =
|
|
40
|
-
id:
|
|
41
|
-
recipient:
|
|
42
|
-
category:
|
|
43
|
-
channel:
|
|
44
|
-
status:
|
|
45
|
-
error:
|
|
46
|
-
at:
|
|
47
|
-
endpoint:
|
|
48
|
-
clickToken:
|
|
49
|
-
clickedAt:
|
|
50
|
-
}).renamedFrom("notification_deliveries").index("byDeliveriesAt", ["at"]).index("byDeliveriesClickToken", ["clickToken"]), q =
|
|
51
|
-
id:
|
|
52
|
-
topic:
|
|
53
|
-
subjectId:
|
|
54
|
-
tenantId:
|
|
55
|
-
}).renamedFrom("notification_topic_subscriptions").unique("byTopicSubject", ["topic", "subjectId"]).index("byTopic", ["topic"]), J =
|
|
56
|
-
id:
|
|
57
|
-
subjectId:
|
|
58
|
-
startMinute:
|
|
59
|
-
endMinute:
|
|
60
|
-
tz:
|
|
61
|
-
policy:
|
|
62
|
-
}).renamedFrom("notification_quiet_hours").unique("byQuietHoursSubject", ["subjectId"]), Y =
|
|
63
|
-
id:
|
|
64
|
-
kind:
|
|
65
|
-
subjectId:
|
|
66
|
-
send:
|
|
67
|
-
flushAt:
|
|
68
|
-
}).renamedFrom("notification_held").index("byHeldFlushAt", ["flushAt"]).index("byHeldSubjectKind", ["subjectId", "kind"]), X = class extends
|
|
39
|
+
]), K = B("_voltro_notification_deliveries", {
|
|
40
|
+
id: F({ prefix: "notifdlv" }),
|
|
41
|
+
recipient: V(),
|
|
42
|
+
category: V(),
|
|
43
|
+
channel: V(),
|
|
44
|
+
status: V(),
|
|
45
|
+
error: V().nullable(),
|
|
46
|
+
at: H().default("now"),
|
|
47
|
+
endpoint: V().nullable(),
|
|
48
|
+
clickToken: V().nullable(),
|
|
49
|
+
clickedAt: H().nullable()
|
|
50
|
+
}).renamedFrom("notification_deliveries").index("byDeliveriesAt", ["at"]).index("byDeliveriesClickToken", ["clickToken"]), q = B("_voltro_notification_topic_subscriptions", {
|
|
51
|
+
id: F({ prefix: "notiftsub" }),
|
|
52
|
+
topic: V(),
|
|
53
|
+
subjectId: V(),
|
|
54
|
+
tenantId: V().nullable()
|
|
55
|
+
}).renamedFrom("notification_topic_subscriptions").unique("byTopicSubject", ["topic", "subjectId"]).index("byTopic", ["topic"]), J = B("_voltro_notification_quiet_hours", {
|
|
56
|
+
id: F({ prefix: "notifqh" }),
|
|
57
|
+
subjectId: V(),
|
|
58
|
+
startMinute: I(),
|
|
59
|
+
endMinute: I(),
|
|
60
|
+
tz: V().default("UTC"),
|
|
61
|
+
policy: V().default("hold")
|
|
62
|
+
}).renamedFrom("notification_quiet_hours").unique("byQuietHoursSubject", ["subjectId"]), Y = B("_voltro_notification_held", {
|
|
63
|
+
id: F({ prefix: "notifheld" }),
|
|
64
|
+
kind: V(),
|
|
65
|
+
subjectId: V(),
|
|
66
|
+
send: L(),
|
|
67
|
+
flushAt: H()
|
|
68
|
+
}).renamedFrom("notification_held").index("byHeldFlushAt", ["flushAt"]).index("byHeldSubjectKind", ["subjectId", "kind"]), X = class extends He.Service()("@voltro/plugin-notifications/NotificationService") {}, Z = (e) => async (t) => {
|
|
69
69
|
if (e !== void 0) try {
|
|
70
70
|
let n = await e(t);
|
|
71
71
|
if (typeof n == "string" && n !== "") return n;
|
|
72
72
|
} catch {}
|
|
73
73
|
return t.request.subject.id ?? "anonymous";
|
|
74
|
-
}, Q = (e) => e.request.subject.tenantId ?? null, $ = g,
|
|
74
|
+
}, Q = (e) => e.request.subject.tenantId ?? null, $ = g, Je = { collects: [
|
|
75
75
|
{
|
|
76
76
|
category: "device-id",
|
|
77
77
|
linked: !0,
|
|
@@ -90,33 +90,33 @@ var W = V("_voltro_notification_inbox", {
|
|
|
90
90
|
tracking: !1,
|
|
91
91
|
purposes: ["app-functionality"]
|
|
92
92
|
}
|
|
93
|
-
] },
|
|
94
|
-
let n = Z(e.resolveSubjectId), r =
|
|
93
|
+
] }, Ye = (e = {}) => {
|
|
94
|
+
let n = Z(e.resolveSubjectId), r = qe({
|
|
95
95
|
base: $,
|
|
96
96
|
alias: e.alias,
|
|
97
97
|
instance: e.name
|
|
98
98
|
});
|
|
99
|
-
|
|
99
|
+
R({
|
|
100
100
|
source: "plugin",
|
|
101
101
|
table: "_voltro_notification_deliveries",
|
|
102
102
|
timeColumn: "at",
|
|
103
|
-
ttlMs:
|
|
103
|
+
ttlMs: z(process.env.VOLTRO_NOTIFICATION_DELIVERIES_TTL_HOURS, 2160),
|
|
104
104
|
envVar: "VOLTRO_NOTIFICATION_DELIVERIES_TTL_HOURS"
|
|
105
|
-
}),
|
|
105
|
+
}), R({
|
|
106
106
|
source: "plugin",
|
|
107
107
|
table: "_voltro_notification_inbox",
|
|
108
108
|
timeColumn: "createdAt",
|
|
109
|
-
ttlMs:
|
|
109
|
+
ttlMs: z(process.env.VOLTRO_NOTIFICATION_INBOX_READ_TTL_HOURS, 4320),
|
|
110
110
|
envVar: "VOLTRO_NOTIFICATION_INBOX_READ_TTL_HOURS",
|
|
111
|
-
where:
|
|
112
|
-
}),
|
|
111
|
+
where: Ge("readAt")
|
|
112
|
+
}), R({
|
|
113
113
|
source: "plugin",
|
|
114
114
|
table: "_voltro_notification_held",
|
|
115
115
|
timeColumn: "flushAt",
|
|
116
|
-
ttlMs:
|
|
116
|
+
ttlMs: z(process.env.VOLTRO_NOTIFICATION_HELD_TTL_HOURS, 168),
|
|
117
117
|
envVar: "VOLTRO_NOTIFICATION_HELD_TTL_HOURS"
|
|
118
118
|
});
|
|
119
|
-
let i = e.store === void 0, a = { current: e.store ?? u() }, o, s = (e) => e.then((e) => (
|
|
119
|
+
let i = e.store === void 0, a = { current: e.store ?? u() }, o, s = (e) => e.then((e) => (U(o, re), e)), c = (e) => e.then((e) => (U(o, ce), e)), p = {
|
|
120
120
|
addInbox: (e) => s(a.current.addInbox(e)),
|
|
121
121
|
listInbox: (e, t) => a.current.listInbox(e, t),
|
|
122
122
|
markRead: (e, t) => s(a.current.markRead(e, t)),
|
|
@@ -125,7 +125,7 @@ var W = V("_voltro_notification_inbox", {
|
|
|
125
125
|
setArchived: (e, t, n) => s(a.current.setArchived(e, t, n)),
|
|
126
126
|
unreadCount: (e) => a.current.unreadCount(e),
|
|
127
127
|
getPreferences: (e) => a.current.getPreferences(e),
|
|
128
|
-
setPreference: (e) => a.current.setPreference(e),
|
|
128
|
+
setPreference: (e) => c(a.current.setPreference(e)),
|
|
129
129
|
recordDelivery: (e) => a.current.recordDelivery(e),
|
|
130
130
|
listDeliveries: (e) => a.current.listDeliveries(e),
|
|
131
131
|
markClicked: (e) => a.current.markClicked(e),
|
|
@@ -139,55 +139,56 @@ var W = V("_voltro_notification_inbox", {
|
|
|
139
139
|
pendingDigest: (e) => a.current.pendingDigest(e),
|
|
140
140
|
dueHeld: (e) => a.current.dueHeld(e),
|
|
141
141
|
clearHeld: (e) => a.current.clearHeld(e)
|
|
142
|
-
},
|
|
143
|
-
channels:
|
|
144
|
-
store:
|
|
145
|
-
digestWindowMs:
|
|
142
|
+
}, m = e.channels ?? [t()], h = m.some((e) => e.id === "inApp") ? m : [...m, f(p)], g = e.digestWindowMs ?? 0, y = h.some((e) => e.id === "webPush"), b = d({
|
|
143
|
+
channels: h,
|
|
144
|
+
store: p,
|
|
145
|
+
digestWindowMs: g,
|
|
146
|
+
...e.deliveryGuards === void 0 ? {} : { deliveryGuards: e.deliveryGuards }
|
|
146
147
|
}), x, S = [
|
|
147
148
|
{
|
|
148
|
-
...
|
|
149
|
+
...ne,
|
|
149
150
|
description: "The caller's in-app inbox items.",
|
|
150
|
-
execute: (e, t) =>
|
|
151
|
+
execute: (e, t) => P.promise(async () => b.inbox(await n(t), e))
|
|
151
152
|
},
|
|
152
153
|
{
|
|
153
|
-
...
|
|
154
|
+
...fe,
|
|
154
155
|
description: "The caller's unread count.",
|
|
155
|
-
execute: (e, t) =>
|
|
156
|
+
execute: (e, t) => P.promise(async () => ({ count: await b.unreadCount(await n(t)) }))
|
|
156
157
|
},
|
|
157
158
|
{
|
|
158
|
-
...
|
|
159
|
+
...ae,
|
|
159
160
|
description: "Mark an inbox item read.",
|
|
160
|
-
execute: (e, t) =>
|
|
161
|
+
execute: (e, t) => P.promise(async () => ({ ok: await b.markRead(e.id, await n(t)) }))
|
|
161
162
|
},
|
|
162
163
|
{
|
|
163
|
-
...
|
|
164
|
+
...oe,
|
|
164
165
|
description: "Mark an inbox item unread again.",
|
|
165
|
-
execute: (e, t) =>
|
|
166
|
+
execute: (e, t) => P.promise(async () => ({ ok: await b.markUnread(e.id, await n(t)) }))
|
|
166
167
|
},
|
|
167
168
|
{
|
|
168
|
-
...
|
|
169
|
+
...ie,
|
|
169
170
|
description: "Mark every unread item in the caller's inbox read.",
|
|
170
|
-
execute: (e, t) =>
|
|
171
|
+
execute: (e, t) => P.promise(async () => ({ count: await b.markAllRead(await n(t)) }))
|
|
171
172
|
},
|
|
172
173
|
{
|
|
173
|
-
...
|
|
174
|
+
...ee,
|
|
174
175
|
description: "Archive an inbox item — a different state from read; this is what empties the inbox.",
|
|
175
|
-
execute: (e, t) =>
|
|
176
|
+
execute: (e, t) => P.promise(async () => ({ ok: await b.setArchived(e.id, await n(t), !0) }))
|
|
176
177
|
},
|
|
177
178
|
{
|
|
178
|
-
...
|
|
179
|
+
...de,
|
|
179
180
|
description: "Return an archived item to the inbox.",
|
|
180
|
-
execute: (e, t) =>
|
|
181
|
+
execute: (e, t) => P.promise(async () => ({ ok: await b.setArchived(e.id, await n(t), !1) }))
|
|
181
182
|
},
|
|
182
183
|
{
|
|
183
|
-
...
|
|
184
|
+
...se,
|
|
184
185
|
description: "The caller's channel preferences.",
|
|
185
|
-
execute: (e, t) =>
|
|
186
|
+
execute: (e, t) => P.promise(async () => b.getPreferences(await n(t)))
|
|
186
187
|
},
|
|
187
188
|
{
|
|
188
|
-
...
|
|
189
|
+
...le,
|
|
189
190
|
description: "Toggle a channel on/off for a category.",
|
|
190
|
-
execute: (e, t) =>
|
|
191
|
+
execute: (e, t) => P.promise(async () => {
|
|
191
192
|
let { category: r, channel: i, enabled: a } = e;
|
|
192
193
|
return await b.setPreference({
|
|
193
194
|
subjectId: await n(t),
|
|
@@ -198,19 +199,19 @@ var W = V("_voltro_notification_inbox", {
|
|
|
198
199
|
})
|
|
199
200
|
},
|
|
200
201
|
{
|
|
201
|
-
...
|
|
202
|
+
...ue,
|
|
202
203
|
description: "Subscribe the caller to a broadcast topic.",
|
|
203
|
-
execute: (e, t) =>
|
|
204
|
+
execute: (e, t) => P.promise(async () => (await b.subscribe(e.topic, await n(t), Q(t)), { ok: !0 }))
|
|
204
205
|
},
|
|
205
206
|
{
|
|
206
|
-
...
|
|
207
|
+
...pe,
|
|
207
208
|
description: "Unsubscribe the caller from a broadcast topic.",
|
|
208
|
-
execute: (e, t) =>
|
|
209
|
+
execute: (e, t) => P.promise(async () => (await b.unsubscribe(e.topic, await n(t)), { ok: !0 }))
|
|
209
210
|
},
|
|
210
211
|
{
|
|
211
|
-
...
|
|
212
|
+
...v,
|
|
212
213
|
description: "Set the caller's quiet-hours (DND) window.",
|
|
213
|
-
execute: (e, t) =>
|
|
214
|
+
execute: (e, t) => P.promise(async () => {
|
|
214
215
|
let r = e;
|
|
215
216
|
return await b.setQuietHours({
|
|
216
217
|
subjectId: await n(t),
|
|
@@ -222,25 +223,25 @@ var W = V("_voltro_notification_inbox", {
|
|
|
222
223
|
})
|
|
223
224
|
},
|
|
224
225
|
{
|
|
225
|
-
...
|
|
226
|
+
...te,
|
|
226
227
|
description: "Clear the caller's quiet-hours window.",
|
|
227
|
-
execute: (e, t) =>
|
|
228
|
+
execute: (e, t) => P.promise(async () => (await b.clearQuietHours(await n(t)), { ok: !0 }))
|
|
228
229
|
}
|
|
229
|
-
], C =
|
|
230
|
+
], C = y ? [
|
|
230
231
|
{
|
|
231
|
-
...
|
|
232
|
+
...me,
|
|
232
233
|
description: "The VAPID application-server key a subscribing browser needs.",
|
|
233
|
-
execute: () =>
|
|
234
|
+
execute: () => P.sync(() => ({ key: M(A().channelOptions?.privateKey) }))
|
|
234
235
|
},
|
|
235
236
|
{
|
|
236
|
-
...
|
|
237
|
+
..._e,
|
|
237
238
|
description: "Register the calling subject's browser push subscription (one row per endpoint).",
|
|
238
|
-
execute: (e, t) =>
|
|
239
|
+
execute: (e, t) => P.promise(async () => {
|
|
239
240
|
let r = e;
|
|
240
241
|
return await A().store.upsert({
|
|
241
242
|
subjectId: await n(t),
|
|
242
243
|
endpoint: r.endpoint,
|
|
243
|
-
endpointHash:
|
|
244
|
+
endpointHash: E(r.endpoint),
|
|
244
245
|
p256dh: r.p256dh,
|
|
245
246
|
auth: r.auth,
|
|
246
247
|
ua: r.ua ?? null,
|
|
@@ -249,14 +250,14 @@ var W = V("_voltro_notification_inbox", {
|
|
|
249
250
|
})
|
|
250
251
|
},
|
|
251
252
|
{
|
|
252
|
-
...
|
|
253
|
+
...ve,
|
|
253
254
|
description: "Remove one of the calling subject's own subscriptions.",
|
|
254
|
-
execute: (e, t) =>
|
|
255
|
+
execute: (e, t) => P.promise(async () => ({ ok: await A().store.removeForSubject(E(e.endpoint), await n(t)) }))
|
|
255
256
|
},
|
|
256
257
|
{
|
|
257
|
-
...
|
|
258
|
+
...ge,
|
|
258
259
|
description: "How many push endpoints the calling subject has registered.",
|
|
259
|
-
execute: (e, t) =>
|
|
260
|
+
execute: (e, t) => P.promise(async () => ({ endpoints: await A().store.countForSubject(await n(t)) }))
|
|
260
261
|
}
|
|
261
262
|
] : [], w = (e) => ({
|
|
262
263
|
kind: "json",
|
|
@@ -265,18 +266,18 @@ var W = V("_voltro_notification_inbox", {
|
|
|
265
266
|
kind: "json",
|
|
266
267
|
status: e,
|
|
267
268
|
data: { error: t }
|
|
268
|
-
}),
|
|
269
|
+
}), D = (e, t) => new URLSearchParams(new URL(e, "http://x").search).get(t), k = [{
|
|
269
270
|
method: "GET",
|
|
270
271
|
path: "/deliveries",
|
|
271
272
|
scope: "process",
|
|
272
273
|
description: "Recent delivery records (newest first) + per-channel + per-status counts.",
|
|
273
|
-
handler: () =>
|
|
274
|
-
let e = await
|
|
274
|
+
handler: () => P.promise(async () => {
|
|
275
|
+
let e = await p.listDeliveries({ limit: 200 }), t = {}, n = 0, r = 0;
|
|
275
276
|
for (let i of e) t[i.channel] = (t[i.channel] ?? 0) + 1, i.status === "sent" ? n++ : i.status === "failed" && r++;
|
|
276
277
|
let i = e.slice(0, 100).map(({ clickToken: e, ...t }) => t);
|
|
277
278
|
return w({
|
|
278
279
|
deliveries: i,
|
|
279
|
-
channels:
|
|
280
|
+
channels: h.map((e) => e.id),
|
|
280
281
|
stats: {
|
|
281
282
|
sent: n,
|
|
282
283
|
failed: r,
|
|
@@ -290,15 +291,15 @@ var W = V("_voltro_notification_inbox", {
|
|
|
290
291
|
scope: "process",
|
|
291
292
|
description: "In-app inbox for a subject (?subjectId=…).",
|
|
292
293
|
handler: (e) => {
|
|
293
|
-
let t =
|
|
294
|
-
return t ?
|
|
295
|
-
items: await
|
|
296
|
-
unread: await
|
|
297
|
-
})) :
|
|
294
|
+
let t = D(e.url, "subjectId");
|
|
295
|
+
return t ? P.promise(async () => w({
|
|
296
|
+
items: await p.listInbox(t, { limit: 100 }),
|
|
297
|
+
unread: await p.unreadCount(t)
|
|
298
|
+
})) : P.succeed(T(400, "subjectId query param required"));
|
|
298
299
|
}
|
|
299
300
|
}];
|
|
300
|
-
return
|
|
301
|
-
privacy:
|
|
301
|
+
return Ke({
|
|
302
|
+
privacy: Je,
|
|
302
303
|
name: r,
|
|
303
304
|
baseName: $,
|
|
304
305
|
description: "Unified notifications — email/slack/sms/push/in-app with preference routing, inbox + delivery log.",
|
|
@@ -310,9 +311,9 @@ var W = V("_voltro_notification_inbox", {
|
|
|
310
311
|
q,
|
|
311
312
|
J,
|
|
312
313
|
Y,
|
|
313
|
-
|
|
314
|
+
O
|
|
314
315
|
] } },
|
|
315
|
-
...
|
|
316
|
+
...y ? {
|
|
316
317
|
declaredEnv: [{
|
|
317
318
|
name: "VOLTRO_VAPID_PRIVATE_KEY",
|
|
318
319
|
required: !0,
|
|
@@ -328,12 +329,12 @@ var W = V("_voltro_notification_inbox", {
|
|
|
328
329
|
try {
|
|
329
330
|
t = JSON.parse(Buffer.from(e.rawBody).toString("utf8")).token;
|
|
330
331
|
} catch {}
|
|
331
|
-
if (typeof t != "string" || t === "" || !
|
|
332
|
+
if (typeof t != "string" || t === "" || !ye(t, t)) return {
|
|
332
333
|
status: 400,
|
|
333
334
|
body: JSON.stringify({ error: "token required" }),
|
|
334
335
|
contentType: "application/json"
|
|
335
336
|
};
|
|
336
|
-
let n = await
|
|
337
|
+
let n = await p.markClicked(t);
|
|
337
338
|
return {
|
|
338
339
|
status: n ? 200 : 404,
|
|
339
340
|
body: JSON.stringify({ ok: n }),
|
|
@@ -343,27 +344,27 @@ var W = V("_voltro_notification_inbox", {
|
|
|
343
344
|
maxBodyBytes: 4096
|
|
344
345
|
}]
|
|
345
346
|
} : {},
|
|
346
|
-
services:
|
|
347
|
+
services: Ue.succeed(X, b),
|
|
347
348
|
routes: [...S, ...C],
|
|
348
|
-
rpcClientDescriptors:
|
|
349
|
-
inspectEndpoints:
|
|
349
|
+
rpcClientDescriptors: y ? [..._, ...he] : _,
|
|
350
|
+
inspectEndpoints: k,
|
|
350
351
|
bindDataStore: (t) => {
|
|
351
|
-
o = t, i && (a.current = l(t)),
|
|
352
|
+
o = t, i && (a.current = l(t)), xe(t);
|
|
352
353
|
let n = e.flushIntervalMs ?? 3e4;
|
|
353
354
|
x = setInterval(() => {
|
|
354
355
|
b.flushDue().catch(() => {});
|
|
355
356
|
}, n), typeof x.unref == "function" && x.unref();
|
|
356
357
|
},
|
|
357
|
-
onActivate: (e) =>
|
|
358
|
-
|
|
359
|
-
channels:
|
|
360
|
-
digestWindowMs:
|
|
358
|
+
onActivate: (e) => P.sync(() => {
|
|
359
|
+
y && be(), e.logger.info("notifications active", {
|
|
360
|
+
channels: h.map((e) => e.id),
|
|
361
|
+
digestWindowMs: g
|
|
361
362
|
});
|
|
362
363
|
}),
|
|
363
|
-
onDeactivate: () =>
|
|
364
|
+
onDeactivate: () => P.sync(() => {
|
|
364
365
|
x && clearInterval(x), x = void 0;
|
|
365
366
|
})
|
|
366
367
|
});
|
|
367
368
|
};
|
|
368
369
|
//#endregion
|
|
369
|
-
export {
|
|
370
|
+
export { De as APNS_HOSTS, Oe as APNS_TOKEN_REJECTIONS, ze as EXPO_PUSH_ENDPOINT, Me as FCM_SCOPE, N as FCM_TOKEN_REJECTIONS, Ne as GOOGLE_TOKEN_URI, we as NOTIFICATION_SEND_EFFECT, X as NotificationService, e as PushTokenRejected, ke as apnsBody, Ae as apnsProviderToken, je as apnsTransport, d as buildNotificationService, t as consoleChannel, n as customChannel, l as dataStoreNotificationStore, T as dataStorePushSubscriptionStore, y as decryptWebPushPayload, Se as defineNotification, K as deliveriesTable, r as emailChannel, b as encryptWebPushPayload, E as endpointHashOf, Te as enqueueNotification, Be as expoPushTransport, Pe as fcmAssertion, Fe as fcmMessage, Ie as fcmSendEndpoint, Le as fcmTransport, x as generateVapidPrivateKey, Y as heldTable, f as inAppChannel, p as inQuietHours, W as inboxTable, Ve as isExpoPushToken, Z as makeSubjectId, u as memoryNotificationStore, D as memoryPushSubscriptionStore, m as minuteOfDayInZone, Ee as notificationOutboxHandler, Ye as notificationsPlugin, Re as parseServiceAccount, G as preferencesTable, i as pushChannel, O as pushSubscriptionsTable, h as quietHoursEnd, J as quietHoursTable, k as resetWebPushStoreForTest, a as resolveChannels, o as richPushOf, Ce as sendNotification, S as sendWebPush, s as smsChannel, q as topicSubscriptionsTable, C as vapidAuthorization, w as vapidPublicKeyFor, j as webPushChannel, M as webPushPublicKey, c as webhookChannel };
|
package/dist/rpc.d.ts
CHANGED
|
@@ -22,6 +22,14 @@ export declare const archiveDescriptor: MutationProcedureDescriptor<"notificatio
|
|
|
22
22
|
*/
|
|
23
23
|
export declare const BASE_NAME = "@voltro/plugin-notifications";
|
|
24
24
|
|
|
25
|
+
/** One channel switch of the calling subject for one category. */
|
|
26
|
+
export declare const ChannelPreferenceSchema: Schema.Struct<{
|
|
27
|
+
readonly subjectId: Schema.String;
|
|
28
|
+
readonly category: Schema.String;
|
|
29
|
+
readonly channel: Schema.String;
|
|
30
|
+
readonly enabled: Schema.Boolean;
|
|
31
|
+
}>;
|
|
32
|
+
|
|
25
33
|
export declare const clearQuietHoursDescriptor: MutationProcedureDescriptor<"notifications.clearQuietHours", Schema.Struct<{}>, Schema.Struct<{
|
|
26
34
|
readonly ok: Schema.Boolean;
|
|
27
35
|
}>, Schema.Never>;
|
|
@@ -55,7 +63,15 @@ export declare const markUnreadDescriptor: MutationProcedureDescriptor<"notifica
|
|
|
55
63
|
* emitted into the generated client group. Kept in lockstep with the exports. */
|
|
56
64
|
export declare const notificationsRpcClientImports: ReadonlyArray<PluginRpcClientDescriptor>;
|
|
57
65
|
|
|
58
|
-
export declare const preferencesDescriptor: QueryProcedureDescriptor<"notifications.preferences", Schema.Struct<{}>, Schema.$Array<Schema
|
|
66
|
+
export declare const preferencesDescriptor: QueryProcedureDescriptor<"notifications.preferences", Schema.Struct<{}>, Schema.$Array<Schema.Struct<{
|
|
67
|
+
readonly subjectId: Schema.String;
|
|
68
|
+
readonly category: Schema.String;
|
|
69
|
+
readonly channel: Schema.String;
|
|
70
|
+
readonly enabled: Schema.Boolean;
|
|
71
|
+
}>>, Schema.Never>;
|
|
72
|
+
|
|
73
|
+
/** Every preference write publishes this channel, so `notifications.preferences` subscribers see the change live. */
|
|
74
|
+
export declare const preferencesFeed: ReactivityChannel;
|
|
59
75
|
|
|
60
76
|
export declare const setPreferenceDescriptor: MutationProcedureDescriptor<"notifications.setPreference", Schema.Struct<{
|
|
61
77
|
readonly category: Schema.String;
|