@aglyn/tenant-data-admin 1.0.0-beta.219 → 1.0.0-beta.221

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.
Files changed (102) hide show
  1. package/package.json +8 -8
  2. package/src/lib/server/api-http.js +5 -1
  3. package/src/lib/server/api-http.js.map +1 -1
  4. package/src/lib/server/api-v1-kit.d.ts +7 -0
  5. package/src/lib/server/api-v1-kit.js.map +1 -1
  6. package/src/lib/server/api-v1-resources.d.ts +1 -2
  7. package/src/lib/server/api-v1-resources.js +0 -1
  8. package/src/lib/server/api-v1-resources.js.map +1 -1
  9. package/src/lib/server/campaign-conversion-attribution.d.ts +1 -1
  10. package/src/lib/server/campaign-conversion-attribution.js +3 -3
  11. package/src/lib/server/campaign-conversion-attribution.js.map +1 -1
  12. package/src/lib/server/card-payment-velocity.d.ts +9 -0
  13. package/src/lib/server/card-payment-velocity.js +28 -2
  14. package/src/lib/server/card-payment-velocity.js.map +1 -1
  15. package/src/lib/server/consent-group-carry.js +1 -1
  16. package/src/lib/server/consent-group-carry.js.map +1 -1
  17. package/src/lib/server/consent-group-change.js +2 -2
  18. package/src/lib/server/consent-group-change.js.map +1 -1
  19. package/src/lib/server/dynamic-list-materialize.js +3 -3
  20. package/src/lib/server/dynamic-list-materialize.js.map +1 -1
  21. package/src/lib/server/email-flow-gate.js +2 -2
  22. package/src/lib/server/email-flow-gate.js.map +1 -1
  23. package/src/lib/server/email-suppression.js +1 -1
  24. package/src/lib/server/email-suppression.js.map +1 -1
  25. package/src/lib/server/host-visitor-records.d.ts +32 -4
  26. package/src/lib/server/host-visitor-records.js +73 -14
  27. package/src/lib/server/host-visitor-records.js.map +1 -1
  28. package/src/lib/server/hosted-page-review.d.ts +34 -0
  29. package/src/lib/server/hosted-page-review.js +270 -22
  30. package/src/lib/server/hosted-page-review.js.map +1 -1
  31. package/src/lib/server/leaving-notice.d.ts +98 -0
  32. package/src/lib/server/leaving-notice.js +250 -0
  33. package/src/lib/server/leaving-notice.js.map +1 -0
  34. package/src/lib/server/link-reputation-review.d.ts +54 -0
  35. package/src/lib/server/link-reputation-review.js +144 -0
  36. package/src/lib/server/link-reputation-review.js.map +1 -0
  37. package/src/lib/server/media-cdn-rate-limit.d.ts +6 -3
  38. package/src/lib/server/media-cdn-rate-limit.js +6 -3
  39. package/src/lib/server/media-cdn-rate-limit.js.map +1 -1
  40. package/src/lib/server/member-email-aliases.d.ts +9 -1
  41. package/src/lib/server/member-email-aliases.js +25 -1
  42. package/src/lib/server/member-email-aliases.js.map +1 -1
  43. package/src/lib/server/notifications.d.ts +14 -0
  44. package/src/lib/server/notifications.js +29 -0
  45. package/src/lib/server/notifications.js.map +1 -1
  46. package/src/lib/server/operator-alerts.d.ts +7 -1
  47. package/src/lib/server/operator-alerts.js +5 -3
  48. package/src/lib/server/operator-alerts.js.map +1 -1
  49. package/src/lib/server/org-age.d.ts +15 -6
  50. package/src/lib/server/org-age.js +20 -8
  51. package/src/lib/server/org-age.js.map +1 -1
  52. package/src/lib/server/org-containers.d.ts +66 -0
  53. package/src/lib/server/org-containers.js +95 -0
  54. package/src/lib/server/org-containers.js.map +1 -0
  55. package/src/lib/server/org-member-notice.js +3 -2
  56. package/src/lib/server/org-member-notice.js.map +1 -1
  57. package/src/lib/server/outbound-send-review.js +23 -6
  58. package/src/lib/server/outbound-send-review.js.map +1 -1
  59. package/src/lib/server/page-security-hold.d.ts +93 -0
  60. package/src/lib/server/page-security-hold.js +180 -0
  61. package/src/lib/server/page-security-hold.js.map +1 -0
  62. package/src/lib/server/payment-fraud-signal.js +5 -1
  63. package/src/lib/server/payment-fraud-signal.js.map +1 -1
  64. package/src/lib/server/payment-provider-stripe-connect-payouts.js +35 -1
  65. package/src/lib/server/payment-provider-stripe-connect-payouts.js.map +1 -1
  66. package/src/lib/server/payment-risk-record.d.ts +1 -1
  67. package/src/lib/server/payment-risk-record.js +3 -1
  68. package/src/lib/server/payment-risk-record.js.map +1 -1
  69. package/src/lib/server/platform-marketing-consent.d.ts +1 -1
  70. package/src/lib/server/platform-marketing-consent.js +1 -1
  71. package/src/lib/server/platform-marketing-consent.js.map +1 -1
  72. package/src/lib/server/risk-notice.d.ts +32 -1
  73. package/src/lib/server/risk-notice.js +90 -28
  74. package/src/lib/server/risk-notice.js.map +1 -1
  75. package/src/lib/server/sending-domains.d.ts +17 -0
  76. package/src/lib/server/sending-domains.js +28 -4
  77. package/src/lib/server/sending-domains.js.map +1 -1
  78. package/src/lib/server/sso-enforcement.js +7 -2
  79. package/src/lib/server/sso-enforcement.js.map +1 -1
  80. package/src/lib/server/staff-alert-email.js +4 -2
  81. package/src/lib/server/staff-alert-email.js.map +1 -1
  82. package/src/lib/server/{email-topic-confirmation.js → topic-subscriptions.js} +15 -11
  83. package/src/lib/server/topic-subscriptions.js.map +1 -0
  84. package/src/lib/server/upsert-contact.js +2 -2
  85. package/src/lib/server/upsert-contact.js.map +1 -1
  86. package/src/lib/server/web-risk.d.ts +100 -0
  87. package/src/lib/server/web-risk.js +383 -0
  88. package/src/lib/server/web-risk.js.map +1 -0
  89. package/src/tenancy.d.ts +1 -3
  90. package/src/tenancy.js +1 -7
  91. package/src/tenancy.js.map +1 -1
  92. package/src/lib/server/contact-email-engagement.d.ts +0 -32
  93. package/src/lib/server/contact-email-engagement.js +0 -145
  94. package/src/lib/server/contact-email-engagement.js.map +0 -1
  95. package/src/lib/server/crm-booking-activity.d.ts +0 -86
  96. package/src/lib/server/crm-booking-activity.js +0 -257
  97. package/src/lib/server/crm-booking-activity.js.map +0 -1
  98. package/src/lib/server/crm-inbound-email.d.ts +0 -168
  99. package/src/lib/server/crm-inbound-email.js +0 -282
  100. package/src/lib/server/crm-inbound-email.js.map +0 -1
  101. package/src/lib/server/email-topic-confirmation.js.map +0 -1
  102. /package/src/lib/server/{email-topic-confirmation.d.ts → topic-subscriptions.d.ts} +0 -0
