@12-apps/notifications 4.11.0 → 4.13.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/dist/{chunk-2TJ4D2KE.js → chunk-4DTUD74E.js} +2 -2
- package/dist/{chunk-WVRODNXQ.js → chunk-53SH5ABN.js} +111 -46
- package/dist/chunk-53SH5ABN.js.map +1 -0
- package/dist/{chunk-6W7INOYQ.js → chunk-CSMFJJXY.js} +1 -1
- package/dist/chunk-CSMFJJXY.js.map +1 -0
- package/dist/{chunk-ZIR3ILFH.js → chunk-QPO6NSRR.js} +2 -2
- package/dist/{chunk-SWOWHIFE.js → chunk-XFODKRRB.js} +37 -2
- package/dist/chunk-XFODKRRB.js.map +1 -0
- package/dist/{create-api-notifications-CcPYrM3p.d.ts → create-api-notifications-cIEbFqoZ.d.ts} +132 -32
- package/dist/{create-web-notifications-CnaXx6km.d.ts → create-web-notifications-BODiQRK-.d.ts} +2 -2
- package/dist/{generators-qAD4fNPq.d.ts → generators-GUF-Kml-.d.ts} +1 -1
- package/dist/hono/index.d.ts +5 -5
- package/dist/hono/index.js +3 -3
- package/dist/index.d.ts +3 -3
- package/dist/index.js +9 -3
- package/dist/{jobs--fex87-q.d.ts → jobs-wKoOz91Q.d.ts} +1 -1
- package/dist/manifest/server.d.ts +6 -6
- package/dist/manifest/server.js +4 -4
- package/dist/manifest/web.d.ts +3 -3
- package/dist/manifest/web.js +1 -1
- package/dist/{preferences-screen-SXUIGECY.js → preferences-screen-YRPNUZS5.js} +2 -2
- package/dist/react/index.d.ts +4 -4
- package/dist/react/index.js +1 -1
- package/dist/server/index.d.ts +7 -7
- package/dist/server/index.js +4 -4
- package/dist/{types-BlqZkCWZ.d.ts → types-DPiePHJD.d.ts} +52 -1
- package/dist/web-push/index.d.ts +2 -2
- package/dist/{web-push-Dnyaha2z.d.ts → web-push-DUn_d_gj.d.ts} +12 -2
- package/dist/{wire-BG1kuoXX.d.ts → wire-CJka1AvM.d.ts} +65 -2
- package/package.json +2 -2
- package/prisma/migrations/20260914210000_scope_push_subscriptions_per_store/migration.sql +31 -0
- package/prisma/notifications.prisma +12 -0
- package/src/index.ts +4 -0
- package/src/preferences-core.ts +107 -0
- package/src/server/context.ts +15 -0
- package/src/server/db.ts +50 -8
- package/src/server/dispatch.ts +9 -2
- package/src/server/inbox.ts +87 -18
- package/src/server/index.ts +2 -0
- package/src/server/preferences.ts +29 -9
- package/src/server/push-subscriptions.ts +61 -7
- package/src/server/router.ts +42 -8
- package/src/server/routes.ts +14 -5
- package/src/server/transports/web-push.ts +15 -2
- package/src/types.ts +52 -1
- package/dist/chunk-6W7INOYQ.js.map +0 -1
- package/dist/chunk-SWOWHIFE.js.map +0 -1
- package/dist/chunk-WVRODNXQ.js.map +0 -1
- /package/dist/{chunk-2TJ4D2KE.js.map → chunk-4DTUD74E.js.map} +0 -0
- /package/dist/{chunk-ZIR3ILFH.js.map → chunk-QPO6NSRR.js.map} +0 -0
- /package/dist/{preferences-screen-SXUIGECY.js.map → preferences-screen-YRPNUZS5.js.map} +0 -0
|
@@ -306,7 +306,7 @@ import { Suspense as Suspense2, lazy as lazy2 } from "react";
|
|
|
306
306
|
import { jsx as jsx3 } from "react/jsx-runtime";
|
|
307
307
|
function lazyPreferencesPage(parts) {
|
|
308
308
|
const Bound = lazy2(async () => {
|
|
309
|
-
const { PreferencesScreen } = await import("./preferences-screen-
|
|
309
|
+
const { PreferencesScreen } = await import("./preferences-screen-YRPNUZS5.js");
|
|
310
310
|
return {
|
|
311
311
|
default: /* @__PURE__ */ __name((props) => /* @__PURE__ */ jsx3(PreferencesScreen, { ...props, ...parts }), "default")
|
|
312
312
|
};
|
|
@@ -395,4 +395,4 @@ export {
|
|
|
395
395
|
httpNotificationsTransport,
|
|
396
396
|
createWebNotifications
|
|
397
397
|
};
|
|
398
|
-
//# sourceMappingURL=chunk-
|
|
398
|
+
//# sourceMappingURL=chunk-4DTUD74E.js.map
|
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
import {
|
|
2
2
|
DEFAULT_CHANNEL_ROW,
|
|
3
3
|
UnknownNotificationRecipientError,
|
|
4
|
+
capToAvailable,
|
|
4
5
|
createGeneratorRegistry,
|
|
5
6
|
enabledChannelsOf,
|
|
7
|
+
explicitChoicesOf,
|
|
6
8
|
inboxWire,
|
|
7
|
-
mergeChoices,
|
|
8
9
|
mergeStoredRow,
|
|
9
|
-
normalizePhoneE164
|
|
10
|
-
|
|
10
|
+
normalizePhoneE164,
|
|
11
|
+
resolveTypeChannels
|
|
12
|
+
} from "./chunk-XFODKRRB.js";
|
|
11
13
|
import {
|
|
12
14
|
messagesOf
|
|
13
15
|
} from "./chunk-M2TVBVH2.js";
|
|
@@ -17,7 +19,7 @@ import {
|
|
|
17
19
|
import {
|
|
18
20
|
NOTIFICATION_CHANNELS,
|
|
19
21
|
taxonomyOf
|
|
20
|
-
} from "./chunk-
|
|
22
|
+
} from "./chunk-CSMFJJXY.js";
|
|
21
23
|
import {
|
|
22
24
|
renderEmail
|
|
23
25
|
} from "./chunk-EKUSNUBT.js";
|
|
@@ -437,7 +439,7 @@ function webPushTransport(declaration, subscriptions, extraDrivers = {}) {
|
|
|
437
439
|
supports: /* @__PURE__ */ __name((recipient) => recipient.pushSubscriptionCount > 0, "supports"),
|
|
438
440
|
format: formatWebPush,
|
|
439
441
|
async send(message, recipient) {
|
|
440
|
-
const rows = await subscriptions.list(recipient.userId);
|
|
442
|
+
const rows = await subscriptions.list(recipient.userId, recipient.clientId);
|
|
441
443
|
if (rows.length === 0) throw new Error("Recipient no longer has push subscriptions.");
|
|
442
444
|
const payload = JSON.stringify(message);
|
|
443
445
|
let delivered = 0;
|
|
@@ -680,7 +682,7 @@ var DEFAULT_MAX_DELIVERY_ATTEMPTS = 5;
|
|
|
680
682
|
var DEFAULT_SWEEP_TAKE = 200;
|
|
681
683
|
var DEFAULT_SWEEP_CUTOFF_MS = 5 * 6e4;
|
|
682
684
|
var RETRYABLE = ["FAILED", "QUEUED", "SENDING"];
|
|
683
|
-
async function loadRecipient(deps, userId) {
|
|
685
|
+
async function loadRecipient(deps, userId, clientId) {
|
|
684
686
|
const contact = await deps.contacts.getContact(userId);
|
|
685
687
|
if (!contact) return null;
|
|
686
688
|
return {
|
|
@@ -691,7 +693,11 @@ async function loadRecipient(deps, userId) {
|
|
|
691
693
|
// absent case has to stay distinguishable from a stated language, because
|
|
692
694
|
// that is what lets a generator apply its own default in one place.
|
|
693
695
|
...contact.locale === void 0 ? {} : { locale: contact.locale },
|
|
694
|
-
|
|
696
|
+
clientId,
|
|
697
|
+
// SCOPED, and this is what keeps `supports()` honest: it gates on this
|
|
698
|
+
// number, so an unscoped count would enqueue a WEB_PUSH delivery for a
|
|
699
|
+
// notification no reachable subscription exists for.
|
|
700
|
+
pushSubscriptionCount: await deps.pushSubscriptions.count(userId, clientId)
|
|
695
701
|
};
|
|
696
702
|
}
|
|
697
703
|
__name(loadRecipient, "loadRecipient");
|
|
@@ -757,7 +763,7 @@ async function dispatchOne(deps, notificationId) {
|
|
|
757
763
|
where: { notificationId, status: "QUEUED" }
|
|
758
764
|
});
|
|
759
765
|
if (queued.length === 0) return 0;
|
|
760
|
-
const recipient = await loadRecipient(deps, notification.userId);
|
|
766
|
+
const recipient = await loadRecipient(deps, notification.userId, notification.clientId);
|
|
761
767
|
if (!recipient) {
|
|
762
768
|
await abandonUnreachable(deps, client, queued, notification.userId);
|
|
763
769
|
return 0;
|
|
@@ -803,6 +809,10 @@ async function drainPending(deps, olderThanMs, take) {
|
|
|
803
809
|
__name(drainPending, "drainPending");
|
|
804
810
|
|
|
805
811
|
// src/server/inbox.ts
|
|
812
|
+
function scopeBranch(scope) {
|
|
813
|
+
return scope === void 0 ? [] : [{ OR: [{ clientId: scope }, { clientId: null }] }];
|
|
814
|
+
}
|
|
815
|
+
__name(scopeBranch, "scopeBranch");
|
|
806
816
|
var DEFAULT_PAGE = 20;
|
|
807
817
|
var MAX_PAGE = 100;
|
|
808
818
|
async function resolveAnchor(notifications, userId, cursor) {
|
|
@@ -811,31 +821,47 @@ async function resolveAnchor(notifications, userId, cursor) {
|
|
|
811
821
|
return { createdAt: anchor.createdAt, id: anchor.id };
|
|
812
822
|
}
|
|
813
823
|
__name(resolveAnchor, "resolveAnchor");
|
|
814
|
-
function pageWhere(userId, filter, anchor) {
|
|
824
|
+
function pageWhere(userId, filter, anchor, scope) {
|
|
825
|
+
const clauses = [
|
|
826
|
+
// `(createdAt, id) < (anchor.createdAt, anchor.id)`, as a portable `where`.
|
|
827
|
+
...anchor ? [
|
|
828
|
+
{
|
|
829
|
+
OR: [
|
|
830
|
+
{ createdAt: { lt: anchor.createdAt } },
|
|
831
|
+
{ createdAt: anchor.createdAt, id: { lt: anchor.id } }
|
|
832
|
+
]
|
|
833
|
+
}
|
|
834
|
+
] : [],
|
|
835
|
+
...scopeBranch(scope)
|
|
836
|
+
];
|
|
815
837
|
return {
|
|
816
838
|
userId,
|
|
817
839
|
deletedAt: null,
|
|
818
840
|
...filter === "unread" ? { readAt: null } : {},
|
|
819
|
-
|
|
820
|
-
...anchor ? {
|
|
821
|
-
OR: [
|
|
822
|
-
{ createdAt: { lt: anchor.createdAt } },
|
|
823
|
-
{ createdAt: anchor.createdAt, id: { lt: anchor.id } }
|
|
824
|
-
]
|
|
825
|
-
} : {}
|
|
841
|
+
...clauses.length > 0 ? { AND: clauses } : {}
|
|
826
842
|
};
|
|
827
843
|
}
|
|
828
844
|
__name(pageWhere, "pageWhere");
|
|
845
|
+
function unreadWhere(userId, scope) {
|
|
846
|
+
const scoped = scopeBranch(scope);
|
|
847
|
+
return {
|
|
848
|
+
userId,
|
|
849
|
+
deletedAt: null,
|
|
850
|
+
readAt: null,
|
|
851
|
+
...scoped.length > 0 ? { AND: scoped } : {}
|
|
852
|
+
};
|
|
853
|
+
}
|
|
854
|
+
__name(unreadWhere, "unreadWhere");
|
|
829
855
|
function createInboxStore(db) {
|
|
830
856
|
return {
|
|
831
857
|
/** The owner's inbox, newest first, keyset-paginated, deleted excluded. */
|
|
832
|
-
async list(userId, input = {}) {
|
|
858
|
+
async list(userId, input = {}, scope) {
|
|
833
859
|
const client = await db();
|
|
834
860
|
const limit = Math.min(Math.max(input.limit ?? DEFAULT_PAGE, 1), MAX_PAGE);
|
|
835
861
|
const anchor = input.cursor ? await resolveAnchor(client.notification, userId, input.cursor) : void 0;
|
|
836
862
|
if (input.cursor && !anchor) return { items: [], nextCursor: null };
|
|
837
863
|
const rows = await client.notification.findMany({
|
|
838
|
-
where: pageWhere(userId, input.filter, anchor),
|
|
864
|
+
where: pageWhere(userId, input.filter, anchor, scope),
|
|
839
865
|
// `id` tie-breaks equal timestamps so pages never skip/repeat.
|
|
840
866
|
orderBy: [{ createdAt: "desc" }, { id: "desc" }],
|
|
841
867
|
take: limit + 1
|
|
@@ -847,9 +873,9 @@ function createInboxStore(db) {
|
|
|
847
873
|
};
|
|
848
874
|
},
|
|
849
875
|
/** Unread badge count (non-deleted, unread). */
|
|
850
|
-
async unreadCount(userId) {
|
|
876
|
+
async unreadCount(userId, scope) {
|
|
851
877
|
const client = await db();
|
|
852
|
-
return client.notification.count({ where:
|
|
878
|
+
return client.notification.count({ where: unreadWhere(userId, scope) });
|
|
853
879
|
},
|
|
854
880
|
/**
|
|
855
881
|
* Mark specific notifications read. Only the owner's own, still-unread,
|
|
@@ -866,10 +892,10 @@ function createInboxStore(db) {
|
|
|
866
892
|
return result.count;
|
|
867
893
|
},
|
|
868
894
|
/** Mark every unread notification of the owner read ("mark all"). */
|
|
869
|
-
async markAllRead(userId) {
|
|
895
|
+
async markAllRead(userId, scope) {
|
|
870
896
|
const client = await db();
|
|
871
897
|
const result = await client.notification.updateMany({
|
|
872
|
-
where:
|
|
898
|
+
where: unreadWhere(userId, scope),
|
|
873
899
|
data: { readAt: /* @__PURE__ */ new Date() }
|
|
874
900
|
});
|
|
875
901
|
return result.count;
|
|
@@ -920,6 +946,14 @@ function createPreferenceStore(db, taxonomy, channelDefaults = {}) {
|
|
|
920
946
|
* A category outside the taxonomy is IGNORED rather than stored: the DB
|
|
921
947
|
* CHECK would reject it anyway, and a 500 from a stale client's extra key
|
|
922
948
|
* would fail the whole save including the toggle the user did flip.
|
|
949
|
+
*
|
|
950
|
+
* What is written is the user's EXPLICIT choices only, never their
|
|
951
|
+
* effective row. Merging onto the effective row wrote a boolean for all
|
|
952
|
+
* four channels the moment anyone touched any switch, so the row could
|
|
953
|
+
* never again say "no opinion" about a channel — which silently disabled
|
|
954
|
+
* every per-type and per-host default for that user, and defeated the
|
|
955
|
+
* missing-key fallback that lets a new channel ship without a data
|
|
956
|
+
* migration.
|
|
923
957
|
*/
|
|
924
958
|
async save(userId, input) {
|
|
925
959
|
const client = await db();
|
|
@@ -928,8 +962,7 @@ function createPreferenceStore(db, taxonomy, channelDefaults = {}) {
|
|
|
928
962
|
const existing = await client.notificationPreference.findUnique({
|
|
929
963
|
where: { userId_category: { userId, category } }
|
|
930
964
|
});
|
|
931
|
-
const
|
|
932
|
-
const channels = mergeChoices(current, choices);
|
|
965
|
+
const channels = { ...explicitChoicesOf(existing?.channels), ...choices };
|
|
933
966
|
await client.notificationPreference.upsert({
|
|
934
967
|
where: { userId_category: { userId, category } },
|
|
935
968
|
create: { userId, category, channels },
|
|
@@ -937,20 +970,27 @@ function createPreferenceStore(db, taxonomy, channelDefaults = {}) {
|
|
|
937
970
|
});
|
|
938
971
|
}
|
|
939
972
|
},
|
|
940
|
-
async enabledChannels(userId, category) {
|
|
973
|
+
async enabledChannels(userId, category, rules) {
|
|
941
974
|
const client = await db();
|
|
942
975
|
const row = await client.notificationPreference.findUnique({
|
|
943
976
|
where: { userId_category: { userId, category } }
|
|
944
977
|
});
|
|
945
|
-
return
|
|
946
|
-
|
|
947
|
-
|
|
978
|
+
return resolveTypeChannels({
|
|
979
|
+
stored: row?.channels,
|
|
980
|
+
categoryDefaults: defaultRow,
|
|
981
|
+
rules
|
|
982
|
+
});
|
|
948
983
|
}
|
|
949
984
|
};
|
|
950
985
|
}
|
|
951
986
|
__name(createPreferenceStore, "createPreferenceStore");
|
|
952
987
|
|
|
953
988
|
// src/server/push-subscriptions.ts
|
|
989
|
+
function reachableBy(userId, notificationClientId) {
|
|
990
|
+
if (notificationClientId === void 0 || notificationClientId === null) return { userId };
|
|
991
|
+
return { userId, OR: [{ clientId: null }, { clientId: notificationClientId }] };
|
|
992
|
+
}
|
|
993
|
+
__name(reachableBy, "reachableBy");
|
|
954
994
|
function createPushSubscriptionStore(db, logger) {
|
|
955
995
|
return {
|
|
956
996
|
async save(userId, input) {
|
|
@@ -970,12 +1010,18 @@ function createPushSubscriptionStore(db, logger) {
|
|
|
970
1010
|
endpoint: input.endpoint,
|
|
971
1011
|
p256dh: input.keys.p256dh,
|
|
972
1012
|
auth: input.keys.auth,
|
|
1013
|
+
clientId: input.clientId ?? null,
|
|
973
1014
|
userAgent: input.userAgent ?? null
|
|
974
1015
|
},
|
|
1016
|
+
// Re-stamped on every save, so the same browser moving between a store's
|
|
1017
|
+
// app and the platform corrects its own scope rather than keeping the
|
|
1018
|
+
// first origin it ever subscribed from. A scope change on the SAME user
|
|
1019
|
+
// is not a re-own and must not log one.
|
|
975
1020
|
update: {
|
|
976
1021
|
userId,
|
|
977
1022
|
p256dh: input.keys.p256dh,
|
|
978
1023
|
auth: input.keys.auth,
|
|
1024
|
+
clientId: input.clientId ?? null,
|
|
979
1025
|
userAgent: input.userAgent ?? null
|
|
980
1026
|
}
|
|
981
1027
|
});
|
|
@@ -984,18 +1030,20 @@ function createPushSubscriptionStore(db, logger) {
|
|
|
984
1030
|
const client = await db();
|
|
985
1031
|
await client.pushSubscription.deleteMany({ where: { userId, endpoint } });
|
|
986
1032
|
},
|
|
987
|
-
async count(userId) {
|
|
1033
|
+
async count(userId, notificationClientId) {
|
|
988
1034
|
const client = await db();
|
|
989
|
-
return client.pushSubscription.count({ where:
|
|
1035
|
+
return client.pushSubscription.count({ where: reachableBy(userId, notificationClientId) });
|
|
990
1036
|
},
|
|
991
1037
|
async isRegisteredTo(userId, endpoint) {
|
|
992
1038
|
const client = await db();
|
|
993
1039
|
const row = await client.pushSubscription.findUnique({ where: { endpoint } });
|
|
994
1040
|
return row?.userId === userId;
|
|
995
1041
|
},
|
|
996
|
-
async list(userId) {
|
|
1042
|
+
async list(userId, notificationClientId) {
|
|
997
1043
|
const client = await db();
|
|
998
|
-
const rows = await client.pushSubscription.findMany({
|
|
1044
|
+
const rows = await client.pushSubscription.findMany({
|
|
1045
|
+
where: reachableBy(userId, notificationClientId)
|
|
1046
|
+
});
|
|
999
1047
|
return rows.map((row) => ({
|
|
1000
1048
|
id: row.id,
|
|
1001
1049
|
endpoint: row.endpoint,
|
|
@@ -1012,12 +1060,13 @@ function createPushSubscriptionStore(db, logger) {
|
|
|
1012
1060
|
__name(createPushSubscriptionStore, "createPushSubscriptionStore");
|
|
1013
1061
|
|
|
1014
1062
|
// src/server/routes.ts
|
|
1015
|
-
async function channelAvailability(deps, userId) {
|
|
1063
|
+
async function channelAvailability(deps, userId, scopeClientId) {
|
|
1016
1064
|
const contact = await deps.contacts.getContact(userId);
|
|
1017
1065
|
const recipient = {
|
|
1018
1066
|
userId,
|
|
1019
1067
|
email: contact?.email ?? null,
|
|
1020
1068
|
phone: contact?.phone ?? null,
|
|
1069
|
+
clientId: scopeClientId,
|
|
1021
1070
|
pushSubscriptionCount: 1
|
|
1022
1071
|
};
|
|
1023
1072
|
return Object.fromEntries(
|
|
@@ -1028,10 +1077,10 @@ async function channelAvailability(deps, userId) {
|
|
|
1028
1077
|
);
|
|
1029
1078
|
}
|
|
1030
1079
|
__name(channelAvailability, "channelAvailability");
|
|
1031
|
-
async function preferencesPayload(deps, userId) {
|
|
1080
|
+
async function preferencesPayload(deps, userId, scopeClientId) {
|
|
1032
1081
|
const [preferences, availability] = await Promise.all([
|
|
1033
1082
|
deps.preferences.get(userId),
|
|
1034
|
-
channelAvailability(deps, userId)
|
|
1083
|
+
channelAvailability(deps, userId, scopeClientId)
|
|
1035
1084
|
]);
|
|
1036
1085
|
return { preferences, availability, categories: [...deps.categories] };
|
|
1037
1086
|
}
|
|
@@ -1045,7 +1094,8 @@ function inboxRoutes(deps) {
|
|
|
1045
1094
|
async ({ actor, query, locale }) => ok(
|
|
1046
1095
|
await deps.inbox.list(
|
|
1047
1096
|
actor.userId,
|
|
1048
|
-
parseListQuery(query, messagesOf(deps, locale))
|
|
1097
|
+
parseListQuery(query, messagesOf(deps, locale)),
|
|
1098
|
+
actor.scopeClientId
|
|
1049
1099
|
)
|
|
1050
1100
|
)
|
|
1051
1101
|
)
|
|
@@ -1056,7 +1106,7 @@ function inboxRoutes(deps) {
|
|
|
1056
1106
|
handle: guarded(
|
|
1057
1107
|
async ({ actor }) => (
|
|
1058
1108
|
// Polled by the SPAs, so it stays a single indexed COUNT.
|
|
1059
|
-
ok({ count: await deps.inbox.unreadCount(actor.userId) })
|
|
1109
|
+
ok({ count: await deps.inbox.unreadCount(actor.userId, actor.scopeClientId) })
|
|
1060
1110
|
)
|
|
1061
1111
|
)
|
|
1062
1112
|
},
|
|
@@ -1065,7 +1115,7 @@ function inboxRoutes(deps) {
|
|
|
1065
1115
|
path: "/notifications/mark-read",
|
|
1066
1116
|
handle: guarded(async ({ actor, body, locale }) => {
|
|
1067
1117
|
const target = parseMarkReadBody(body, messagesOf(deps, locale));
|
|
1068
|
-
const updated = "all" in target ? await deps.inbox.markAllRead(actor.userId) : await deps.inbox.markRead(actor.userId, target.ids);
|
|
1118
|
+
const updated = "all" in target ? await deps.inbox.markAllRead(actor.userId, actor.scopeClientId) : await deps.inbox.markRead(actor.userId, target.ids);
|
|
1069
1119
|
if (updated > 0) deps.onInboxChanged?.(actor.userId);
|
|
1070
1120
|
return ok({ updated });
|
|
1071
1121
|
})
|
|
@@ -1091,7 +1141,9 @@ function preferenceRoutes(deps) {
|
|
|
1091
1141
|
{
|
|
1092
1142
|
method: "GET",
|
|
1093
1143
|
path: "/notification-preferences",
|
|
1094
|
-
handle: guarded(
|
|
1144
|
+
handle: guarded(
|
|
1145
|
+
async ({ actor }) => ok(await preferencesPayload(deps, actor.userId, actor.scopeClientId ?? null))
|
|
1146
|
+
)
|
|
1095
1147
|
},
|
|
1096
1148
|
{
|
|
1097
1149
|
method: "PUT",
|
|
@@ -1101,7 +1153,7 @@ function preferenceRoutes(deps) {
|
|
|
1101
1153
|
actor.userId,
|
|
1102
1154
|
parsePreferencesBody(body, messagesOf(deps, locale))
|
|
1103
1155
|
);
|
|
1104
|
-
return ok(await preferencesPayload(deps, actor.userId));
|
|
1156
|
+
return ok(await preferencesPayload(deps, actor.userId, actor.scopeClientId ?? null));
|
|
1105
1157
|
})
|
|
1106
1158
|
}
|
|
1107
1159
|
];
|
|
@@ -1132,6 +1184,9 @@ function pushRoutes(deps) {
|
|
|
1132
1184
|
const input = parsePushSubscriptionBody(body, messagesOf(deps, locale));
|
|
1133
1185
|
const userAgent = headers?.["user-agent"];
|
|
1134
1186
|
await deps.pushSubscriptions.save(actor.userId, {
|
|
1187
|
+
// The HOST's resolved origin, never the body's — a caller cannot
|
|
1188
|
+
// choose which store's app their browser counts as.
|
|
1189
|
+
clientId: actor.scopeClientId ?? null,
|
|
1135
1190
|
...input,
|
|
1136
1191
|
...userAgent ? { userAgent } : {}
|
|
1137
1192
|
});
|
|
@@ -1189,13 +1244,19 @@ function announce(deps, notification) {
|
|
|
1189
1244
|
}
|
|
1190
1245
|
}
|
|
1191
1246
|
__name(announce, "announce");
|
|
1192
|
-
async function resolveChannels(deps, event,
|
|
1193
|
-
const
|
|
1247
|
+
async function resolveChannels(deps, event, generator, recipient) {
|
|
1248
|
+
const rules = { channels: generator.channels, channelDefaults: generator.channelDefaults };
|
|
1249
|
+
const enabled = await deps.preferences.enabledChannels(
|
|
1250
|
+
event.recipient.userId,
|
|
1251
|
+
generator.category,
|
|
1252
|
+
rules
|
|
1253
|
+
);
|
|
1194
1254
|
const supported = enabled.filter((channel) => {
|
|
1195
1255
|
const transport = deps.transports.get(channel);
|
|
1196
1256
|
return transport !== null && transport.supports(recipient);
|
|
1197
1257
|
});
|
|
1198
|
-
|
|
1258
|
+
const permitted = await applyPolicy(deps, event.recipient.clientId, supported);
|
|
1259
|
+
return capToAvailable(permitted, generator.channels);
|
|
1199
1260
|
}
|
|
1200
1261
|
__name(resolveChannels, "resolveChannels");
|
|
1201
1262
|
async function commit(deps, event, category, content, channels) {
|
|
@@ -1232,10 +1293,14 @@ function createNotificationRouter(deps) {
|
|
|
1232
1293
|
drainPending: /* @__PURE__ */ __name((olderThanMs = DEFAULT_SWEEP_CUTOFF_MS, take = DEFAULT_SWEEP_TAKE) => drainPending(deps, olderThanMs, take), "drainPending"),
|
|
1233
1294
|
async notify(event, options = {}) {
|
|
1234
1295
|
const generator = deps.generators.resolve(event.type);
|
|
1235
|
-
const recipient = await loadRecipient(
|
|
1296
|
+
const recipient = await loadRecipient(
|
|
1297
|
+
deps,
|
|
1298
|
+
event.recipient.userId,
|
|
1299
|
+
event.recipient.clientId ?? null
|
|
1300
|
+
);
|
|
1236
1301
|
if (!recipient) throw new UnknownNotificationRecipientError(event.recipient.userId);
|
|
1237
1302
|
const content = generator.generate(event.payload, { locale: recipient.locale });
|
|
1238
|
-
const channels = await resolveChannels(deps, event, generator
|
|
1303
|
+
const channels = await resolveChannels(deps, event, generator, recipient);
|
|
1239
1304
|
const notification = await commit(deps, event, generator.category, content, channels);
|
|
1240
1305
|
announce(deps, {
|
|
1241
1306
|
notificationId: notification.id,
|
|
@@ -1353,4 +1418,4 @@ export {
|
|
|
1353
1418
|
createTransportRegistry,
|
|
1354
1419
|
createApiNotifications
|
|
1355
1420
|
};
|
|
1356
|
-
//# sourceMappingURL=chunk-
|
|
1421
|
+
//# sourceMappingURL=chunk-53SH5ABN.js.map
|