@voltro/plugin-notifications 0.85.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 +5987 -0
- package/THIRD-PARTY-NOTICES.md +1125 -34
- package/dist/apnsPush.js +97 -0
- package/dist/channels.js +92 -0
- package/dist/durable.js +42 -0
- package/dist/errors.js +8 -0
- package/dist/expoPush.js +35 -0
- package/dist/fcmPush.js +82 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.js +203 -1322
- package/dist/rpc.d.ts +17 -1
- package/dist/rpc.js +27 -21
- package/dist/sendNotification.js +42 -0
- package/dist/service.js +246 -0
- package/dist/store.js +303 -0
- package/dist/webPush.js +161 -0
- package/dist/webPushProtocol.js +117 -0
- package/package.json +4 -4
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;
|
package/dist/rpc.js
CHANGED
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
import { Schema as e } from "effect";
|
|
2
2
|
import { defineMutation as t, defineQuery as n, reactivityChannel as r } from "@voltro/protocol";
|
|
3
3
|
//#region src/rpc.ts
|
|
4
|
-
var i = "@voltro/plugin-notifications", a = r("notifications-inbox"), o =
|
|
4
|
+
var i = "@voltro/plugin-notifications", a = r("notifications-inbox"), o = r("notifications-preferences"), s = e.Struct({
|
|
5
|
+
subjectId: e.String,
|
|
6
|
+
category: e.String,
|
|
7
|
+
channel: e.String,
|
|
8
|
+
enabled: e.Boolean
|
|
9
|
+
}), c = n({
|
|
5
10
|
name: "notifications.inbox",
|
|
6
11
|
source: a,
|
|
7
12
|
input: e.Struct({
|
|
@@ -10,43 +15,44 @@ var i = "@voltro/plugin-notifications", a = r("notifications-inbox"), o = n({
|
|
|
10
15
|
}),
|
|
11
16
|
output: e.Array(e.Record(e.String, e.Unknown)),
|
|
12
17
|
openAccess: "self-scoped read: returns only the calling subject's own inbox items"
|
|
13
|
-
}),
|
|
18
|
+
}), l = n({
|
|
14
19
|
name: "notifications.unreadCount",
|
|
15
20
|
source: a,
|
|
16
21
|
input: e.Struct({}),
|
|
17
22
|
output: e.Struct({ count: e.Number }),
|
|
18
23
|
openAccess: "self-scoped read: counts only the calling subject's own unread items"
|
|
19
|
-
}),
|
|
24
|
+
}), u = t({
|
|
20
25
|
name: "notifications.markRead",
|
|
21
26
|
input: e.Struct({ id: e.String }),
|
|
22
27
|
output: e.Struct({ ok: e.Boolean }),
|
|
23
28
|
openAccess: "self-scoped write: marks an item read only within the calling subject's own inbox"
|
|
24
|
-
}),
|
|
29
|
+
}), d = t({
|
|
25
30
|
name: "notifications.markUnread",
|
|
26
31
|
input: e.Struct({ id: e.String }),
|
|
27
32
|
output: e.Struct({ ok: e.Boolean }),
|
|
28
33
|
openAccess: "self-scoped write: marks an item unread only within the calling subject's own inbox"
|
|
29
|
-
}),
|
|
34
|
+
}), f = t({
|
|
30
35
|
name: "notifications.markAllRead",
|
|
31
36
|
input: e.Struct({}),
|
|
32
37
|
output: e.Struct({ count: e.Number }),
|
|
33
38
|
openAccess: "self-scoped write: clears unread state only in the calling subject's own inbox"
|
|
34
|
-
}),
|
|
39
|
+
}), p = t({
|
|
35
40
|
name: "notifications.archive",
|
|
36
41
|
input: e.Struct({ id: e.String }),
|
|
37
42
|
output: e.Struct({ ok: e.Boolean }),
|
|
38
43
|
openAccess: "self-scoped write: archives an item only within the calling subject's own inbox"
|
|
39
|
-
}),
|
|
44
|
+
}), m = t({
|
|
40
45
|
name: "notifications.unarchive",
|
|
41
46
|
input: e.Struct({ id: e.String }),
|
|
42
47
|
output: e.Struct({ ok: e.Boolean }),
|
|
43
48
|
openAccess: "self-scoped write: unarchives an item only within the calling subject's own inbox"
|
|
44
|
-
}),
|
|
49
|
+
}), h = n({
|
|
45
50
|
name: "notifications.preferences",
|
|
51
|
+
source: o,
|
|
46
52
|
input: e.Struct({}),
|
|
47
|
-
output: e.Array(
|
|
53
|
+
output: e.Array(s),
|
|
48
54
|
openAccess: "self-scoped read: returns only the calling subject's own channel preferences"
|
|
49
|
-
}),
|
|
55
|
+
}), g = t({
|
|
50
56
|
name: "notifications.setPreference",
|
|
51
57
|
input: e.Struct({
|
|
52
58
|
category: e.String,
|
|
@@ -55,17 +61,17 @@ var i = "@voltro/plugin-notifications", a = r("notifications-inbox"), o = n({
|
|
|
55
61
|
}),
|
|
56
62
|
output: e.Struct({ ok: e.Boolean }),
|
|
57
63
|
openAccess: "self-scoped write: toggles a channel only on the calling subject's own preferences"
|
|
58
|
-
}),
|
|
64
|
+
}), _ = t({
|
|
59
65
|
name: "notifications.subscribe",
|
|
60
66
|
input: e.Struct({ topic: e.String }),
|
|
61
67
|
output: e.Struct({ ok: e.Boolean }),
|
|
62
68
|
openAccess: "self-service: subscribes the calling subject itself to a topic (the subject is never caller-supplied)"
|
|
63
|
-
}),
|
|
69
|
+
}), v = t({
|
|
64
70
|
name: "notifications.unsubscribe",
|
|
65
71
|
input: e.Struct({ topic: e.String }),
|
|
66
72
|
output: e.Struct({ ok: e.Boolean }),
|
|
67
73
|
openAccess: "self-service: unsubscribes the calling subject itself from a topic"
|
|
68
|
-
}),
|
|
74
|
+
}), y = t({
|
|
69
75
|
name: "notifications.setQuietHours",
|
|
70
76
|
input: e.Struct({
|
|
71
77
|
startMinute: e.Number,
|
|
@@ -75,17 +81,17 @@ var i = "@voltro/plugin-notifications", a = r("notifications-inbox"), o = n({
|
|
|
75
81
|
}),
|
|
76
82
|
output: e.Struct({ ok: e.Boolean }),
|
|
77
83
|
openAccess: "self-scoped write: sets only the calling subject's own quiet-hours window"
|
|
78
|
-
}),
|
|
84
|
+
}), b = t({
|
|
79
85
|
name: "notifications.clearQuietHours",
|
|
80
86
|
input: e.Struct({}),
|
|
81
87
|
output: e.Struct({ ok: e.Boolean }),
|
|
82
88
|
openAccess: "self-scoped write: clears only the calling subject's own quiet-hours window"
|
|
83
|
-
}),
|
|
89
|
+
}), x = n({
|
|
84
90
|
name: "notifications.webPushPublicKey",
|
|
85
91
|
input: e.Struct({}),
|
|
86
92
|
output: e.Struct({ key: e.String }),
|
|
87
93
|
openAccess: "public application-server key — the value every subscribing browser is handed by design"
|
|
88
|
-
}),
|
|
94
|
+
}), S = t({
|
|
89
95
|
name: "notifications.webPushSubscribe",
|
|
90
96
|
input: e.Struct({
|
|
91
97
|
endpoint: e.String,
|
|
@@ -95,17 +101,17 @@ var i = "@voltro/plugin-notifications", a = r("notifications-inbox"), o = n({
|
|
|
95
101
|
}),
|
|
96
102
|
output: e.Struct({ ok: e.Boolean }),
|
|
97
103
|
openAccess: "self-scoped write: registers the calling subject's own browser subscription (the subject is never caller-supplied)"
|
|
98
|
-
}),
|
|
104
|
+
}), C = t({
|
|
99
105
|
name: "notifications.webPushUnsubscribe",
|
|
100
106
|
input: e.Struct({ endpoint: e.String }),
|
|
101
107
|
output: e.Struct({ ok: e.Boolean }),
|
|
102
108
|
openAccess: "self-scoped write: removes only a subscription the calling subject owns"
|
|
103
|
-
}),
|
|
109
|
+
}), w = n({
|
|
104
110
|
name: "notifications.webPushStatus",
|
|
105
111
|
input: e.Struct({}),
|
|
106
112
|
output: e.Struct({ endpoints: e.Number }),
|
|
107
113
|
openAccess: "self-scoped read: counts only the calling subject's own subscriptions"
|
|
108
|
-
}),
|
|
114
|
+
}), T = [
|
|
109
115
|
{
|
|
110
116
|
tag: "notifications.webPushPublicKey",
|
|
111
117
|
kind: "query",
|
|
@@ -138,7 +144,7 @@ var i = "@voltro/plugin-notifications", a = r("notifications-inbox"), o = n({
|
|
|
138
144
|
name: "webPushStatusDescriptor"
|
|
139
145
|
}
|
|
140
146
|
}
|
|
141
|
-
],
|
|
147
|
+
], E = [
|
|
142
148
|
{
|
|
143
149
|
tag: "notifications.inbox",
|
|
144
150
|
kind: "query",
|
|
@@ -245,4 +251,4 @@ var i = "@voltro/plugin-notifications", a = r("notifications-inbox"), o = n({
|
|
|
245
251
|
}
|
|
246
252
|
];
|
|
247
253
|
//#endregion
|
|
248
|
-
export { i as BASE_NAME,
|
|
254
|
+
export { i as BASE_NAME, s as ChannelPreferenceSchema, p as archiveDescriptor, b as clearQuietHoursDescriptor, c as inboxDescriptor, a as inboxFeed, f as markAllReadDescriptor, u as markReadDescriptor, d as markUnreadDescriptor, E as notificationsRpcClientImports, h as preferencesDescriptor, o as preferencesFeed, g as setPreferenceDescriptor, y as setQuietHoursDescriptor, _ as subscribeDescriptor, m as unarchiveDescriptor, l as unreadCountDescriptor, v as unsubscribeDescriptor, x as webPushPublicKeyDescriptor, T as webPushRpcClientImports, w as webPushStatusDescriptor, S as webPushSubscribeDescriptor, C as webPushUnsubscribeDescriptor };
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { Schema as e } from "effect";
|
|
2
|
+
//#region src/sendNotification.ts
|
|
3
|
+
var t = (e) => {
|
|
4
|
+
if (typeof e == "object" && e) {
|
|
5
|
+
let t = e;
|
|
6
|
+
if (typeof t.userId == "string") return [t.userId];
|
|
7
|
+
if (Array.isArray(t.userIds)) return t.userIds.filter((e) => typeof e == "string");
|
|
8
|
+
}
|
|
9
|
+
throw Error("sendNotification: pass `recipients` — the audience has neither `userId` nor `userIds`");
|
|
10
|
+
}, n = async (n, i, a) => {
|
|
11
|
+
let o = e.decodeUnknownSync(i.audience)(a.audience), s = e.decodeUnknownSync(i.payload)(a.payload), c = a.recipients ? await a.recipients(o) : t(o), l = [];
|
|
12
|
+
for (let e of c) {
|
|
13
|
+
let t = (a.localeFor ? await a.localeFor(e) : null) ?? a.defaultLocale ?? "en", o = await n.send(r(i, s, e, t, a.tenantId));
|
|
14
|
+
l.push({
|
|
15
|
+
recipientId: e,
|
|
16
|
+
locale: t,
|
|
17
|
+
result: o
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
return l;
|
|
21
|
+
}, r = (e, t, n, r, i) => {
|
|
22
|
+
let a = e.render(t, {
|
|
23
|
+
locale: r,
|
|
24
|
+
recipientId: n
|
|
25
|
+
});
|
|
26
|
+
return {
|
|
27
|
+
to: n,
|
|
28
|
+
category: e.category,
|
|
29
|
+
title: a.title,
|
|
30
|
+
body: a.body,
|
|
31
|
+
data: {
|
|
32
|
+
...a.data,
|
|
33
|
+
notification: e.name,
|
|
34
|
+
...a.image === void 0 ? {} : { image: a.image },
|
|
35
|
+
...a.actions === void 0 ? {} : { actions: a.actions }
|
|
36
|
+
},
|
|
37
|
+
...e.channels === void 0 ? {} : { channels: e.channels },
|
|
38
|
+
...i === void 0 ? {} : { tenantId: i }
|
|
39
|
+
};
|
|
40
|
+
};
|
|
41
|
+
//#endregion
|
|
42
|
+
export { t as defaultRecipients, r as sendInputFor, n as sendNotification };
|
package/dist/service.js
ADDED
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
import { resolveChannels as e } from "./channels.js";
|
|
2
|
+
import { randomUUID as t } from "node:crypto";
|
|
3
|
+
//#region src/service.ts
|
|
4
|
+
var n = (e) => ({
|
|
5
|
+
id: "inApp",
|
|
6
|
+
deliver: async (n) => {
|
|
7
|
+
await e.addInbox({
|
|
8
|
+
id: t(),
|
|
9
|
+
subjectId: n.to,
|
|
10
|
+
category: n.category,
|
|
11
|
+
title: n.title,
|
|
12
|
+
body: n.body,
|
|
13
|
+
data: n.data ?? {},
|
|
14
|
+
tenantId: n.tenantId ?? null,
|
|
15
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
}), r = (e, t) => {
|
|
19
|
+
try {
|
|
20
|
+
let n = new Intl.DateTimeFormat("en-US", {
|
|
21
|
+
timeZone: t,
|
|
22
|
+
hour: "2-digit",
|
|
23
|
+
minute: "2-digit",
|
|
24
|
+
hour12: !1
|
|
25
|
+
}).formatToParts(e), r = Number(n.find((e) => e.type === "hour")?.value ?? "0") % 24, i = Number(n.find((e) => e.type === "minute")?.value ?? "0");
|
|
26
|
+
return r * 60 + i;
|
|
27
|
+
} catch {
|
|
28
|
+
return e.getUTCHours() * 60 + e.getUTCMinutes();
|
|
29
|
+
}
|
|
30
|
+
}, i = (e, t) => {
|
|
31
|
+
if (e.startMinute === e.endMinute) return !1;
|
|
32
|
+
let n = r(t, e.tz ?? "UTC");
|
|
33
|
+
return e.startMinute < e.endMinute ? n >= e.startMinute && n < e.endMinute : n >= e.startMinute || n < e.endMinute;
|
|
34
|
+
}, a = (e, t) => {
|
|
35
|
+
let n = r(t, e.tz ?? "UTC"), i = e.endMinute - n;
|
|
36
|
+
return i <= 0 && (i += 1440), new Date(t.getTime() + i * 6e4);
|
|
37
|
+
}, o = (n) => {
|
|
38
|
+
let r = new Map(n.channels.map((e) => [e.id, e])), o = n.channels.map((e) => e.id), c = n.now ?? (() => (/* @__PURE__ */ new Date()).toISOString()), l = n.digestWindowMs ?? 0, u = new Map(Object.entries(n.deliveryGuards ?? {}));
|
|
39
|
+
if (u.size > 256 || [...u].some(([e, t]) => !/^[a-z][a-zA-Z0-9._-]{0,127}$/.test(e) || typeof t != "function")) throw Error("Invalid notification delivery guard registry");
|
|
40
|
+
let d = async (e) => {
|
|
41
|
+
let t = e.deliveryGuard;
|
|
42
|
+
if (t === void 0) return !0;
|
|
43
|
+
if (!t || typeof t.id != "string" || !t.payload || typeof t.payload != "object" || Array.isArray(t.payload)) throw Error("Invalid notification delivery guard reference");
|
|
44
|
+
let n = u.get(t.id);
|
|
45
|
+
if (!n) throw Error("Notification delivery guard unavailable");
|
|
46
|
+
let r = JSON.stringify(t.payload);
|
|
47
|
+
if (typeof r != "string" || r.length > 8192) throw Error("Notification delivery guard payload exceeds budget");
|
|
48
|
+
let i = JSON.parse(r);
|
|
49
|
+
if (typeof i != "object" || !i || Array.isArray(i)) throw Error("Invalid notification delivery guard payload");
|
|
50
|
+
let a = await n({
|
|
51
|
+
recipientId: e.to,
|
|
52
|
+
tenantId: e.tenantId ?? null,
|
|
53
|
+
payload: i
|
|
54
|
+
});
|
|
55
|
+
if (a !== !0 && a !== !1) throw Error("Invalid notification delivery guard decision");
|
|
56
|
+
return a;
|
|
57
|
+
}, f = () => ({
|
|
58
|
+
delivered: [],
|
|
59
|
+
failed: [],
|
|
60
|
+
skipped: [...o]
|
|
61
|
+
}), p = async (i) => {
|
|
62
|
+
if (!await d(i)) return f();
|
|
63
|
+
let a = await n.store.getPreferences(i.to), s = e(o, i.category, i.channels, a, i.to), l = o.filter((e) => !s.includes(e)), u = [], p = [], m = {
|
|
64
|
+
to: i.to,
|
|
65
|
+
category: i.category,
|
|
66
|
+
title: i.title,
|
|
67
|
+
body: i.body,
|
|
68
|
+
...i.data ? { data: i.data } : {},
|
|
69
|
+
tenantId: i.tenantId ?? null
|
|
70
|
+
};
|
|
71
|
+
for (let e of s) {
|
|
72
|
+
if (!await d(i)) {
|
|
73
|
+
l.push(e);
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
let a = r.get(e);
|
|
77
|
+
if (!a) continue;
|
|
78
|
+
if (a.deliverDetailed !== void 0) {
|
|
79
|
+
try {
|
|
80
|
+
let r = await a.deliverDetailed(m);
|
|
81
|
+
for (let a of r) await n.store.recordDelivery({
|
|
82
|
+
id: t(),
|
|
83
|
+
to: i.to,
|
|
84
|
+
category: i.category,
|
|
85
|
+
channel: e,
|
|
86
|
+
at: c(),
|
|
87
|
+
status: a.status,
|
|
88
|
+
endpoint: a.endpoint,
|
|
89
|
+
...a.error === void 0 ? {} : { error: a.error },
|
|
90
|
+
...a.clickToken === void 0 ? {} : { clickToken: a.clickToken }
|
|
91
|
+
});
|
|
92
|
+
let o = r.filter((e) => e.status === "failed");
|
|
93
|
+
r.length === 0 || o.length < r.length ? u.push(e) : p.push({
|
|
94
|
+
channel: e,
|
|
95
|
+
error: o[0]?.error ?? "all endpoints failed"
|
|
96
|
+
});
|
|
97
|
+
} catch (r) {
|
|
98
|
+
let a = r instanceof Error ? r.message : String(r);
|
|
99
|
+
p.push({
|
|
100
|
+
channel: e,
|
|
101
|
+
error: a
|
|
102
|
+
}), await n.store.recordDelivery({
|
|
103
|
+
id: t(),
|
|
104
|
+
to: i.to,
|
|
105
|
+
category: i.category,
|
|
106
|
+
channel: e,
|
|
107
|
+
at: c(),
|
|
108
|
+
status: "failed",
|
|
109
|
+
error: a
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
let o = {
|
|
115
|
+
id: t(),
|
|
116
|
+
to: i.to,
|
|
117
|
+
category: i.category,
|
|
118
|
+
channel: e,
|
|
119
|
+
at: c()
|
|
120
|
+
};
|
|
121
|
+
try {
|
|
122
|
+
await a.deliver(m), u.push(e), await n.store.recordDelivery({
|
|
123
|
+
...o,
|
|
124
|
+
status: "sent"
|
|
125
|
+
});
|
|
126
|
+
} catch (t) {
|
|
127
|
+
let r = t instanceof Error ? t.message : String(t);
|
|
128
|
+
p.push({
|
|
129
|
+
channel: e,
|
|
130
|
+
error: r
|
|
131
|
+
}), await n.store.recordDelivery({
|
|
132
|
+
...o,
|
|
133
|
+
status: "failed",
|
|
134
|
+
error: r
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
return {
|
|
139
|
+
delivered: u,
|
|
140
|
+
failed: p,
|
|
141
|
+
skipped: l
|
|
142
|
+
};
|
|
143
|
+
}, m = async (e) => {
|
|
144
|
+
let r = structuredClone(e);
|
|
145
|
+
if (!await d(r)) return f();
|
|
146
|
+
let o = await n.store.getQuietHours(r.to), s = new Date(c());
|
|
147
|
+
if (o && i(o, s)) return (o.policy ?? "hold") === "drop" ? {
|
|
148
|
+
delivered: [],
|
|
149
|
+
failed: [],
|
|
150
|
+
skipped: [],
|
|
151
|
+
held: "quiet-drop"
|
|
152
|
+
} : (await n.store.enqueueHeld({
|
|
153
|
+
id: t(),
|
|
154
|
+
kind: "quiet",
|
|
155
|
+
subjectId: r.to,
|
|
156
|
+
send: r,
|
|
157
|
+
flushAt: a(o, s).toISOString()
|
|
158
|
+
}), {
|
|
159
|
+
delivered: [],
|
|
160
|
+
failed: [],
|
|
161
|
+
skipped: [],
|
|
162
|
+
held: "quiet-held"
|
|
163
|
+
});
|
|
164
|
+
if (l > 0 && !r.channels) {
|
|
165
|
+
let e = (await n.store.pendingDigest(r.to))[0]?.flushAt ?? new Date(s.getTime() + l).toISOString();
|
|
166
|
+
return await n.store.enqueueHeld({
|
|
167
|
+
id: t(),
|
|
168
|
+
kind: "digest",
|
|
169
|
+
subjectId: r.to,
|
|
170
|
+
send: r,
|
|
171
|
+
flushAt: e
|
|
172
|
+
}), {
|
|
173
|
+
delivered: [],
|
|
174
|
+
failed: [],
|
|
175
|
+
skipped: [],
|
|
176
|
+
held: "digest"
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
return p(r);
|
|
180
|
+
};
|
|
181
|
+
return {
|
|
182
|
+
send: m,
|
|
183
|
+
inbox: (e, t) => n.store.listInbox(e, t),
|
|
184
|
+
unreadCount: (e) => n.store.unreadCount(e),
|
|
185
|
+
markRead: (e, t) => n.store.markRead(e, t),
|
|
186
|
+
markUnread: (e, t) => n.store.markUnread(e, t),
|
|
187
|
+
markAllRead: (e) => n.store.markAllRead(e),
|
|
188
|
+
setArchived: (e, t, r) => n.store.setArchived(e, t, r),
|
|
189
|
+
getPreferences: (e) => n.store.getPreferences(e),
|
|
190
|
+
setPreference: (e) => n.store.setPreference(e),
|
|
191
|
+
subscribe: (e, t, r) => n.store.subscribeTopic({
|
|
192
|
+
topic: e,
|
|
193
|
+
subjectId: t,
|
|
194
|
+
tenantId: r ?? null
|
|
195
|
+
}),
|
|
196
|
+
unsubscribe: (e, t) => n.store.unsubscribeTopic(e, t),
|
|
197
|
+
broadcast: async (e, t) => {
|
|
198
|
+
let r = await n.store.listTopicSubscribers(e), i = [];
|
|
199
|
+
for (let e of r) i.push(await m({
|
|
200
|
+
...t,
|
|
201
|
+
to: e.subjectId,
|
|
202
|
+
tenantId: t.tenantId ?? e.tenantId ?? null
|
|
203
|
+
}));
|
|
204
|
+
return {
|
|
205
|
+
recipients: r.length,
|
|
206
|
+
results: i
|
|
207
|
+
};
|
|
208
|
+
},
|
|
209
|
+
setQuietHours: (e) => n.store.setQuietHours(e),
|
|
210
|
+
clearQuietHours: (e) => n.store.clearQuietHours(e),
|
|
211
|
+
getQuietHours: (e) => n.store.getQuietHours(e),
|
|
212
|
+
flushDue: async (e = new Date(c())) => {
|
|
213
|
+
let t = await n.store.dueHeld(e.toISOString());
|
|
214
|
+
if (t.length === 0) return 0;
|
|
215
|
+
for (let e of t.filter((e) => e.kind === "quiet")) await p(e.send);
|
|
216
|
+
let r = /* @__PURE__ */ new Map();
|
|
217
|
+
for (let e of t.filter((e) => e.kind === "digest")) {
|
|
218
|
+
if (e.send.deliveryGuard !== void 0) {
|
|
219
|
+
await p(e.send);
|
|
220
|
+
continue;
|
|
221
|
+
}
|
|
222
|
+
let t = JSON.stringify([e.send.tenantId ?? null, e.subjectId]), n = r.get(t) ?? [];
|
|
223
|
+
n.push(e), r.set(t, n);
|
|
224
|
+
}
|
|
225
|
+
for (let e of r.values()) await p(s(e[0].subjectId, e));
|
|
226
|
+
return await n.store.clearHeld(t.map((e) => e.id)), t.length;
|
|
227
|
+
}
|
|
228
|
+
};
|
|
229
|
+
}, s = (e, t) => {
|
|
230
|
+
if (t.length === 1) return t[0].send;
|
|
231
|
+
let n = t.map((e) => ({
|
|
232
|
+
category: e.send.category,
|
|
233
|
+
title: e.send.title,
|
|
234
|
+
body: e.send.body
|
|
235
|
+
}));
|
|
236
|
+
return {
|
|
237
|
+
to: e,
|
|
238
|
+
category: "digest",
|
|
239
|
+
title: `${t.length} new notifications`,
|
|
240
|
+
body: n.map((e) => `• ${e.title}`).join("\n"),
|
|
241
|
+
data: { items: n },
|
|
242
|
+
tenantId: t[0].send.tenantId ?? null
|
|
243
|
+
};
|
|
244
|
+
};
|
|
245
|
+
//#endregion
|
|
246
|
+
export { o as buildNotificationService, n as inAppChannel, i as inQuietHours, r as minuteOfDayInZone, a as quietHoursEnd };
|