@@ -0,0 +1,383 @@
1
+ import { _ as _extends } from "@swc/helpers/_/_extends";
2
+ /**
3
+ * @license
4
+ * Copyright 2026 Aglyn LLC
5
+ *
6
+ * Licensed under the Apache License, Version 2.0 (the "License");
7
+ * you may not use this file except in compliance with the License.
8
+ * You may obtain a copy of the License at
9
+ *
10
+ * http://www.apache.org/licenses/LICENSE-2.0
11
+ *
12
+ * Unless required by applicable law or agreed to in writing, software
13
+ * distributed under the License is distributed on an "AS IS" BASIS,
14
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ * See the License for the specific language governing permissions and
16
+ * limitations under the License.
17
+ */ /*==========================================
18
+ * GOOGLE WEB RISK (AGL-3451): the lookup behind the link reputation screen.
19
+ *
20
+ * `@aglyn/shared-util-email/link-reputation` decides which hosts are foreign
21
+ * and turns a listing into a strong `web-risk-link` signal. This is the store
22
+ * half: the `uris.search` client, the cache, the kill switch.
23
+ *
24
+ * ## The client
25
+ *
26
+ * {@link WebRiskClient} is the contract: one URI in, the threat types it is
27
+ * listed for and how long that answer may be kept. The default
28
+ * implementation calls `webrisk.googleapis.com/v1/uris:search` for
29
+ * SOCIAL_ENGINEERING, MALWARE and UNWANTED_SOFTWARE as the platform's own
30
+ * service account — the firebase-admin credential every other server-side
31
+ * Google call here already uses (`client-error-report.ts`), so there is no
32
+ * key of its own. Only `https://<host>/` is ever sent: see the module header
33
+ * of `link-reputation.ts`.
34
+ *
35
+ * ## Not evidence when it fails
36
+ *
37
+ * A call is given {@link WEB_RISK_TIMEOUT_MS}, and a whole lookup
38
+ * {@link WEB_RISK_DEADLINE_MS}. A host whose answer did not arrive, or whose
39
+ * call failed, is UNKNOWN: it holds nothing and logs a warning. A credential
40
+ * the API refuses (the API is not enabled on a self-hosted project, say) backs
41
+ * the lookup off for a quarter of an hour, so a misconfiguration is one log
42
+ * line rather than one per page.
43
+ *
44
+ * ## The cache
45
+ *
46
+ * Per host, in two layers:
47
+ *
48
+ * - this process's memory — a listed host until the answer's `expireTime`,
49
+ * which Web Risk sets and which may not be exceeded; a clean one for
50
+ * {@link WEB_RISK_MEMORY_CLEAN_TTL_MS}, short so a listing the daily
51
+ * re-check writes reaches every warm render within minutes;
52
+ * - `webRiskVerdicts/{host}` — server-only, one document per host, a clean
53
+ * answer kept {@link WEB_RISK_CLEAN_TTL_MS}. Bounded by the number of
54
+ * distinct foreign hosts and rewritten in place, so it needs no TTL policy.
55
+ *
56
+ * Read in one `getAll` per lookup, so a page's twenty hosts cost one round
57
+ * trip, and nothing at all once they are in memory.
58
+ *
59
+ * ## The kill switch
60
+ *
61
+ * `platformSettings/webRisk` `{ enabled: false }` stops every lookup within a
62
+ * minute — every host reads unknown, so nothing holds on it — without a
63
+ * deploy. Absent or anything else means on.
64
+ *=========================================*/ import { TENANT_APEX } from "@aglyn/aglyn/app-utils/tenant-apex";
65
+ import { LINK_THREAT_TYPES, MAX_REPUTATION_HOSTS_PER_LOOKUP, normalizeReputationHost, setLinkReputationLookup } from "@aglyn/shared-util-email/link-reputation";
66
+ import { getApp } from "firebase-admin/app";
67
+ import firebaseAdmin from "./firebase-admin.js";
68
+ export const WEB_RISK_SEARCH_ENDPOINT = 'https://webrisk.googleapis.com/v1/uris:search';
69
+ export const WEB_RISK_CACHE_COLLECTION = 'webRiskVerdicts';
70
+ /** The kill switch: `{ enabled: false }` turns every lookup off. */ export const WEB_RISK_SETTINGS_COLLECTION = 'platformSettings';
71
+ export const WEB_RISK_SETTINGS_DOC = 'webRisk';
72
+ /** One call's budget. */ export const WEB_RISK_TIMEOUT_MS = 1000;
73
+ /** One lookup's budget, however many hosts it asks about. */ export const WEB_RISK_DEADLINE_MS = 1200;
74
+ /** How long a clean answer is kept in the store. */ export const WEB_RISK_CLEAN_TTL_MS = 12 * 60 * 60000;
75
+ /** How long a clean answer is trusted from this process's memory. */ export const WEB_RISK_MEMORY_CLEAN_TTL_MS = 10 * 60000;
76
+ /** A listing whose answer named no `expireTime` is kept this long. */ export const WEB_RISK_LISTED_FALLBACK_TTL_MS = 5 * 60000;
77
+ /** How long the switch's answer is trusted. */ const SWITCH_TTL_MS = 60000;
78
+ /** How long a refused credential stops the lookup. */ const REFUSED_BACKOFF_MS = 15 * 60000;
79
+ /** How long a rate limit stops the lookup. */ const THROTTLED_BACKOFF_MS = 60000;
80
+ /** At most one warning per this long, so an outage is not a log flood. */ const WARN_EVERY_MS = 60000;
81
+ /** The API answered with a status that is not an answer. */ export class WebRiskHttpError extends Error {
82
+ constructor(status, detail){
83
+ super(`Web Risk answered ${status}: ${detail.slice(0, 200)}`), this.status = status;
84
+ this.name = 'WebRiskHttpError';
85
+ }
86
+ }
87
+ /** There is no credential to call with. */ export class WebRiskUnavailableError extends Error {
88
+ constructor(reason){
89
+ super(reason);
90
+ this.name = 'WebRiskUnavailableError';
91
+ }
92
+ }
93
+ /** `uris.search`'s body, read defensively: an empty object is "not listed". */ export function parseWebRiskSearch(body) {
94
+ const threat = body == null ? void 0 : body.threat;
95
+ if (!threat || typeof threat !== 'object') return {
96
+ threats: [],
97
+ expireTimeMs: null
98
+ };
99
+ const listed = Array.isArray(threat.threatTypes) ? threat.threatTypes : [];
100
+ const threats = LINK_THREAT_TYPES.filter((type)=>listed.includes(type));
101
+ const expireTimeMs = typeof threat.expireTime === 'string' ? Date.parse(threat.expireTime) : Number.NaN;
102
+ return {
103
+ threats,
104
+ expireTimeMs: Number.isFinite(expireTimeMs) ? expireTimeMs : null
105
+ };
106
+ }
107
+ /** The URI a host is looked up as. Only the host: never a path or a query. */ export function webRiskUriForHost(host) {
108
+ return `https://${host}/`;
109
+ }
110
+ /** The default client: `uris.search` over HTTPS with a bearer token. */ export function createWebRiskHttpClient(options) {
111
+ var _options_fetch, _options_timeoutMs;
112
+ const request = (_options_fetch = options.fetch) != null ? _options_fetch : fetch;
113
+ const timeoutMs = (_options_timeoutMs = options.timeoutMs) != null ? _options_timeoutMs : WEB_RISK_TIMEOUT_MS;
114
+ return {
115
+ async searchUri (uri) {
116
+ var _options_endpoint;
117
+ const token = await options.accessToken();
118
+ if (!token) throw new WebRiskUnavailableError('no service-account credential');
119
+ const url = new URL((_options_endpoint = options.endpoint) != null ? _options_endpoint : WEB_RISK_SEARCH_ENDPOINT);
120
+ for (const type of LINK_THREAT_TYPES)url.searchParams.append('threatTypes', type);
121
+ url.searchParams.set('uri', uri);
122
+ const controller = new AbortController();
123
+ const timer = setTimeout(()=>controller.abort(), timeoutMs);
124
+ try {
125
+ const response = await request(url.toString(), {
126
+ method: 'GET',
127
+ headers: {
128
+ Authorization: `Bearer ${token}`,
129
+ Accept: 'application/json'
130
+ },
131
+ signal: controller.signal
132
+ });
133
+ if (!response.ok) {
134
+ throw new WebRiskHttpError(response.status, await response.text().catch(()=>''));
135
+ }
136
+ return parseWebRiskSearch(await response.json());
137
+ } finally{
138
+ clearTimeout(timer);
139
+ }
140
+ }
141
+ };
142
+ }
143
+ /**
144
+ * The platform service account's access token, or null when this deployment
145
+ * has none — the same credential `client-error-report.ts` writes logs with.
146
+ */ async function serviceAccountAccessToken() {
147
+ try {
148
+ var _ref;
149
+ var _this;
150
+ const credential = getApp().options.credential;
151
+ if (!credential) return null;
152
+ return (_ref = (_this = await credential.getAccessToken()) == null ? void 0 : _this.access_token) != null ? _ref : null;
153
+ } catch (unused) {
154
+ return null;
155
+ }
156
+ }
157
+ let defaultClient = null;
158
+ let clientOverride;
159
+ function activeClient() {
160
+ if (clientOverride !== undefined) return clientOverride;
161
+ defaultClient != null ? defaultClient : defaultClient = createWebRiskHttpClient({
162
+ accessToken: serviceAccountAccessToken
163
+ });
164
+ return defaultClient;
165
+ }
166
+ const memory = new Map();
167
+ let switchMemo = null;
168
+ let backoffUntilMs = 0;
169
+ let lastWarnAtMs = 0;
170
+ /** Test seam: forget the caches, the switch, the back-off; optionally stand in a client. */ export function resetWebRiskForTests(client) {
171
+ memory.clear();
172
+ switchMemo = null;
173
+ backoffUntilMs = 0;
174
+ lastWarnAtMs = 0;
175
+ clientOverride = client;
176
+ defaultClient = null;
177
+ }
178
+ function warn(nowMs, message, error) {
179
+ if (nowMs - lastWarnAtMs < WARN_EVERY_MS) return;
180
+ lastWarnAtMs = nowMs;
181
+ console.warn(`[web-risk] ${message} — the host reads unknown, which is not evidence`, error != null ? error : '');
182
+ }
183
+ /** Is the lookup switched off? Read at most once a minute; an unreadable switch is on. */ async function switchedOff(nowMs) {
184
+ if (switchMemo && nowMs - switchMemo.atMs < SWITCH_TTL_MS) return switchMemo.off;
185
+ const off = await firebaseAdmin.app().firestore().collection(WEB_RISK_SETTINGS_COLLECTION).doc(WEB_RISK_SETTINGS_DOC).get().then((snapshot)=>snapshot.get('enabled') === false, ()=>false);
186
+ switchMemo = {
187
+ off,
188
+ atMs: nowMs
189
+ };
190
+ return off;
191
+ }
192
+ /**
193
+ * Hosts that are the platform's own: every site's subdomain on the tenant
194
+ * apex. Never looked up — a neighbour's site is screened on its own page.
195
+ */ export function platformReputationExclusions() {
196
+ return [
197
+ TENANT_APEX
198
+ ].filter(Boolean);
199
+ }
200
+ function isPlatformHost(host) {
201
+ return platformReputationExclusions().some((domain)=>host === domain || host.endsWith(`.${domain}`));
202
+ }
203
+ function verdictFrom(data) {
204
+ if (!data) return null;
205
+ const threats = Array.isArray(data['threats']) ? LINK_THREAT_TYPES.filter((type)=>data['threats'].includes(type)) : [];
206
+ const checkedAtMs = Number(data['checkedAtMs']);
207
+ const expiresAtMs = Number(data['expiresAtMs']);
208
+ if (!Number.isFinite(checkedAtMs) || !Number.isFinite(expiresAtMs)) return null;
209
+ return {
210
+ threats,
211
+ checkedAtMs,
212
+ expiresAtMs
213
+ };
214
+ }
215
+ function remember(host, verdict, nowMs) {
216
+ memory.set(host, _extends({}, verdict, {
217
+ memoryUntilMs: verdict.threats.length ? verdict.expiresAtMs : Math.min(verdict.expiresAtMs, nowMs + WEB_RISK_MEMORY_CLEAN_TTL_MS)
218
+ }));
219
+ }
220
+ /**
221
+ * The reputation of each host: listed, clean, or unknown. Platform hosts are
222
+ * clean without asking. Never throws, and never waits longer than the
223
+ * deadline: a call still running then finishes in the background and fills
224
+ * the caches for the next asker.
225
+ *
226
+ * "Never throws" covers the store as well as the API: a missing app, a
227
+ * settings read that fails before it is a promise, or a cache reference
228
+ * that cannot be built all read every host as unknown. The page review
229
+ * calls this before its own fail-closed `try`, so a throw here would take
230
+ * down every page with a link off the site.
231
+ */ export async function lookupHostReputation(hosts, options = {}) {
232
+ try {
233
+ return await lookupHostReputationOrThrow(hosts, options);
234
+ } catch (error) {
235
+ var _options_nowMs;
236
+ warn((_options_nowMs = options.nowMs) != null ? _options_nowMs : Date.now(), 'a lookup failed before it could answer', error);
237
+ const unknown = [
238
+ ...new Set(hosts.map((raw)=>normalizeReputationHost(raw)).filter((host)=>Boolean(host)))
239
+ ];
240
+ return {
241
+ hits: [],
242
+ clean: [],
243
+ unknown,
244
+ looked: 0
245
+ };
246
+ }
247
+ }
248
+ async function lookupHostReputationOrThrow(hosts, options) {
249
+ var _options_nowMs;
250
+ const nowMs = (_options_nowMs = options.nowMs) != null ? _options_nowMs : Date.now();
251
+ const result = {
252
+ hits: [],
253
+ clean: [],
254
+ unknown: [],
255
+ looked: 0
256
+ };
257
+ const wanted = [];
258
+ for (const raw of hosts){
259
+ var _options_maxHosts;
260
+ const host = normalizeReputationHost(raw);
261
+ if (!host || wanted.includes(host) || result.clean.includes(host)) continue;
262
+ if (isPlatformHost(host)) result.clean.push(host);
263
+ else if (wanted.length < ((_options_maxHosts = options.maxHosts) != null ? _options_maxHosts : MAX_REPUTATION_HOSTS_PER_LOOKUP)) wanted.push(host);
264
+ else result.unknown.push(host);
265
+ }
266
+ if (!wanted.length) return result;
267
+ const settle = (host, verdict)=>{
268
+ if (verdict.threats.length) result.hits.push({
269
+ host,
270
+ threats: verdict.threats
271
+ });
272
+ else result.clean.push(host);
273
+ };
274
+ // 1. This process's memory.
275
+ let missing = wanted.filter((host)=>{
276
+ const known = memory.get(host);
277
+ if (known && nowMs < known.memoryUntilMs) {
278
+ settle(host, known);
279
+ return false;
280
+ }
281
+ return true;
282
+ });
283
+ if (!missing.length) return result;
284
+ // 2. The switch. Off reads every remaining host as unknown.
285
+ if (await switchedOff(nowMs)) {
286
+ result.unknown.push(...missing);
287
+ return result;
288
+ }
289
+ // 3. The store, in one round trip.
290
+ const firestore = firebaseAdmin.app().firestore();
291
+ const collection = firestore.collection(WEB_RISK_CACHE_COLLECTION);
292
+ try {
293
+ const snapshots = await firestore.getAll(...missing.map((host)=>collection.doc(host)));
294
+ const stored = new Map();
295
+ for (const snapshot of snapshots){
296
+ const verdict = verdictFrom(snapshot.exists ? snapshot.data() : undefined);
297
+ if (verdict && nowMs < verdict.expiresAtMs) stored.set(snapshot.id, verdict);
298
+ }
299
+ missing = missing.filter((host)=>{
300
+ const verdict = stored.get(host);
301
+ if (!verdict) return true;
302
+ remember(host, verdict, nowMs);
303
+ settle(host, verdict);
304
+ return false;
305
+ });
306
+ } catch (error) {
307
+ warn(nowMs, 'the verdict cache could not be read', error);
308
+ }
309
+ if (!missing.length) return result;
310
+ // 4. The API, every remaining host at once, within the deadline.
311
+ const client = activeClient();
312
+ if (!client || nowMs < backoffUntilMs) {
313
+ result.unknown.push(...missing);
314
+ return result;
315
+ }
316
+ result.looked = missing.length;
317
+ const answers = new Map();
318
+ const calls = missing.map(async (host)=>{
319
+ try {
320
+ var _answer_expireTimeMs;
321
+ const answer = await client.searchUri(webRiskUriForHost(host));
322
+ const checkedAtMs = Date.now();
323
+ const verdict = {
324
+ threats: answer.threats,
325
+ checkedAtMs,
326
+ expiresAtMs: answer.threats.length ? (_answer_expireTimeMs = answer.expireTimeMs) != null ? _answer_expireTimeMs : checkedAtMs + WEB_RISK_LISTED_FALLBACK_TTL_MS : checkedAtMs + WEB_RISK_CLEAN_TTL_MS
327
+ };
328
+ answers.set(host, verdict);
329
+ remember(host, verdict, checkedAtMs);
330
+ await collection.doc(host).set(_extends({
331
+ host
332
+ }, verdict, {
333
+ listed: verdict.threats.length > 0
334
+ })).catch((error)=>warn(checkedAtMs, 'a verdict could not be cached', error));
335
+ } catch (error) {
336
+ if (error instanceof WebRiskHttpError && [
337
+ 401,
338
+ 403,
339
+ 404
340
+ ].includes(error.status)) {
341
+ backoffUntilMs = Date.now() + REFUSED_BACKOFF_MS;
342
+ console.error('[web-risk] the API refused the credential — lookups paused for 15 minutes', error);
343
+ } else if (error instanceof WebRiskHttpError && error.status === 429) {
344
+ backoffUntilMs = Date.now() + THROTTLED_BACKOFF_MS;
345
+ warn(Date.now(), 'the API is rate limiting', error);
346
+ } else if (error instanceof WebRiskUnavailableError) {
347
+ backoffUntilMs = Date.now() + REFUSED_BACKOFF_MS;
348
+ warn(Date.now(), 'no credential to call the API with', error);
349
+ } else {
350
+ warn(Date.now(), `the lookup of ${host} failed or timed out`, error);
351
+ }
352
+ }
353
+ });
354
+ let timer;
355
+ await Promise.race([
356
+ Promise.allSettled(calls),
357
+ new Promise((resolve)=>{
358
+ var _options_deadlineMs;
359
+ timer = setTimeout(resolve, (_options_deadlineMs = options.deadlineMs) != null ? _options_deadlineMs : WEB_RISK_DEADLINE_MS);
360
+ })
361
+ ]);
362
+ if (timer) clearTimeout(timer);
363
+ const late = missing.filter((host)=>!answers.has(host));
364
+ for (const host of missing){
365
+ const verdict = answers.get(host);
366
+ if (verdict) settle(host, verdict);
367
+ }
368
+ result.unknown.push(...late);
369
+ if (late.length) warn(Date.now(), `${late.length} host(s) had no answer within the deadline`);
370
+ return result;
371
+ }
372
+ /**
373
+ * Puts this lookup behind the shared screen's reputation seam, so the send
374
+ * seam (`screenTenantMessage`) asks it. Called by
375
+ * `installOutboundScreenGate`, at that module's load.
376
+ */ export function installLinkReputationLookup() {
377
+ if (typeof setLinkReputationLookup !== 'function') return;
378
+ setLinkReputationLookup((hosts, options)=>lookupHostReputation(hosts, {
379
+ deadlineMs: options == null ? void 0 : options.deadlineMs
380
+ }));
381
+ }
382
+
383
+ //# sourceMappingURL=web-risk.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../../../libs/tenant/data/admin/src/lib/server/web-risk.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/*==========================================\n * GOOGLE WEB RISK (AGL-3451): the lookup behind the link reputation screen.\n *\n * `@aglyn/shared-util-email/link-reputation` decides which hosts are foreign\n * and turns a listing into a strong `web-risk-link` signal. This is the store\n * half: the `uris.search` client, the cache, the kill switch.\n *\n * ## The client\n *\n * {@link WebRiskClient} is the contract: one URI in, the threat types it is\n * listed for and how long that answer may be kept. The default\n * implementation calls `webrisk.googleapis.com/v1/uris:search` for\n * SOCIAL_ENGINEERING, MALWARE and UNWANTED_SOFTWARE as the platform's own\n * service account — the firebase-admin credential every other server-side\n * Google call here already uses (`client-error-report.ts`), so there is no\n * key of its own. Only `https://<host>/` is ever sent: see the module header\n * of `link-reputation.ts`.\n *\n * ## Not evidence when it fails\n *\n * A call is given {@link WEB_RISK_TIMEOUT_MS}, and a whole lookup\n * {@link WEB_RISK_DEADLINE_MS}. A host whose answer did not arrive, or whose\n * call failed, is UNKNOWN: it holds nothing and logs a warning. A credential\n * the API refuses (the API is not enabled on a self-hosted project, say) backs\n * the lookup off for a quarter of an hour, so a misconfiguration is one log\n * line rather than one per page.\n *\n * ## The cache\n *\n * Per host, in two layers:\n *\n * - this process's memory — a listed host until the answer's `expireTime`,\n * which Web Risk sets and which may not be exceeded; a clean one for\n * {@link WEB_RISK_MEMORY_CLEAN_TTL_MS}, short so a listing the daily\n * re-check writes reaches every warm render within minutes;\n * - `webRiskVerdicts/{host}` — server-only, one document per host, a clean\n * answer kept {@link WEB_RISK_CLEAN_TTL_MS}. Bounded by the number of\n * distinct foreign hosts and rewritten in place, so it needs no TTL policy.\n *\n * Read in one `getAll` per lookup, so a page's twenty hosts cost one round\n * trip, and nothing at all once they are in memory.\n *\n * ## The kill switch\n *\n * `platformSettings/webRisk` `{ enabled: false }` stops every lookup within a\n * minute — every host reads unknown, so nothing holds on it — without a\n * deploy. Absent or anything else means on.\n *=========================================*/\n\nimport { TENANT_APEX } from '@aglyn/aglyn/app-utils/tenant-apex'\nimport {\n LINK_THREAT_TYPES,\n type LinkReputationAnswer,\n type LinkThreatType,\n MAX_REPUTATION_HOSTS_PER_LOOKUP,\n normalizeReputationHost,\n setLinkReputationLookup,\n} from '@aglyn/shared-util-email/link-reputation'\nimport { getApp } from 'firebase-admin/app'\nimport firebaseAdmin from './firebase-admin'\n\nexport const WEB_RISK_SEARCH_ENDPOINT = 'https://webrisk.googleapis.com/v1/uris:search'\nexport const WEB_RISK_CACHE_COLLECTION = 'webRiskVerdicts'\n/** The kill switch: `{ enabled: false }` turns every lookup off. */\nexport const WEB_RISK_SETTINGS_COLLECTION = 'platformSettings'\nexport const WEB_RISK_SETTINGS_DOC = 'webRisk'\n\n/** One call's budget. */\nexport const WEB_RISK_TIMEOUT_MS = 1_000\n/** One lookup's budget, however many hosts it asks about. */\nexport const WEB_RISK_DEADLINE_MS = 1_200\n/** How long a clean answer is kept in the store. */\nexport const WEB_RISK_CLEAN_TTL_MS = 12 * 60 * 60_000\n/** How long a clean answer is trusted from this process's memory. */\nexport const WEB_RISK_MEMORY_CLEAN_TTL_MS = 10 * 60_000\n/** A listing whose answer named no `expireTime` is kept this long. */\nexport const WEB_RISK_LISTED_FALLBACK_TTL_MS = 5 * 60_000\n/** How long the switch's answer is trusted. */\nconst SWITCH_TTL_MS = 60_000\n/** How long a refused credential stops the lookup. */\nconst REFUSED_BACKOFF_MS = 15 * 60_000\n/** How long a rate limit stops the lookup. */\nconst THROTTLED_BACKOFF_MS = 60_000\n/** At most one warning per this long, so an outage is not a log flood. */\nconst WARN_EVERY_MS = 60_000\n\n/** What `uris.search` answered for one URI. */\nexport interface WebRiskSearchResult {\n /** The lists it is on; empty when it is on none. */\n threats: LinkThreatType[]\n /** Until when the answer may be kept, when the API said. */\n expireTimeMs: number | null\n}\n\n/** The contract: one URI, its listing. Throws on anything but an answer. */\nexport interface WebRiskClient {\n searchUri(uri: string): Promise<WebRiskSearchResult>\n}\n\n/** The API answered with a status that is not an answer. */\nexport class WebRiskHttpError extends Error {\n constructor(\n readonly status: number,\n detail: string,\n ) {\n super(`Web Risk answered ${status}: ${detail.slice(0, 200)}`)\n this.name = 'WebRiskHttpError'\n }\n}\n\n/** There is no credential to call with. */\nexport class WebRiskUnavailableError extends Error {\n constructor(reason: string) {\n super(reason)\n this.name = 'WebRiskUnavailableError'\n }\n}\n\n/** `uris.search`'s body, read defensively: an empty object is \"not listed\". */\nexport function parseWebRiskSearch(body: unknown): WebRiskSearchResult {\n const threat = (body as { threat?: { threatTypes?: unknown; expireTime?: unknown } } | null)\n ?.threat\n if (!threat || typeof threat !== 'object') return { threats: [], expireTimeMs: null }\n const listed = Array.isArray(threat.threatTypes) ? threat.threatTypes : []\n const threats = LINK_THREAT_TYPES.filter((type) => listed.includes(type))\n const expireTimeMs =\n typeof threat.expireTime === 'string' ? Date.parse(threat.expireTime) : Number.NaN\n return { threats, expireTimeMs: Number.isFinite(expireTimeMs) ? expireTimeMs : null }\n}\n\n/** The URI a host is looked up as. Only the host: never a path or a query. */\nexport function webRiskUriForHost(host: string): string {\n return `https://${host}/`\n}\n\n/** The default client: `uris.search` over HTTPS with a bearer token. */\nexport function createWebRiskHttpClient(options: {\n accessToken: () => Promise<string | null>\n fetch?: typeof fetch\n timeoutMs?: number\n endpoint?: string\n}): WebRiskClient {\n const request = options.fetch ?? fetch\n const timeoutMs = options.timeoutMs ?? WEB_RISK_TIMEOUT_MS\n return {\n async searchUri(uri: string): Promise<WebRiskSearchResult> {\n const token = await options.accessToken()\n if (!token) throw new WebRiskUnavailableError('no service-account credential')\n const url = new URL(options.endpoint ?? WEB_RISK_SEARCH_ENDPOINT)\n for (const type of LINK_THREAT_TYPES) url.searchParams.append('threatTypes', type)\n url.searchParams.set('uri', uri)\n const controller = new AbortController()\n const timer = setTimeout(() => controller.abort(), timeoutMs)\n try {\n const response = await request(url.toString(), {\n method: 'GET',\n headers: { Authorization: `Bearer ${token}`, Accept: 'application/json' },\n signal: controller.signal,\n })\n if (!response.ok) {\n throw new WebRiskHttpError(response.status, await response.text().catch(() => ''))\n }\n return parseWebRiskSearch(await response.json())\n } finally {\n clearTimeout(timer)\n }\n },\n }\n}\n\n/**\n * The platform service account's access token, or null when this deployment\n * has none — the same credential `client-error-report.ts` writes logs with.\n */\nasync function serviceAccountAccessToken(): Promise<string | null> {\n try {\n const credential = getApp().options.credential\n if (!credential) return null\n return (await credential.getAccessToken())?.access_token ?? null\n } catch {\n return null\n }\n}\n\nlet defaultClient: WebRiskClient | null = null\nlet clientOverride: WebRiskClient | null | undefined\n\nfunction activeClient(): WebRiskClient | null {\n if (clientOverride !== undefined) return clientOverride\n defaultClient ??= createWebRiskHttpClient({ accessToken: serviceAccountAccessToken })\n return defaultClient\n}\n\ninterface Verdict {\n threats: LinkThreatType[]\n checkedAtMs: number\n /** When the answer stops being good, in the store. */\n expiresAtMs: number\n}\n\nconst memory = new Map<string, Verdict & { memoryUntilMs: number }>()\nlet switchMemo: { off: boolean; atMs: number } | null = null\nlet backoffUntilMs = 0\nlet lastWarnAtMs = 0\n\n/** Test seam: forget the caches, the switch, the back-off; optionally stand in a client. */\nexport function resetWebRiskForTests(client?: WebRiskClient | null): void {\n memory.clear()\n switchMemo = null\n backoffUntilMs = 0\n lastWarnAtMs = 0\n clientOverride = client\n defaultClient = null\n}\n\nfunction warn(nowMs: number, message: string, error?: unknown): void {\n if (nowMs - lastWarnAtMs < WARN_EVERY_MS) return\n lastWarnAtMs = nowMs\n console.warn(`[web-risk] ${message} — the host reads unknown, which is not evidence`, error ?? '')\n}\n\n/** Is the lookup switched off? Read at most once a minute; an unreadable switch is on. */\nasync function switchedOff(nowMs: number): Promise<boolean> {\n if (switchMemo && nowMs - switchMemo.atMs < SWITCH_TTL_MS) return switchMemo.off\n const off = await firebaseAdmin\n .app()\n .firestore()\n .collection(WEB_RISK_SETTINGS_COLLECTION)\n .doc(WEB_RISK_SETTINGS_DOC)\n .get()\n .then(\n (snapshot) => snapshot.get('enabled') === false,\n () => false,\n )\n switchMemo = { off, atMs: nowMs }\n return off\n}\n\n/**\n * Hosts that are the platform's own: every site's subdomain on the tenant\n * apex. Never looked up — a neighbour's site is screened on its own page.\n */\nexport function platformReputationExclusions(): string[] {\n return [TENANT_APEX].filter(Boolean)\n}\n\nfunction isPlatformHost(host: string): boolean {\n return platformReputationExclusions().some(\n (domain) => host === domain || host.endsWith(`.${domain}`),\n )\n}\n\nfunction verdictFrom(data: Record<string, unknown> | undefined): Verdict | null {\n if (!data) return null\n const threats = Array.isArray(data['threats'])\n ? LINK_THREAT_TYPES.filter((type) => (data['threats'] as unknown[]).includes(type))\n : []\n const checkedAtMs = Number(data['checkedAtMs'])\n const expiresAtMs = Number(data['expiresAtMs'])\n if (!Number.isFinite(checkedAtMs) || !Number.isFinite(expiresAtMs)) return null\n return { threats, checkedAtMs, expiresAtMs }\n}\n\nfunction remember(host: string, verdict: Verdict, nowMs: number): void {\n memory.set(host, {\n ...verdict,\n memoryUntilMs: verdict.threats.length\n ? verdict.expiresAtMs\n : Math.min(verdict.expiresAtMs, nowMs + WEB_RISK_MEMORY_CLEAN_TTL_MS),\n })\n}\n\nexport interface HostReputationLookupOptions {\n /** How long the caller will wait, overall. */\n deadlineMs?: number\n /** The most hosts asked about; the rest read unknown. */\n maxHosts?: number\n nowMs?: number\n}\n\n/** What a lookup answered, and how many hosts it had to ask the API about. */\nexport interface HostReputationLookupResult extends LinkReputationAnswer {\n looked: number\n}\n\n/**\n * The reputation of each host: listed, clean, or unknown. Platform hosts are\n * clean without asking. Never throws, and never waits longer than the\n * deadline: a call still running then finishes in the background and fills\n * the caches for the next asker.\n *\n * \"Never throws\" covers the store as well as the API: a missing app, a\n * settings read that fails before it is a promise, or a cache reference\n * that cannot be built all read every host as unknown. The page review\n * calls this before its own fail-closed `try`, so a throw here would take\n * down every page with a link off the site.\n */\nexport async function lookupHostReputation(\n hosts: readonly string[],\n options: HostReputationLookupOptions = {},\n): Promise<HostReputationLookupResult> {\n try {\n return await lookupHostReputationOrThrow(hosts, options)\n } catch (error) {\n warn(options.nowMs ?? Date.now(), 'a lookup failed before it could answer', error)\n const unknown = [\n ...new Set(\n hosts\n .map((raw) => normalizeReputationHost(raw))\n .filter((host): host is string => Boolean(host)),\n ),\n ]\n return { hits: [], clean: [], unknown, looked: 0 }\n }\n}\n\nasync function lookupHostReputationOrThrow(\n hosts: readonly string[],\n options: HostReputationLookupOptions,\n): Promise<HostReputationLookupResult> {\n const nowMs = options.nowMs ?? Date.now()\n const result: HostReputationLookupResult = { hits: [], clean: [], unknown: [], looked: 0 }\n const wanted: string[] = []\n for (const raw of hosts) {\n const host = normalizeReputationHost(raw)\n if (!host || wanted.includes(host) || result.clean.includes(host)) continue\n if (isPlatformHost(host)) result.clean.push(host)\n else if (wanted.length < (options.maxHosts ?? MAX_REPUTATION_HOSTS_PER_LOOKUP)) wanted.push(host)\n else result.unknown.push(host)\n }\n if (!wanted.length) return result\n\n const settle = (host: string, verdict: Verdict) => {\n if (verdict.threats.length) result.hits.push({ host, threats: verdict.threats })\n else result.clean.push(host)\n }\n\n // 1. This process's memory.\n let missing = wanted.filter((host) => {\n const known = memory.get(host)\n if (known && nowMs < known.memoryUntilMs) {\n settle(host, known)\n return false\n }\n return true\n })\n if (!missing.length) return result\n\n // 2. The switch. Off reads every remaining host as unknown.\n if (await switchedOff(nowMs)) {\n result.unknown.push(...missing)\n return result\n }\n\n // 3. The store, in one round trip.\n const firestore = firebaseAdmin.app().firestore()\n const collection = firestore.collection(WEB_RISK_CACHE_COLLECTION)\n try {\n const snapshots = await firestore.getAll(...missing.map((host) => collection.doc(host)))\n const stored = new Map<string, Verdict>()\n for (const snapshot of snapshots) {\n const verdict = verdictFrom(snapshot.exists ? (snapshot.data() as Record<string, unknown>) : undefined)\n if (verdict && nowMs < verdict.expiresAtMs) stored.set(snapshot.id, verdict)\n }\n missing = missing.filter((host) => {\n const verdict = stored.get(host)\n if (!verdict) return true\n remember(host, verdict, nowMs)\n settle(host, verdict)\n return false\n })\n } catch (error) {\n warn(nowMs, 'the verdict cache could not be read', error)\n }\n if (!missing.length) return result\n\n // 4. The API, every remaining host at once, within the deadline.\n const client = activeClient()\n if (!client || nowMs < backoffUntilMs) {\n result.unknown.push(...missing)\n return result\n }\n result.looked = missing.length\n const answers = new Map<string, Verdict>()\n const calls = missing.map(async (host) => {\n try {\n const answer = await client.searchUri(webRiskUriForHost(host))\n const checkedAtMs = Date.now()\n const verdict: Verdict = {\n threats: answer.threats,\n checkedAtMs,\n expiresAtMs: answer.threats.length\n ? (answer.expireTimeMs ?? checkedAtMs + WEB_RISK_LISTED_FALLBACK_TTL_MS)\n : checkedAtMs + WEB_RISK_CLEAN_TTL_MS,\n }\n answers.set(host, verdict)\n remember(host, verdict, checkedAtMs)\n await collection\n .doc(host)\n .set({ host, ...verdict, listed: verdict.threats.length > 0 })\n .catch((error: unknown) => warn(checkedAtMs, 'a verdict could not be cached', error))\n } catch (error) {\n if (error instanceof WebRiskHttpError && [401, 403, 404].includes(error.status)) {\n backoffUntilMs = Date.now() + REFUSED_BACKOFF_MS\n console.error('[web-risk] the API refused the credential — lookups paused for 15 minutes', error)\n } else if (error instanceof WebRiskHttpError && error.status === 429) {\n backoffUntilMs = Date.now() + THROTTLED_BACKOFF_MS\n warn(Date.now(), 'the API is rate limiting', error)\n } else if (error instanceof WebRiskUnavailableError) {\n backoffUntilMs = Date.now() + REFUSED_BACKOFF_MS\n warn(Date.now(), 'no credential to call the API with', error)\n } else {\n warn(Date.now(), `the lookup of ${host} failed or timed out`, error)\n }\n }\n })\n let timer: ReturnType<typeof setTimeout> | undefined\n await Promise.race([\n Promise.allSettled(calls),\n new Promise<void>((resolve) => {\n timer = setTimeout(resolve, options.deadlineMs ?? WEB_RISK_DEADLINE_MS)\n }),\n ])\n if (timer) clearTimeout(timer)\n const late = missing.filter((host) => !answers.has(host))\n for (const host of missing) {\n const verdict = answers.get(host)\n if (verdict) settle(host, verdict)\n }\n result.unknown.push(...late)\n if (late.length) warn(Date.now(), `${late.length} host(s) had no answer within the deadline`)\n return result\n}\n\n/**\n * Puts this lookup behind the shared screen's reputation seam, so the send\n * seam (`screenTenantMessage`) asks it. Called by\n * `installOutboundScreenGate`, at that module's load.\n */\nexport function installLinkReputationLookup(): void {\n if (typeof setLinkReputationLookup !== 'function') return\n setLinkReputationLookup((hosts, options) =>\n lookupHostReputation(hosts, { deadlineMs: options?.deadlineMs }),\n )\n}\n"],"names":["TENANT_APEX","LINK_THREAT_TYPES","MAX_REPUTATION_HOSTS_PER_LOOKUP","normalizeReputationHost","setLinkReputationLookup","getApp","firebaseAdmin","WEB_RISK_SEARCH_ENDPOINT","WEB_RISK_CACHE_COLLECTION","WEB_RISK_SETTINGS_COLLECTION","WEB_RISK_SETTINGS_DOC","WEB_RISK_TIMEOUT_MS","WEB_RISK_DEADLINE_MS","WEB_RISK_CLEAN_TTL_MS","WEB_RISK_MEMORY_CLEAN_TTL_MS","WEB_RISK_LISTED_FALLBACK_TTL_MS","SWITCH_TTL_MS","REFUSED_BACKOFF_MS","THROTTLED_BACKOFF_MS","WARN_EVERY_MS","WebRiskHttpError","Error","status","detail","slice","name","WebRiskUnavailableError","reason","parseWebRiskSearch","body","threat","threats","expireTimeMs","listed","Array","isArray","threatTypes","filter","type","includes","expireTime","Date","parse","Number","NaN","isFinite","webRiskUriForHost","host","createWebRiskHttpClient","options","request","fetch","timeoutMs","searchUri","uri","token","accessToken","url","URL","endpoint","searchParams","append","set","controller","AbortController","timer","setTimeout","abort","response","toString","method","headers","Authorization","Accept","signal","ok","text","catch","json","clearTimeout","serviceAccountAccessToken","credential","getAccessToken","access_token","defaultClient","clientOverride","activeClient","undefined","memory","Map","switchMemo","backoffUntilMs","lastWarnAtMs","resetWebRiskForTests","client","clear","warn","nowMs","message","error","console","switchedOff","atMs","off","app","firestore","collection","doc","get","then","snapshot","platformReputationExclusions","Boolean","isPlatformHost","some","domain","endsWith","verdictFrom","data","checkedAtMs","expiresAtMs","remember","verdict","memoryUntilMs","length","Math","min","lookupHostReputation","hosts","lookupHostReputationOrThrow","now","unknown","Set","map","raw","hits","clean","looked","result","wanted","push","maxHosts","settle","missing","known","snapshots","getAll","stored","exists","id","answers","calls","answer","Promise","race","allSettled","resolve","deadlineMs","late","has","installLinkReputationLookup"],"mappings":";AAAA;;;;;;;;;;;;;;;CAeC,GAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;2CA+C2C,GAE3C,SAASA,WAAW,QAAQ,qCAAoC;AAChE,SACEC,iBAAiB,EAGjBC,+BAA+B,EAC/BC,uBAAuB,EACvBC,uBAAuB,QAClB,2CAA0C;AACjD,SAASC,MAAM,QAAQ,qBAAoB;AAC3C,OAAOC,mBAAmB,sBAAkB;AAE5C,OAAO,MAAMC,2BAA2B,gDAA+C;AACvF,OAAO,MAAMC,4BAA4B,kBAAiB;AAC1D,kEAAkE,GAClE,OAAO,MAAMC,+BAA+B,mBAAkB;AAC9D,OAAO,MAAMC,wBAAwB,UAAS;AAE9C,uBAAuB,GACvB,OAAO,MAAMC,sBAAsB,KAAK;AACxC,2DAA2D,GAC3D,OAAO,MAAMC,uBAAuB,KAAK;AACzC,kDAAkD,GAClD,OAAO,MAAMC,wBAAwB,KAAK,KAAK,MAAM;AACrD,mEAAmE,GACnE,OAAO,MAAMC,+BAA+B,KAAK,MAAM;AACvD,oEAAoE,GACpE,OAAO,MAAMC,kCAAkC,IAAI,MAAM;AACzD,6CAA6C,GAC7C,MAAMC,gBAAgB;AACtB,oDAAoD,GACpD,MAAMC,qBAAqB,KAAK;AAChC,4CAA4C,GAC5C,MAAMC,uBAAuB;AAC7B,wEAAwE,GACxE,MAAMC,gBAAgB;AAetB,0DAA0D,GAC1D,OAAO,MAAMC,yBAAyBC;IACpC,YACE,AAASC,MAAc,EACvBC,MAAc,CACd;QACA,KAAK,CAAC,CAAC,kBAAkB,EAAED,OAAO,EAAE,EAAEC,OAAOC,KAAK,CAAC,GAAG,MAAM,QAHnDF,SAAAA;QAIT,IAAI,CAACG,IAAI,GAAG;IACd;AACF;AAEA,yCAAyC,GACzC,OAAO,MAAMC,gCAAgCL;IAC3C,YAAYM,MAAc,CAAE;QAC1B,KAAK,CAACA;QACN,IAAI,CAACF,IAAI,GAAG;IACd;AACF;AAEA,6EAA6E,GAC7E,OAAO,SAASG,mBAAmBC,IAAa;IAC9C,MAAMC,SAAUD,wBAAD,AAACA,KACZC,MAAM;IACV,IAAI,CAACA,UAAU,OAAOA,WAAW,UAAU,OAAO;QAAEC,SAAS,EAAE;QAAEC,cAAc;IAAK;IACpF,MAAMC,SAASC,MAAMC,OAAO,CAACL,OAAOM,WAAW,IAAIN,OAAOM,WAAW,GAAG,EAAE;IAC1E,MAAML,UAAU9B,kBAAkBoC,MAAM,CAAC,CAACC,OAASL,OAAOM,QAAQ,CAACD;IACnE,MAAMN,eACJ,OAAOF,OAAOU,UAAU,KAAK,WAAWC,KAAKC,KAAK,CAACZ,OAAOU,UAAU,IAAIG,OAAOC,GAAG;IACpF,OAAO;QAAEb;QAASC,cAAcW,OAAOE,QAAQ,CAACb,gBAAgBA,eAAe;IAAK;AACtF;AAEA,4EAA4E,GAC5E,OAAO,SAASc,kBAAkBC,IAAY;IAC5C,OAAO,CAAC,QAAQ,EAAEA,KAAK,CAAC,CAAC;AAC3B;AAEA,sEAAsE,GACtE,OAAO,SAASC,wBAAwBC,OAKvC;QACiBA,gBACEA;IADlB,MAAMC,WAAUD,iBAAAA,QAAQE,KAAK,YAAbF,iBAAiBE;IACjC,MAAMC,aAAYH,qBAAAA,QAAQG,SAAS,YAAjBH,qBAAqBtC;IACvC,OAAO;QACL,MAAM0C,WAAUC,GAAW;gBAGLL;YAFpB,MAAMM,QAAQ,MAAMN,QAAQO,WAAW;YACvC,IAAI,CAACD,OAAO,MAAM,IAAI7B,wBAAwB;YAC9C,MAAM+B,MAAM,IAAIC,KAAIT,oBAAAA,QAAQU,QAAQ,YAAhBV,oBAAoB1C;YACxC,KAAK,MAAM+B,QAAQrC,kBAAmBwD,IAAIG,YAAY,CAACC,MAAM,CAAC,eAAevB;YAC7EmB,IAAIG,YAAY,CAACE,GAAG,CAAC,OAAOR;YAC5B,MAAMS,aAAa,IAAIC;YACvB,MAAMC,QAAQC,WAAW,IAAMH,WAAWI,KAAK,IAAIf;YACnD,IAAI;gBACF,MAAMgB,WAAW,MAAMlB,QAAQO,IAAIY,QAAQ,IAAI;oBAC7CC,QAAQ;oBACRC,SAAS;wBAAEC,eAAe,CAAC,OAAO,EAAEjB,OAAO;wBAAEkB,QAAQ;oBAAmB;oBACxEC,QAAQX,WAAWW,MAAM;gBAC3B;gBACA,IAAI,CAACN,SAASO,EAAE,EAAE;oBAChB,MAAM,IAAIvD,iBAAiBgD,SAAS9C,MAAM,EAAE,MAAM8C,SAASQ,IAAI,GAAGC,KAAK,CAAC,IAAM;gBAChF;gBACA,OAAOjD,mBAAmB,MAAMwC,SAASU,IAAI;YAC/C,SAAU;gBACRC,aAAad;YACf;QACF;IACF;AACF;AAEA;;;CAGC,GACD,eAAee;IACb,IAAI;;YAGM;QAFR,MAAMC,aAAa5E,SAAS4C,OAAO,CAACgC,UAAU;QAC9C,IAAI,CAACA,YAAY,OAAO;QACxB,gBAAQ,QAAA,MAAMA,WAAWC,cAAc,uBAAhC,AAAC,MAAoCC,YAAY,mBAAI;IAC9D,EAAE,eAAM;QACN,OAAO;IACT;AACF;AAEA,IAAIC,gBAAsC;AAC1C,IAAIC;AAEJ,SAASC;IACP,IAAID,mBAAmBE,WAAW,OAAOF;IACzCD,wBAAAA,gBAAAA,gBAAkBpC,wBAAwB;QAAEQ,aAAawB;IAA0B;IACnF,OAAOI;AACT;AASA,MAAMI,SAAS,IAAIC;AACnB,IAAIC,aAAoD;AACxD,IAAIC,iBAAiB;AACrB,IAAIC,eAAe;AAEnB,0FAA0F,GAC1F,OAAO,SAASC,qBAAqBC,MAA6B;IAChEN,OAAOO,KAAK;IACZL,aAAa;IACbC,iBAAiB;IACjBC,eAAe;IACfP,iBAAiBS;IACjBV,gBAAgB;AAClB;AAEA,SAASY,KAAKC,KAAa,EAAEC,OAAe,EAAEC,KAAe;IAC3D,IAAIF,QAAQL,eAAezE,eAAe;IAC1CyE,eAAeK;IACfG,QAAQJ,IAAI,CAAC,CAAC,WAAW,EAAEE,QAAQ,gDAAgD,CAAC,EAAEC,gBAAAA,QAAS;AACjG;AAEA,wFAAwF,GACxF,eAAeE,YAAYJ,KAAa;IACtC,IAAIP,cAAcO,QAAQP,WAAWY,IAAI,GAAGtF,eAAe,OAAO0E,WAAWa,GAAG;IAChF,MAAMA,MAAM,MAAMjG,cACfkG,GAAG,GACHC,SAAS,GACTC,UAAU,CAACjG,8BACXkG,GAAG,CAACjG,uBACJkG,GAAG,GACHC,IAAI,CACH,CAACC,WAAaA,SAASF,GAAG,CAAC,eAAe,OAC1C,IAAM;IAEVlB,aAAa;QAAEa;QAAKD,MAAML;IAAM;IAChC,OAAOM;AACT;AAEA;;;CAGC,GACD,OAAO,SAASQ;IACd,OAAO;QAAC/G;KAAY,CAACqC,MAAM,CAAC2E;AAC9B;AAEA,SAASC,eAAelE,IAAY;IAClC,OAAOgE,+BAA+BG,IAAI,CACxC,CAACC,SAAWpE,SAASoE,UAAUpE,KAAKqE,QAAQ,CAAC,CAAC,CAAC,EAAED,QAAQ;AAE7D;AAEA,SAASE,YAAYC,IAAyC;IAC5D,IAAI,CAACA,MAAM,OAAO;IAClB,MAAMvF,UAAUG,MAAMC,OAAO,CAACmF,IAAI,CAAC,UAAU,IACzCrH,kBAAkBoC,MAAM,CAAC,CAACC,OAAS,AAACgF,IAAI,CAAC,UAAU,CAAe/E,QAAQ,CAACD,SAC3E,EAAE;IACN,MAAMiF,cAAc5E,OAAO2E,IAAI,CAAC,cAAc;IAC9C,MAAME,cAAc7E,OAAO2E,IAAI,CAAC,cAAc;IAC9C,IAAI,CAAC3E,OAAOE,QAAQ,CAAC0E,gBAAgB,CAAC5E,OAAOE,QAAQ,CAAC2E,cAAc,OAAO;IAC3E,OAAO;QAAEzF;QAASwF;QAAaC;IAAY;AAC7C;AAEA,SAASC,SAAS1E,IAAY,EAAE2E,OAAgB,EAAEzB,KAAa;IAC7DT,OAAO1B,GAAG,CAACf,MAAM,aACZ2E;QACHC,eAAeD,QAAQ3F,OAAO,CAAC6F,MAAM,GACjCF,QAAQF,WAAW,GACnBK,KAAKC,GAAG,CAACJ,QAAQF,WAAW,EAAEvB,QAAQnF;;AAE9C;AAeA;;;;;;;;;;;CAWC,GACD,OAAO,eAAeiH,qBACpBC,KAAwB,EACxB/E,UAAuC,CAAC,CAAC;IAEzC,IAAI;QACF,OAAO,MAAMgF,4BAA4BD,OAAO/E;IAClD,EAAE,OAAOkD,OAAO;YACTlD;QAAL+C,MAAK/C,iBAAAA,QAAQgD,KAAK,YAAbhD,iBAAiBR,KAAKyF,GAAG,IAAI,0CAA0C/B;QAC5E,MAAMgC,UAAU;eACX,IAAIC,IACLJ,MACGK,GAAG,CAAC,CAACC,MAAQnI,wBAAwBmI,MACrCjG,MAAM,CAAC,CAACU,OAAyBiE,QAAQjE;SAE/C;QACD,OAAO;YAAEwF,MAAM,EAAE;YAAEC,OAAO,EAAE;YAAEL;YAASM,QAAQ;QAAE;IACnD;AACF;AAEA,eAAeR,4BACbD,KAAwB,EACxB/E,OAAoC;QAEtBA;IAAd,MAAMgD,SAAQhD,iBAAAA,QAAQgD,KAAK,YAAbhD,iBAAiBR,KAAKyF,GAAG;IACvC,MAAMQ,SAAqC;QAAEH,MAAM,EAAE;QAAEC,OAAO,EAAE;QAAEL,SAAS,EAAE;QAAEM,QAAQ;IAAE;IACzF,MAAME,SAAmB,EAAE;IAC3B,KAAK,MAAML,OAAON,MAAO;YAIG/E;QAH1B,MAAMF,OAAO5C,wBAAwBmI;QACrC,IAAI,CAACvF,QAAQ4F,OAAOpG,QAAQ,CAACQ,SAAS2F,OAAOF,KAAK,CAACjG,QAAQ,CAACQ,OAAO;QACnE,IAAIkE,eAAelE,OAAO2F,OAAOF,KAAK,CAACI,IAAI,CAAC7F;aACvC,IAAI4F,OAAOf,MAAM,KAAI3E,oBAAAA,QAAQ4F,QAAQ,YAAhB5F,oBAAoB/C,kCAAkCyI,OAAOC,IAAI,CAAC7F;aACvF2F,OAAOP,OAAO,CAACS,IAAI,CAAC7F;IAC3B;IACA,IAAI,CAAC4F,OAAOf,MAAM,EAAE,OAAOc;IAE3B,MAAMI,SAAS,CAAC/F,MAAc2E;QAC5B,IAAIA,QAAQ3F,OAAO,CAAC6F,MAAM,EAAEc,OAAOH,IAAI,CAACK,IAAI,CAAC;YAAE7F;YAAMhB,SAAS2F,QAAQ3F,OAAO;QAAC;aACzE2G,OAAOF,KAAK,CAACI,IAAI,CAAC7F;IACzB;IAEA,4BAA4B;IAC5B,IAAIgG,UAAUJ,OAAOtG,MAAM,CAAC,CAACU;QAC3B,MAAMiG,QAAQxD,OAAOoB,GAAG,CAAC7D;QACzB,IAAIiG,SAAS/C,QAAQ+C,MAAMrB,aAAa,EAAE;YACxCmB,OAAO/F,MAAMiG;YACb,OAAO;QACT;QACA,OAAO;IACT;IACA,IAAI,CAACD,QAAQnB,MAAM,EAAE,OAAOc;IAE5B,4DAA4D;IAC5D,IAAI,MAAMrC,YAAYJ,QAAQ;QAC5ByC,OAAOP,OAAO,CAACS,IAAI,IAAIG;QACvB,OAAOL;IACT;IAEA,mCAAmC;IACnC,MAAMjC,YAAYnG,cAAckG,GAAG,GAAGC,SAAS;IAC/C,MAAMC,aAAaD,UAAUC,UAAU,CAAClG;IACxC,IAAI;QACF,MAAMyI,YAAY,MAAMxC,UAAUyC,MAAM,IAAIH,QAAQV,GAAG,CAAC,CAACtF,OAAS2D,WAAWC,GAAG,CAAC5D;QACjF,MAAMoG,SAAS,IAAI1D;QACnB,KAAK,MAAMqB,YAAYmC,UAAW;YAChC,MAAMvB,UAAUL,YAAYP,SAASsC,MAAM,GAAItC,SAASQ,IAAI,KAAiC/B;YAC7F,IAAImC,WAAWzB,QAAQyB,QAAQF,WAAW,EAAE2B,OAAOrF,GAAG,CAACgD,SAASuC,EAAE,EAAE3B;QACtE;QACAqB,UAAUA,QAAQ1G,MAAM,CAAC,CAACU;YACxB,MAAM2E,UAAUyB,OAAOvC,GAAG,CAAC7D;YAC3B,IAAI,CAAC2E,SAAS,OAAO;YACrBD,SAAS1E,MAAM2E,SAASzB;YACxB6C,OAAO/F,MAAM2E;YACb,OAAO;QACT;IACF,EAAE,OAAOvB,OAAO;QACdH,KAAKC,OAAO,uCAAuCE;IACrD;IACA,IAAI,CAAC4C,QAAQnB,MAAM,EAAE,OAAOc;IAE5B,iEAAiE;IACjE,MAAM5C,SAASR;IACf,IAAI,CAACQ,UAAUG,QAAQN,gBAAgB;QACrC+C,OAAOP,OAAO,CAACS,IAAI,IAAIG;QACvB,OAAOL;IACT;IACAA,OAAOD,MAAM,GAAGM,QAAQnB,MAAM;IAC9B,MAAM0B,UAAU,IAAI7D;IACpB,MAAM8D,QAAQR,QAAQV,GAAG,CAAC,OAAOtF;QAC/B,IAAI;gBAOKyG;YANP,MAAMA,SAAS,MAAM1D,OAAOzC,SAAS,CAACP,kBAAkBC;YACxD,MAAMwE,cAAc9E,KAAKyF,GAAG;YAC5B,MAAMR,UAAmB;gBACvB3F,SAASyH,OAAOzH,OAAO;gBACvBwF;gBACAC,aAAagC,OAAOzH,OAAO,CAAC6F,MAAM,IAC7B4B,uBAAAA,OAAOxH,YAAY,YAAnBwH,uBAAuBjC,cAAcxG,kCACtCwG,cAAc1G;YACpB;YACAyI,QAAQxF,GAAG,CAACf,MAAM2E;YAClBD,SAAS1E,MAAM2E,SAASH;YACxB,MAAMb,WACHC,GAAG,CAAC5D,MACJe,GAAG,CAAC;gBAAEf;eAAS2E;gBAASzF,QAAQyF,QAAQ3F,OAAO,CAAC6F,MAAM,GAAG;gBACzD/C,KAAK,CAAC,CAACsB,QAAmBH,KAAKuB,aAAa,iCAAiCpB;QAClF,EAAE,OAAOA,OAAO;YACd,IAAIA,iBAAiB/E,oBAAoB;gBAAC;gBAAK;gBAAK;aAAI,CAACmB,QAAQ,CAAC4D,MAAM7E,MAAM,GAAG;gBAC/EqE,iBAAiBlD,KAAKyF,GAAG,KAAKjH;gBAC9BmF,QAAQD,KAAK,CAAC,6EAA6EA;YAC7F,OAAO,IAAIA,iBAAiB/E,oBAAoB+E,MAAM7E,MAAM,KAAK,KAAK;gBACpEqE,iBAAiBlD,KAAKyF,GAAG,KAAKhH;gBAC9B8E,KAAKvD,KAAKyF,GAAG,IAAI,4BAA4B/B;YAC/C,OAAO,IAAIA,iBAAiBzE,yBAAyB;gBACnDiE,iBAAiBlD,KAAKyF,GAAG,KAAKjH;gBAC9B+E,KAAKvD,KAAKyF,GAAG,IAAI,sCAAsC/B;YACzD,OAAO;gBACLH,KAAKvD,KAAKyF,GAAG,IAAI,CAAC,cAAc,EAAEnF,KAAK,oBAAoB,CAAC,EAAEoD;YAChE;QACF;IACF;IACA,IAAIlC;IACJ,MAAMwF,QAAQC,IAAI,CAAC;QACjBD,QAAQE,UAAU,CAACJ;QACnB,IAAIE,QAAc,CAACG;gBACW3G;YAA5BgB,QAAQC,WAAW0F,UAAS3G,sBAAAA,QAAQ4G,UAAU,YAAlB5G,sBAAsBrC;QACpD;KACD;IACD,IAAIqD,OAAOc,aAAad;IACxB,MAAM6F,OAAOf,QAAQ1G,MAAM,CAAC,CAACU,OAAS,CAACuG,QAAQS,GAAG,CAAChH;IACnD,KAAK,MAAMA,QAAQgG,QAAS;QAC1B,MAAMrB,UAAU4B,QAAQ1C,GAAG,CAAC7D;QAC5B,IAAI2E,SAASoB,OAAO/F,MAAM2E;IAC5B;IACAgB,OAAOP,OAAO,CAACS,IAAI,IAAIkB;IACvB,IAAIA,KAAKlC,MAAM,EAAE5B,KAAKvD,KAAKyF,GAAG,IAAI,GAAG4B,KAAKlC,MAAM,CAAC,0CAA0C,CAAC;IAC5F,OAAOc;AACT;AAEA;;;;CAIC,GACD,OAAO,SAASsB;IACd,IAAI,OAAO5J,4BAA4B,YAAY;IACnDA,wBAAwB,CAAC4H,OAAO/E,UAC9B8E,qBAAqBC,OAAO;YAAE6B,UAAU,EAAE5G,2BAAAA,QAAS4G,UAAU;QAAC;AAElE"}
package/src/tenancy.d.ts CHANGED
@@ -38,7 +38,7 @@ export * from './lib/server/csp-aggregate';
38
38
  export * from './lib/server/data-storage-gate';
39
39
  export * from './lib/server/contact-suppression';
40
40
  export * from './lib/server/email-suppression';
41
- export * from './lib/server/email-topic-confirmation';
41
+ export * from './lib/server/topic-subscriptions';
42
42
  export * from './lib/server/email-unsubscribe-link';
43
43
  export * from './lib/server/email-flow-gate';
44
44
  export * from './lib/server/email-marketing-gate';
@@ -117,8 +117,6 @@ export * from './lib/server/upsert-contact';
117
117
  export * from './lib/server/crm-records';
118
118
  export * from './lib/server/crm-next-activity';
119
119
  export * from './lib/server/crm-email-activity';
120
- export * from './lib/server/crm-booking-activity';
121
- export * from './lib/server/crm-inbound-email';
122
120
  export * from './lib/server/member-email-aliases';
123
121
  export * from './lib/server/user-profiles';
124
122
  export * from './lib/server/new-account-announcement';
package/src/tenancy.js CHANGED
@@ -46,7 +46,7 @@ export * from "./lib/server/email-suppression.js";
46
46
  // The double opt-in quarantine, after the module whose keying it shares and
47
47
  // whose `filterTopicSendable` is the reader that makes it a quarantine rather
48
48
  // than a stored fact.
49
- export * from "./lib/server/email-topic-confirmation.js";
49
+ export * from "./lib/server/topic-subscriptions.js";
50
50
  export * from "./lib/server/email-unsubscribe-link.js";
51
51
  export * from "./lib/server/email-flow-gate.js";
52
52
  // Side-effecting on purpose, for the reason `email-send-rate` below is:
@@ -148,12 +148,6 @@ export * from "./lib/server/crm-next-activity.js";
148
148
  // Where a one-to-one email's activity row lives and how the delivery
149
149
  // webhook advances it (AGL-2615).
150
150
  export * from "./lib/server/crm-email-activity.js";
151
- // What a confirmed booking files on the CRM record — the meeting and the
152
- // follow-up (AGL-2660).
153
- export * from "./lib/server/crm-booking-activity.js";
154
- // The org's email capture token and the filing of one received message
155
- // on the record it was with (AGL-2657).
156
- export * from "./lib/server/crm-inbound-email.js";
157
151
  // A member's own addresses in a workspace, and the signed link that
158
152
  // confirms one (AGL-2975).
159
153
  export * from "./lib/server/member-email-aliases.js";
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../libs/tenant/data/admin/src/tenancy.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2022 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nexport * from './lib/server/analytics-retention'\nexport * from './lib/server/api-http'\nexport * from './lib/server/api-keys'\nexport * from './lib/server/account-emails'\nexport * from './lib/server/auth-pools'\nexport * from './lib/server/client-error-report'\n// The merchant's payment account, as a contract. Its adapter is not exported:\n// a plugin asks the contract, and the doors that only need the readiness rule\n// import `@aglyn/tenant-data-admin/server/payment-provider` directly, which\n// loads no Firebase.\nexport {\n merchantAccountIsReady,\n merchantAccountReadiness,\n paymentProvider,\n} from './lib/server/payment-provider'\nexport type {\n MerchantAccountReadiness,\n MerchantAccountReadinessInput,\n PaymentMode,\n PaymentProvider,\n PaymentProviderEvent,\n} from './lib/server/payment-provider'\nexport * from './lib/server/auth-handoff'\nexport * from './lib/server/console-domains'\nexport * from './lib/server/dns-probe'\nexport * from './lib/server/sending-domains'\nexport * from './lib/server/sender-readiness'\nexport * from './lib/server/host-sending-domain'\nexport * from './lib/server/sending-domain-debt'\nexport * from './lib/server/sending-domain-recheck'\n// The hosting seam itself, so a door can ask whether this deployment is able\n// to register a name at all before it promises the caller one.\n// `workspace-domains` re-exports the pieces its own callers share with it; the\n// provider behind it is not among them.\nexport { domainProvider } from './lib/server/domain-provider'\nexport type {\n DomainProvider,\n DomainProviderId,\n DomainScope,\n} from './lib/server/domain-provider'\nexport * from './lib/server/platform-domain-names'\nexport * from './lib/server/consume-once'\nexport * from './lib/server/csp-aggregate'\nexport * from './lib/server/data-storage-gate'\nexport * from './lib/server/contact-suppression'\nexport * from './lib/server/email-suppression'\n// The double opt-in quarantine, after the module whose keying it shares and\n// whose `filterTopicSendable` is the reader that makes it a quarantine rather\n// than a stored fact.\nexport * from './lib/server/email-topic-confirmation'\nexport * from './lib/server/email-unsubscribe-link'\nexport * from './lib/server/email-flow-gate'\n// Side-effecting on purpose, for the reason `email-send-rate` below is:\n// evaluating this module INSTALLS the marketing gate on `sendEmail`, so the\n// unsubscribe header, the suppression check and the frequency ceiling reach\n// every bulk sender without a call at each server entrypoint.\nexport * from './lib/server/email-marketing-gate'\n// A consent group change (AGL-3320): the carries of the three stores above,\n// and the executor that runs a change around them.\nexport * from './lib/server/consent-group-carry'\nexport * from './lib/server/consent-group-change'\n// A person's refusal kept after the record it was written on is deleted\n// (AGL-3338): the delete's write, the list gate's read, and the change's carry.\nexport * from './lib/server/retained-refusals'\nexport * from './lib/server/sms-keywords'\nexport * from './lib/server/document-id'\nexport * from './lib/server/collection-preview-token'\nexport * from './lib/server/edit-access-authz'\nexport * from './lib/server/edit-access-token'\nexport * from './lib/server/edit-hint-token'\nexport * from './lib/server/email-metering'\n// Side-effecting on purpose (AGL-2409): evaluating this module INSTALLS the\n// platform send-rate governor on `sendEmail`. Every server surface already\n// imports this barrel, so nothing has to remember to call an installer.\nexport * from './lib/server/campaign-conversion-attribution'\nexport * from './lib/server/email-send-rate'\n// Side-effecting on purpose, for the same reason (AGL-3328): evaluating this\n// module INSTALLS the deliverability preflight on `sendEmail`, so a recipient\n// whose domain takes no mail is refused, and bulk mail behind a gateway that\n// refused its sending domain twice is held, on every send path at once.\nexport * from './lib/server/email-deliverability'\nexport * from './lib/server/email-sender-reputation'\nexport * from './lib/server/erase'\nexport * from './lib/server/erase-person'\nexport * from './lib/server/dynamic-list-materialize'\nexport * from './lib/server/firebase-admin'\nexport * from './lib/server/free-workspace-cap'\nexport * from './lib/server/ga4-measurement-protocol'\nexport * from './lib/server/host-memberships'\nexport * from './lib/server/host-visitor-records'\nexport * from './lib/server/list-members'\nexport * from './lib/server/lockdown'\nexport * from './lib/server/token-revocation'\nexport * from './lib/server/tenant-write-lockdown'\nexport * from './lib/server/outbound-send-review'\n// The same review for a published page, read where it is composed (AGL-3356).\nexport * from './lib/server/hosted-page-review'\n// Every hold, flag, lock and pause tells owners and staff through one seam (AGL-3368).\nexport * from './lib/server/risk-notice'\nexport * from './lib/server/host-email-tokens'\nexport * from './lib/server/notifications'\n// Every operator alert raises through one entry point, and every health check\n// remembers its state (AGL-3377).\nexport * from './lib/server/operator-alerts'\nexport * from './lib/server/operator-health'\nexport * from './lib/server/org-billing'\nexport * from './lib/server/organizations'\nexport * from './lib/server/duplicate-activity'\nexport * from './lib/server/duplicate-resource'\nexport * from './lib/server/workspace-domains'\nexport * from './lib/server/membership-recover-throttle'\nexport * from './lib/server/password-reset-throttle'\nexport * from './lib/server/rate-limit-store'\nexport * from './lib/server/verify-email-cooldown'\nexport * from './lib/server/vercel-drain-signature'\nexport * from './lib/server/vercel-log-drain'\nexport * from './lib/server/visitor-write-rate-limit'\nexport * from './lib/server/card-payment-velocity'\nexport * from './lib/server/console-api-rate-limit'\nexport * from './lib/server/realm-plugins'\nexport * from './lib/server/release-flags'\nexport * from './lib/server/resolve-people'\nexport * from './lib/server/member-photo'\nexport * from './lib/server/personal-data-export'\nexport * from './lib/server/media-delivery'\nexport * from './lib/server/media-download-tokens'\nexport * from './lib/server/media-quarantine'\nexport * from './lib/server/media-signing'\nexport * from './lib/server/media-storage-path'\nexport * from './lib/server/safe-equal'\nexport * from './lib/server/media-strong-digest'\nexport * from './lib/server/media-tombstone'\nexport * from './lib/server/media-variants'\nexport * from './lib/server/serve-media-cdn'\nexport * from './lib/server/serve-plugin-fetch'\nexport * from './lib/server/configured-url-fetch'\nexport * from './lib/server/sso-break-glass-owners'\nexport * from './lib/server/sso-domain-policy'\nexport * from './lib/server/sso-enforcement'\nexport * from './lib/server/sso-provisioning'\nexport * from './lib/server/update-existing'\nexport * from './lib/server/contact-company-link'\nexport * from './lib/server/contact-email-index'\nexport * from './lib/server/upsert-contact'\nexport * from './lib/server/crm-records'\n// The `nextTaskAtMs` a contact, a company and a deal carry, recomputed from\n// their open tasks by every server-side task writer (AGL-2661).\nexport * from './lib/server/crm-next-activity'\n// Where a one-to-one email's activity row lives and how the delivery\n// webhook advances it (AGL-2615).\nexport * from './lib/server/crm-email-activity'\n// What a confirmed booking files on the CRM record — the meeting and the\n// follow-up (AGL-2660).\nexport * from './lib/server/crm-booking-activity'\n// The org's email capture token and the filing of one received message\n// on the record it was with (AGL-2657).\nexport * from './lib/server/crm-inbound-email'\n// A member's own addresses in a workspace, and the signed link that\n// confirms one (AGL-2975).\nexport * from './lib/server/member-email-aliases'\nexport * from './lib/server/user-profiles'\n// The one \"New account\" staff notice per account, keyed on the auth\n// record's creation (AGL-3225).\nexport * from './lib/server/new-account-announcement'\nexport * from './lib/server/legal-acceptance'\n// The person's decision about the platform's own product email, on their\n// document and on the operator's marketing contact (AGL-3185).\nexport * from './lib/server/platform-marketing-consent'\n"],"names":["merchantAccountIsReady","merchantAccountReadiness","paymentProvider","domainProvider"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED,cAAc,sCAAkC;AAChD,cAAc,2BAAuB;AACrC,cAAc,2BAAuB;AACrC,cAAc,iCAA6B;AAC3C,cAAc,6BAAyB;AACvC,cAAc,sCAAkC;AAChD,8EAA8E;AAC9E,8EAA8E;AAC9E,4EAA4E;AAC5E,qBAAqB;AACrB,SACEA,sBAAsB,EACtBC,wBAAwB,EACxBC,eAAe,QACV,mCAA+B;AAQtC,cAAc,+BAA2B;AACzC,cAAc,kCAA8B;AAC5C,cAAc,4BAAwB;AACtC,cAAc,kCAA8B;AAC5C,cAAc,mCAA+B;AAC7C,cAAc,sCAAkC;AAChD,cAAc,sCAAkC;AAChD,cAAc,yCAAqC;AACnD,6EAA6E;AAC7E,+DAA+D;AAC/D,+EAA+E;AAC/E,wCAAwC;AACxC,SAASC,cAAc,QAAQ,kCAA8B;AAM7D,cAAc,wCAAoC;AAClD,cAAc,+BAA2B;AACzC,cAAc,gCAA4B;AAC1C,cAAc,oCAAgC;AAC9C,cAAc,sCAAkC;AAChD,cAAc,oCAAgC;AAC9C,4EAA4E;AAC5E,8EAA8E;AAC9E,sBAAsB;AACtB,cAAc,2CAAuC;AACrD,cAAc,yCAAqC;AACnD,cAAc,kCAA8B;AAC5C,wEAAwE;AACxE,4EAA4E;AAC5E,4EAA4E;AAC5E,8DAA8D;AAC9D,cAAc,uCAAmC;AACjD,4EAA4E;AAC5E,mDAAmD;AACnD,cAAc,sCAAkC;AAChD,cAAc,uCAAmC;AACjD,wEAAwE;AACxE,gFAAgF;AAChF,cAAc,oCAAgC;AAC9C,cAAc,+BAA2B;AACzC,cAAc,8BAA0B;AACxC,cAAc,2CAAuC;AACrD,cAAc,oCAAgC;AAC9C,cAAc,oCAAgC;AAC9C,cAAc,kCAA8B;AAC5C,cAAc,iCAA6B;AAC3C,4EAA4E;AAC5E,2EAA2E;AAC3E,wEAAwE;AACxE,cAAc,kDAA8C;AAC5D,cAAc,kCAA8B;AAC5C,6EAA6E;AAC7E,8EAA8E;AAC9E,6EAA6E;AAC7E,wEAAwE;AACxE,cAAc,uCAAmC;AACjD,cAAc,0CAAsC;AACpD,cAAc,wBAAoB;AAClC,cAAc,+BAA2B;AACzC,cAAc,2CAAuC;AACrD,cAAc,iCAA6B;AAC3C,cAAc,qCAAiC;AAC/C,cAAc,2CAAuC;AACrD,cAAc,mCAA+B;AAC7C,cAAc,uCAAmC;AACjD,cAAc,+BAA2B;AACzC,cAAc,2BAAuB;AACrC,cAAc,mCAA+B;AAC7C,cAAc,wCAAoC;AAClD,cAAc,uCAAmC;AACjD,8EAA8E;AAC9E,cAAc,qCAAiC;AAC/C,uFAAuF;AACvF,cAAc,8BAA0B;AACxC,cAAc,oCAAgC;AAC9C,cAAc,gCAA4B;AAC1C,8EAA8E;AAC9E,kCAAkC;AAClC,cAAc,kCAA8B;AAC5C,cAAc,kCAA8B;AAC5C,cAAc,8BAA0B;AACxC,cAAc,gCAA4B;AAC1C,cAAc,qCAAiC;AAC/C,cAAc,qCAAiC;AAC/C,cAAc,oCAAgC;AAC9C,cAAc,8CAA0C;AACxD,cAAc,0CAAsC;AACpD,cAAc,mCAA+B;AAC7C,cAAc,wCAAoC;AAClD,cAAc,yCAAqC;AACnD,cAAc,mCAA+B;AAC7C,cAAc,2CAAuC;AACrD,cAAc,wCAAoC;AAClD,cAAc,yCAAqC;AACnD,cAAc,gCAA4B;AAC1C,cAAc,gCAA4B;AAC1C,cAAc,iCAA6B;AAC3C,cAAc,+BAA2B;AACzC,cAAc,uCAAmC;AACjD,cAAc,iCAA6B;AAC3C,cAAc,wCAAoC;AAClD,cAAc,mCAA+B;AAC7C,cAAc,gCAA4B;AAC1C,cAAc,qCAAiC;AAC/C,cAAc,6BAAyB;AACvC,cAAc,sCAAkC;AAChD,cAAc,kCAA8B;AAC5C,cAAc,iCAA6B;AAC3C,cAAc,kCAA8B;AAC5C,cAAc,qCAAiC;AAC/C,cAAc,uCAAmC;AACjD,cAAc,yCAAqC;AACnD,cAAc,oCAAgC;AAC9C,cAAc,kCAA8B;AAC5C,cAAc,mCAA+B;AAC7C,cAAc,kCAA8B;AAC5C,cAAc,uCAAmC;AACjD,cAAc,sCAAkC;AAChD,cAAc,iCAA6B;AAC3C,cAAc,8BAA0B;AACxC,4EAA4E;AAC5E,gEAAgE;AAChE,cAAc,oCAAgC;AAC9C,qEAAqE;AACrE,kCAAkC;AAClC,cAAc,qCAAiC;AAC/C,yEAAyE;AACzE,wBAAwB;AACxB,cAAc,uCAAmC;AACjD,uEAAuE;AACvE,wCAAwC;AACxC,cAAc,oCAAgC;AAC9C,oEAAoE;AACpE,2BAA2B;AAC3B,cAAc,uCAAmC;AACjD,cAAc,gCAA4B;AAC1C,oEAAoE;AACpE,gCAAgC;AAChC,cAAc,2CAAuC;AACrD,cAAc,mCAA+B;AAC7C,yEAAyE;AACzE,+DAA+D;AAC/D,cAAc,6CAAyC"}
1
+ {"version":3,"sources":["../../../../../../libs/tenant/data/admin/src/tenancy.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2022 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nexport * from './lib/server/analytics-retention'\nexport * from './lib/server/api-http'\nexport * from './lib/server/api-keys'\nexport * from './lib/server/account-emails'\nexport * from './lib/server/auth-pools'\nexport * from './lib/server/client-error-report'\n// The merchant's payment account, as a contract. Its adapter is not exported:\n// a plugin asks the contract, and the doors that only need the readiness rule\n// import `@aglyn/tenant-data-admin/server/payment-provider` directly, which\n// loads no Firebase.\nexport {\n merchantAccountIsReady,\n merchantAccountReadiness,\n paymentProvider,\n} from './lib/server/payment-provider'\nexport type {\n MerchantAccountReadiness,\n MerchantAccountReadinessInput,\n PaymentMode,\n PaymentProvider,\n PaymentProviderEvent,\n} from './lib/server/payment-provider'\nexport * from './lib/server/auth-handoff'\nexport * from './lib/server/console-domains'\nexport * from './lib/server/dns-probe'\nexport * from './lib/server/sending-domains'\nexport * from './lib/server/sender-readiness'\nexport * from './lib/server/host-sending-domain'\nexport * from './lib/server/sending-domain-debt'\nexport * from './lib/server/sending-domain-recheck'\n// The hosting seam itself, so a door can ask whether this deployment is able\n// to register a name at all before it promises the caller one.\n// `workspace-domains` re-exports the pieces its own callers share with it; the\n// provider behind it is not among them.\nexport { domainProvider } from './lib/server/domain-provider'\nexport type {\n DomainProvider,\n DomainProviderId,\n DomainScope,\n} from './lib/server/domain-provider'\nexport * from './lib/server/platform-domain-names'\nexport * from './lib/server/consume-once'\nexport * from './lib/server/csp-aggregate'\nexport * from './lib/server/data-storage-gate'\nexport * from './lib/server/contact-suppression'\nexport * from './lib/server/email-suppression'\n// The double opt-in quarantine, after the module whose keying it shares and\n// whose `filterTopicSendable` is the reader that makes it a quarantine rather\n// than a stored fact.\nexport * from './lib/server/topic-subscriptions'\nexport * from './lib/server/email-unsubscribe-link'\nexport * from './lib/server/email-flow-gate'\n// Side-effecting on purpose, for the reason `email-send-rate` below is:\n// evaluating this module INSTALLS the marketing gate on `sendEmail`, so the\n// unsubscribe header, the suppression check and the frequency ceiling reach\n// every bulk sender without a call at each server entrypoint.\nexport * from './lib/server/email-marketing-gate'\n// A consent group change (AGL-3320): the carries of the three stores above,\n// and the executor that runs a change around them.\nexport * from './lib/server/consent-group-carry'\nexport * from './lib/server/consent-group-change'\n// A person's refusal kept after the record it was written on is deleted\n// (AGL-3338): the delete's write, the list gate's read, and the change's carry.\nexport * from './lib/server/retained-refusals'\nexport * from './lib/server/sms-keywords'\nexport * from './lib/server/document-id'\nexport * from './lib/server/collection-preview-token'\nexport * from './lib/server/edit-access-authz'\nexport * from './lib/server/edit-access-token'\nexport * from './lib/server/edit-hint-token'\nexport * from './lib/server/email-metering'\n// Side-effecting on purpose (AGL-2409): evaluating this module INSTALLS the\n// platform send-rate governor on `sendEmail`. Every server surface already\n// imports this barrel, so nothing has to remember to call an installer.\nexport * from './lib/server/campaign-conversion-attribution'\nexport * from './lib/server/email-send-rate'\n// Side-effecting on purpose, for the same reason (AGL-3328): evaluating this\n// module INSTALLS the deliverability preflight on `sendEmail`, so a recipient\n// whose domain takes no mail is refused, and bulk mail behind a gateway that\n// refused its sending domain twice is held, on every send path at once.\nexport * from './lib/server/email-deliverability'\nexport * from './lib/server/email-sender-reputation'\nexport * from './lib/server/erase'\nexport * from './lib/server/erase-person'\nexport * from './lib/server/dynamic-list-materialize'\nexport * from './lib/server/firebase-admin'\nexport * from './lib/server/free-workspace-cap'\nexport * from './lib/server/ga4-measurement-protocol'\nexport * from './lib/server/host-memberships'\nexport * from './lib/server/host-visitor-records'\nexport * from './lib/server/list-members'\nexport * from './lib/server/lockdown'\nexport * from './lib/server/token-revocation'\nexport * from './lib/server/tenant-write-lockdown'\nexport * from './lib/server/outbound-send-review'\n// The same review for a published page, read where it is composed (AGL-3356).\nexport * from './lib/server/hosted-page-review'\n// Every hold, flag, lock and pause tells owners and staff through one seam (AGL-3368).\nexport * from './lib/server/risk-notice'\nexport * from './lib/server/host-email-tokens'\nexport * from './lib/server/notifications'\n// Every operator alert raises through one entry point, and every health check\n// remembers its state (AGL-3377).\nexport * from './lib/server/operator-alerts'\nexport * from './lib/server/operator-health'\nexport * from './lib/server/org-billing'\nexport * from './lib/server/organizations'\nexport * from './lib/server/duplicate-activity'\nexport * from './lib/server/duplicate-resource'\nexport * from './lib/server/workspace-domains'\nexport * from './lib/server/membership-recover-throttle'\nexport * from './lib/server/password-reset-throttle'\nexport * from './lib/server/rate-limit-store'\nexport * from './lib/server/verify-email-cooldown'\nexport * from './lib/server/vercel-drain-signature'\nexport * from './lib/server/vercel-log-drain'\nexport * from './lib/server/visitor-write-rate-limit'\nexport * from './lib/server/card-payment-velocity'\nexport * from './lib/server/console-api-rate-limit'\nexport * from './lib/server/realm-plugins'\nexport * from './lib/server/release-flags'\nexport * from './lib/server/resolve-people'\nexport * from './lib/server/member-photo'\nexport * from './lib/server/personal-data-export'\nexport * from './lib/server/media-delivery'\nexport * from './lib/server/media-download-tokens'\nexport * from './lib/server/media-quarantine'\nexport * from './lib/server/media-signing'\nexport * from './lib/server/media-storage-path'\nexport * from './lib/server/safe-equal'\nexport * from './lib/server/media-strong-digest'\nexport * from './lib/server/media-tombstone'\nexport * from './lib/server/media-variants'\nexport * from './lib/server/serve-media-cdn'\nexport * from './lib/server/serve-plugin-fetch'\nexport * from './lib/server/configured-url-fetch'\nexport * from './lib/server/sso-break-glass-owners'\nexport * from './lib/server/sso-domain-policy'\nexport * from './lib/server/sso-enforcement'\nexport * from './lib/server/sso-provisioning'\nexport * from './lib/server/update-existing'\nexport * from './lib/server/contact-company-link'\nexport * from './lib/server/contact-email-index'\nexport * from './lib/server/upsert-contact'\nexport * from './lib/server/crm-records'\n// The `nextTaskAtMs` a contact, a company and a deal carry, recomputed from\n// their open tasks by every server-side task writer (AGL-2661).\nexport * from './lib/server/crm-next-activity'\n// Where a one-to-one email's activity row lives and how the delivery\n// webhook advances it (AGL-2615).\nexport * from './lib/server/crm-email-activity'\n// A member's own addresses in a workspace, and the signed link that\n// confirms one (AGL-2975).\nexport * from './lib/server/member-email-aliases'\nexport * from './lib/server/user-profiles'\n// The one \"New account\" staff notice per account, keyed on the auth\n// record's creation (AGL-3225).\nexport * from './lib/server/new-account-announcement'\nexport * from './lib/server/legal-acceptance'\n// The person's decision about the platform's own product email, on their\n// document and on the operator's marketing contact (AGL-3185).\nexport * from './lib/server/platform-marketing-consent'\n"],"names":["merchantAccountIsReady","merchantAccountReadiness","paymentProvider","domainProvider"],"mappings":"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"}
@@ -1,32 +0,0 @@
1
- /**
2
- * @license
3
- * Copyright 2026 Aglyn LLC
4
- *
5
- * Licensed under the Apache License, Version 2.0 (the "License");
6
- * you may not use this file except in compliance with the License.
7
- * You may obtain a copy of the License at
8
- *
9
- * http://www.apache.org/licenses/LICENSE-2.0
10
- *
11
- * Unless required by applicable law or agreed to in writing, software
12
- * distributed under the License is distributed on an "AS IS" BASIS,
13
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
- * See the License for the specific language governing permissions and
15
- * limitations under the License.
16
- */
17
- import type { EmailDeliveryEventOutcome } from './email-delivery-log';
18
- /** The facet field the stamp lives under. */
19
- export declare const CONTACT_EMAIL_ENGAGEMENT_FIELD = "lastEmailEngagementAtMs";
20
- /**
21
- * Stamps the sending site's contact facet for every person these outcomes
22
- * say engaged for the first time with a message.
23
- *
24
- * @param hostId the site the campaign went out from — the `hostId` tag the
25
- * send stamped, which is the only tenant identity a delivery event carries.
26
- * @returns how many contact documents were written.
27
- */
28
- export declare function recordContactEmailEngagement(args: {
29
- hostId: string;
30
- outcomes: readonly EmailDeliveryEventOutcome[];
31
- firestore?: any;
32
- }): Promise<number>;