@aglyn/tenant-data-admin 1.0.0-beta.261 → 1.0.0-beta.263
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/package.json +7 -7
- package/src/lib/server/figure-readers.d.ts +44 -0
- package/src/lib/server/figure-readers.js +45 -0
- package/src/lib/server/figure-readers.js.map +1 -0
- package/src/lib/server/keep-alive.d.ts +18 -0
- package/src/lib/server/keep-alive.js +59 -0
- package/src/lib/server/keep-alive.js.map +1 -0
- package/src/lib/server/mobile-push-switch.js +17 -4
- package/src/lib/server/mobile-push-switch.js.map +1 -1
- package/src/lib/server/native-push.js +6 -3
- package/src/lib/server/native-push.js.map +1 -1
- package/src/lib/server/notifications.d.ts +16 -5
- package/src/lib/server/notifications.js +30 -9
- package/src/lib/server/notifications.js.map +1 -1
- package/src/lib/server/operator-alerts.d.ts +7 -0
- package/src/lib/server/operator-alerts.js +6 -3
- package/src/lib/server/operator-alerts.js.map +1 -1
- package/src/lib/server/operator-health.d.ts +69 -0
- package/src/lib/server/operator-health.js +87 -0
- package/src/lib/server/operator-health.js.map +1 -1
- package/src/lib/server/push-apns.js +3 -1
- package/src/lib/server/push-apns.js.map +1 -1
- package/src/lib/server/push-delivery.d.ts +9 -0
- package/src/lib/server/push-delivery.js +10 -0
- package/src/lib/server/push-delivery.js.map +1 -1
- package/src/lib/server/render-system-email.d.ts +7 -1
- package/src/lib/server/render-system-email.js +1 -1
- package/src/lib/server/render-system-email.js.map +1 -1
- package/src/lib/server/sso-domain-policy.d.ts +43 -7
- package/src/lib/server/sso-domain-policy.js +61 -9
- package/src/lib/server/sso-domain-policy.js.map +1 -1
- package/src/lib/server/weekly-summary.d.ts +160 -0
- package/src/lib/server/weekly-summary.js +568 -0
- package/src/lib/server/weekly-summary.js.map +1 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aglyn/tenant-data-admin",
|
|
3
|
-
"version": "1.0.0-beta.
|
|
3
|
+
"version": "1.0.0-beta.263",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"homepage": "https://aglyn.com",
|
|
6
6
|
"repository": {
|
|
@@ -25,12 +25,12 @@
|
|
|
25
25
|
"./package.json": "./package.json"
|
|
26
26
|
},
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"@aglyn/aglyn": "1.0.0-beta.
|
|
29
|
-
"@aglyn/shared-util-email": "1.0.0-beta.
|
|
30
|
-
"@aglyn/shared-util-fbserver": "1.0.0-beta.
|
|
31
|
-
"@aglyn/shared-util-first-touch": "1.0.0-beta.
|
|
32
|
-
"@aglyn/shared-util-http": "1.0.0-beta.
|
|
33
|
-
"@aglyn/shared-util-tools": "1.0.0-beta.
|
|
28
|
+
"@aglyn/aglyn": "1.0.0-beta.263",
|
|
29
|
+
"@aglyn/shared-util-email": "1.0.0-beta.263",
|
|
30
|
+
"@aglyn/shared-util-fbserver": "1.0.0-beta.263",
|
|
31
|
+
"@aglyn/shared-util-first-touch": "1.0.0-beta.263",
|
|
32
|
+
"@aglyn/shared-util-http": "1.0.0-beta.263",
|
|
33
|
+
"@aglyn/shared-util-tools": "1.0.0-beta.263",
|
|
34
34
|
"@msgpack/msgpack": "^3.1.3",
|
|
35
35
|
"@swc/helpers": "0.5.23",
|
|
36
36
|
"sharp": "^0.35.5",
|
|
@@ -0,0 +1,44 @@
|
|
|
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 PluginFigureReader, type ResolvedPluginFigureReader } from '@aglyn/aglyn/plugin-manager/plugin-figures';
|
|
18
|
+
/**
|
|
19
|
+
* The figure readers a caller may read for one workspace or one site
|
|
20
|
+
* (AGL-2915): the caller's half of the `plugin-figures` contract.
|
|
21
|
+
*
|
|
22
|
+
* The registry carries no authorization, so before a reader is read this
|
|
23
|
+
* establishes that the workspace's plan includes the feature the figures are
|
|
24
|
+
* sold under, and that the plugin whose records they are runs here: past its
|
|
25
|
+
* release flag, on for the workspace and not switched off for the site. The
|
|
26
|
+
* site belonging to the workspace, and the person's reach, are the caller's
|
|
27
|
+
* own checks.
|
|
28
|
+
*/
|
|
29
|
+
/** Which of a set of plugins are past their release flags for an org; the platform's flags by default. */
|
|
30
|
+
export type ReleasedFigurePlugins = (pluginIds: string[], orgId: string) => Promise<string[]>;
|
|
31
|
+
export interface FigureReaderContext {
|
|
32
|
+
orgId: string;
|
|
33
|
+
/** The site the figures are about; `null` for the workspace as a whole. */
|
|
34
|
+
hostId: string | null;
|
|
35
|
+
/** The org document. */
|
|
36
|
+
org: object | null;
|
|
37
|
+
/** The site's host document, already checked to belong to the org; `null` with no site. */
|
|
38
|
+
host: Record<string, unknown> | null;
|
|
39
|
+
/** Which readers the caller asks about at all, before the gates. */
|
|
40
|
+
accept: (reader: PluginFigureReader) => boolean;
|
|
41
|
+
released?: ReleasedFigurePlugins;
|
|
42
|
+
}
|
|
43
|
+
/** The readers that pass the gates, ordered by id so a list of them is the same on every call. */
|
|
44
|
+
export declare function eligibleFigureReaders(context: FigureReaderContext): Promise<ResolvedPluginFigureReader[]>;
|
|
@@ -0,0 +1,45 @@
|
|
|
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
|
+
*/ import { checkEntitlement } from "@aglyn/aglyn/app-utils/plan-entitlements";
|
|
17
|
+
import { isHostPluginEnabled, isPluginEnabled } from "@aglyn/aglyn/plugin-manager/enabled-plugins";
|
|
18
|
+
import { listPluginFigureReaders, pluginFigureReaderPlugin } from "@aglyn/aglyn/plugin-manager/plugin-figures";
|
|
19
|
+
import { filterEnabledPluginsByReleaseFlags } from "./release-flags.js";
|
|
20
|
+
const releasedByFlags = (pluginIds, orgId)=>filterEnabledPluginsByReleaseFlags(pluginIds, {
|
|
21
|
+
orgId,
|
|
22
|
+
authorization: null
|
|
23
|
+
});
|
|
24
|
+
/** The readers that pass the gates, ordered by id so a list of them is the same on every call. */ export async function eligibleFigureReaders(context) {
|
|
25
|
+
var _context_released;
|
|
26
|
+
const org = context.org;
|
|
27
|
+
const candidates = listPluginFigureReaders().filter(({ reader })=>{
|
|
28
|
+
if (!context.accept(reader)) return false;
|
|
29
|
+
// A site reader needs a site; a workspace reader reads with or without one.
|
|
30
|
+
if (reader.scope === 'site' && !context.hostId) return false;
|
|
31
|
+
return !reader.feature || checkEntitlement(org, reader.feature);
|
|
32
|
+
});
|
|
33
|
+
const plugins = [
|
|
34
|
+
...new Set(candidates.map(pluginFigureReaderPlugin).filter((id)=>Boolean(id)))
|
|
35
|
+
];
|
|
36
|
+
const released = new Set(plugins.length ? await ((_context_released = context.released) != null ? _context_released : releasedByFlags)(plugins, context.orgId) : []);
|
|
37
|
+
return candidates.filter((resolved)=>{
|
|
38
|
+
const plugin = pluginFigureReaderPlugin(resolved);
|
|
39
|
+
if (!plugin) return true;
|
|
40
|
+
if (!released.has(plugin)) return false;
|
|
41
|
+
return context.hostId ? isHostPluginEnabled(org, context.host, plugin) : isPluginEnabled(org, plugin);
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
//# sourceMappingURL=figure-readers.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../../../libs/tenant/data/admin/src/lib/server/figure-readers.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\nimport { checkEntitlement } from '@aglyn/aglyn/app-utils/plan-entitlements'\nimport { isHostPluginEnabled, isPluginEnabled } from '@aglyn/aglyn/plugin-manager/enabled-plugins'\nimport {\n listPluginFigureReaders,\n pluginFigureReaderPlugin,\n type PluginFigureReader,\n type ResolvedPluginFigureReader,\n} from '@aglyn/aglyn/plugin-manager/plugin-figures'\nimport { filterEnabledPluginsByReleaseFlags } from './release-flags'\n\n/**\n * The figure readers a caller may read for one workspace or one site\n * (AGL-2915): the caller's half of the `plugin-figures` contract.\n *\n * The registry carries no authorization, so before a reader is read this\n * establishes that the workspace's plan includes the feature the figures are\n * sold under, and that the plugin whose records they are runs here: past its\n * release flag, on for the workspace and not switched off for the site. The\n * site belonging to the workspace, and the person's reach, are the caller's\n * own checks.\n */\n\n/** Which of a set of plugins are past their release flags for an org; the platform's flags by default. */\nexport type ReleasedFigurePlugins = (pluginIds: string[], orgId: string) => Promise<string[]>\n\nconst releasedByFlags: ReleasedFigurePlugins = (pluginIds, orgId) =>\n filterEnabledPluginsByReleaseFlags(pluginIds, { orgId, authorization: null })\n\nexport interface FigureReaderContext {\n orgId: string\n /** The site the figures are about; `null` for the workspace as a whole. */\n hostId: string | null\n /** The org document. */\n org: object | null\n /** The site's host document, already checked to belong to the org; `null` with no site. */\n host: Record<string, unknown> | null\n /** Which readers the caller asks about at all, before the gates. */\n accept: (reader: PluginFigureReader) => boolean\n released?: ReleasedFigurePlugins\n}\n\n/** The readers that pass the gates, ordered by id so a list of them is the same on every call. */\nexport async function eligibleFigureReaders(\n context: FigureReaderContext,\n): Promise<ResolvedPluginFigureReader[]> {\n const org = context.org as { enabledPlugins?: string[] } | null\n const candidates = listPluginFigureReaders().filter(({ reader }) => {\n if (!context.accept(reader)) return false\n // A site reader needs a site; a workspace reader reads with or without one.\n if (reader.scope === 'site' && !context.hostId) return false\n return !reader.feature || checkEntitlement(org as never, reader.feature)\n })\n const plugins = [\n ...new Set(candidates.map(pluginFigureReaderPlugin).filter((id): id is string => Boolean(id))),\n ]\n const released = new Set(\n plugins.length ? await (context.released ?? releasedByFlags)(plugins, context.orgId) : [],\n )\n return candidates.filter((resolved) => {\n const plugin = pluginFigureReaderPlugin(resolved)\n if (!plugin) return true\n if (!released.has(plugin)) return false\n return context.hostId\n ? isHostPluginEnabled(org, context.host as { disabledPlugins?: string[] } | null, plugin)\n : isPluginEnabled(org, plugin)\n })\n}\n"],"names":["checkEntitlement","isHostPluginEnabled","isPluginEnabled","listPluginFigureReaders","pluginFigureReaderPlugin","filterEnabledPluginsByReleaseFlags","releasedByFlags","pluginIds","orgId","authorization","eligibleFigureReaders","context","org","candidates","filter","reader","accept","scope","hostId","feature","plugins","Set","map","id","Boolean","released","length","resolved","plugin","has","host"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED,SAASA,gBAAgB,QAAQ,2CAA0C;AAC3E,SAASC,mBAAmB,EAAEC,eAAe,QAAQ,8CAA6C;AAClG,SACEC,uBAAuB,EACvBC,wBAAwB,QAGnB,6CAA4C;AACnD,SAASC,kCAAkC,QAAQ,qBAAiB;AAiBpE,MAAMC,kBAAyC,CAACC,WAAWC,QACzDH,mCAAmCE,WAAW;QAAEC;QAAOC,eAAe;IAAK;AAe7E,gGAAgG,GAChG,OAAO,eAAeC,sBACpBC,OAA4B;QAaFA;IAX1B,MAAMC,MAAMD,QAAQC,GAAG;IACvB,MAAMC,aAAaV,0BAA0BW,MAAM,CAAC,CAAC,EAAEC,MAAM,EAAE;QAC7D,IAAI,CAACJ,QAAQK,MAAM,CAACD,SAAS,OAAO;QACpC,4EAA4E;QAC5E,IAAIA,OAAOE,KAAK,KAAK,UAAU,CAACN,QAAQO,MAAM,EAAE,OAAO;QACvD,OAAO,CAACH,OAAOI,OAAO,IAAInB,iBAAiBY,KAAcG,OAAOI,OAAO;IACzE;IACA,MAAMC,UAAU;WACX,IAAIC,IAAIR,WAAWS,GAAG,CAAClB,0BAA0BU,MAAM,CAAC,CAACS,KAAqBC,QAAQD;KAC1F;IACD,MAAME,WAAW,IAAIJ,IACnBD,QAAQM,MAAM,GAAG,MAAM,EAACf,oBAAAA,QAAQc,QAAQ,YAAhBd,oBAAoBL,iBAAiBc,SAAST,QAAQH,KAAK,IAAI,EAAE;IAE3F,OAAOK,WAAWC,MAAM,CAAC,CAACa;QACxB,MAAMC,SAASxB,yBAAyBuB;QACxC,IAAI,CAACC,QAAQ,OAAO;QACpB,IAAI,CAACH,SAASI,GAAG,CAACD,SAAS,OAAO;QAClC,OAAOjB,QAAQO,MAAM,GACjBjB,oBAAoBW,KAAKD,QAAQmB,IAAI,EAA2CF,UAChF1B,gBAAgBU,KAAKgB;IAC3B;AACF"}
|
|
@@ -0,0 +1,18 @@
|
|
|
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
|
+
/** Returns `work` unchanged, after asking the platform to wait for it. */
|
|
18
|
+
export declare function keepAlive<T>(work: Promise<T>): Promise<T>;
|
|
@@ -0,0 +1,59 @@
|
|
|
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
|
+
* Keeps fire-and-forget work alive after the response is sent.
|
|
18
|
+
*
|
|
19
|
+
* A serverless function may be frozen the moment its handler returns, and a
|
|
20
|
+
* notification is usually started with `void notify…()` beside the mutation
|
|
21
|
+
* it reports. The feed write lands first, so the console shows it, while the
|
|
22
|
+
* push and the email after it are cut off part way: the native apps then miss
|
|
23
|
+
* notifications the browser shows. Handing the promise to the platform's
|
|
24
|
+
* `waitUntil` keeps the invocation alive until it settles.
|
|
25
|
+
*
|
|
26
|
+
* Next reads `waitUntil` from the `@next/request-context` slot, and Vercel's
|
|
27
|
+
* own helpers read `@vercel/request-context`; both are asked. Outside a
|
|
28
|
+
* request (a script, a test, a cron worker that awaits anyway) there is no
|
|
29
|
+
* context and the work simply runs as it would have.
|
|
30
|
+
*/ const CONTEXT_SLOTS = [
|
|
31
|
+
Symbol.for('@next/request-context'),
|
|
32
|
+
Symbol.for('@vercel/request-context')
|
|
33
|
+
];
|
|
34
|
+
function platformWaitUntil() {
|
|
35
|
+
const scope = globalThis;
|
|
36
|
+
for (const slot of CONTEXT_SLOTS){
|
|
37
|
+
try {
|
|
38
|
+
var _scope_slot_get, _scope_slot_get1, _scope_slot;
|
|
39
|
+
const waitUntil = (_scope_slot = scope[slot]) == null ? void 0 : (_scope_slot_get1 = _scope_slot.get) == null ? void 0 : (_scope_slot_get = _scope_slot_get1.call(_scope_slot)) == null ? void 0 : _scope_slot_get.waitUntil;
|
|
40
|
+
if (typeof waitUntil === 'function') return waitUntil;
|
|
41
|
+
} catch (unused) {
|
|
42
|
+
// A slot that cannot answer is treated as absent.
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return undefined;
|
|
46
|
+
}
|
|
47
|
+
/** Returns `work` unchanged, after asking the platform to wait for it. */ export function keepAlive(work) {
|
|
48
|
+
const waitUntil = platformWaitUntil();
|
|
49
|
+
if (waitUntil) {
|
|
50
|
+
try {
|
|
51
|
+
waitUntil(work.catch(()=>undefined));
|
|
52
|
+
} catch (unused) {
|
|
53
|
+
// The platform refused the promise: the work still runs.
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return work;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
//# sourceMappingURL=keep-alive.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../../../libs/tenant/data/admin/src/lib/server/keep-alive.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 * Keeps fire-and-forget work alive after the response is sent.\n *\n * A serverless function may be frozen the moment its handler returns, and a\n * notification is usually started with `void notify…()` beside the mutation\n * it reports. The feed write lands first, so the console shows it, while the\n * push and the email after it are cut off part way: the native apps then miss\n * notifications the browser shows. Handing the promise to the platform's\n * `waitUntil` keeps the invocation alive until it settles.\n *\n * Next reads `waitUntil` from the `@next/request-context` slot, and Vercel's\n * own helpers read `@vercel/request-context`; both are asked. Outside a\n * request (a script, a test, a cron worker that awaits anyway) there is no\n * context and the work simply runs as it would have.\n */\n\ntype WaitUntil = (promise: Promise<unknown>) => void\n\ninterface RequestContextSlot {\n get?: () => { waitUntil?: WaitUntil } | undefined\n}\n\nconst CONTEXT_SLOTS = [Symbol.for('@next/request-context'), Symbol.for('@vercel/request-context')]\n\nfunction platformWaitUntil(): WaitUntil | undefined {\n const scope = globalThis as unknown as Record<symbol, RequestContextSlot | undefined>\n for (const slot of CONTEXT_SLOTS) {\n try {\n const waitUntil = scope[slot]?.get?.()?.waitUntil\n if (typeof waitUntil === 'function') return waitUntil\n } catch {\n // A slot that cannot answer is treated as absent.\n }\n }\n return undefined\n}\n\n/** Returns `work` unchanged, after asking the platform to wait for it. */\nexport function keepAlive<T>(work: Promise<T>): Promise<T> {\n const waitUntil = platformWaitUntil()\n if (waitUntil) {\n try {\n waitUntil(work.catch(() => undefined))\n } catch {\n // The platform refused the promise: the work still runs.\n }\n }\n return work\n}\n"],"names":["CONTEXT_SLOTS","Symbol","for","platformWaitUntil","scope","globalThis","slot","waitUntil","get","undefined","keepAlive","work","catch"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED;;;;;;;;;;;;;;CAcC,GAQD,MAAMA,gBAAgB;IAACC,OAAOC,GAAG,CAAC;IAA0BD,OAAOC,GAAG,CAAC;CAA2B;AAElG,SAASC;IACP,MAAMC,QAAQC;IACd,KAAK,MAAMC,QAAQN,cAAe;QAChC,IAAI;gBACgBI,iBAAAA,kBAAAA;YAAlB,MAAMG,aAAYH,cAAAA,KAAK,CAACE,KAAK,sBAAXF,mBAAAA,YAAaI,GAAG,sBAAhBJ,kBAAAA,sBAAAA,iCAAAA,gBAAsBG,SAAS;YACjD,IAAI,OAAOA,cAAc,YAAY,OAAOA;QAC9C,EAAE,eAAM;QACN,kDAAkD;QACpD;IACF;IACA,OAAOE;AACT;AAEA,wEAAwE,GACxE,OAAO,SAASC,UAAaC,IAAgB;IAC3C,MAAMJ,YAAYJ;IAClB,IAAII,WAAW;QACb,IAAI;YACFA,UAAUI,KAAKC,KAAK,CAAC,IAAMH;QAC7B,EAAE,eAAM;QACN,yDAAyD;QAC3D;IACF;IACA,OAAOE;AACT"}
|
|
@@ -24,21 +24,34 @@
|
|
|
24
24
|
var _env_MOBILE_PUSH_ENABLED;
|
|
25
25
|
return ((_env_MOBILE_PUSH_ENABLED = env['MOBILE_PUSH_ENABLED']) != null ? _env_MOBILE_PUSH_ENABLED : '').trim() !== '0';
|
|
26
26
|
}
|
|
27
|
-
|
|
27
|
+
/*
|
|
28
|
+
* The registry lives on `globalThis`, not in this module: `instrumentation.ts`
|
|
29
|
+
* registers the senders in its own copy of this module, and a route handler's
|
|
30
|
+
* `notifyUsers` reads another one (AGL-3412). A module-scoped set is empty
|
|
31
|
+
* where it is read, and every push is silently skipped.
|
|
32
|
+
*/ const SENDERS_KEY = Symbol.for('@aglyn/tenant-data-admin:mobile-push-senders');
|
|
33
|
+
const globalScope = globalThis;
|
|
34
|
+
function senders() {
|
|
35
|
+
const existing = globalScope[SENDERS_KEY];
|
|
36
|
+
if (existing) return existing;
|
|
37
|
+
const created = new Set();
|
|
38
|
+
globalScope[SENDERS_KEY] = created;
|
|
39
|
+
return created;
|
|
40
|
+
}
|
|
28
41
|
/**
|
|
29
42
|
* Adds a push transport to the fan-out. `notifyUsers` hands every registered
|
|
30
43
|
* sender the recipients whose preferences say push, after the feed entries
|
|
31
44
|
* are committed and only while the kill switch is up. With none registered,
|
|
32
45
|
* nothing is pushed. Returns the unregister function.
|
|
33
46
|
*/ export function registerMobilePushSender(sender) {
|
|
34
|
-
senders.add(sender);
|
|
47
|
+
senders().add(sender);
|
|
35
48
|
return ()=>{
|
|
36
|
-
senders.delete(sender);
|
|
49
|
+
senders().delete(sender);
|
|
37
50
|
};
|
|
38
51
|
}
|
|
39
52
|
/** The registered push transports, in registration order. */ export function mobilePushSenders() {
|
|
40
53
|
return [
|
|
41
|
-
...senders
|
|
54
|
+
...senders()
|
|
42
55
|
];
|
|
43
56
|
}
|
|
44
57
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../../libs/tenant/data/admin/src/lib/server/mobile-push-switch.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\nimport type { AglynNotification } from '@aglyn/aglyn/server'\nimport type { Firestore } from 'firebase-admin/firestore'\n\n/**\n * Whether this deployment sends mobile push (AGL-3648).\n *\n * On unless `MOBILE_PUSH_ENABLED=0`: the variable is a kill switch, not an\n * opt-in, and it governs every push sender. Kept apart from the senders so\n * `notifyUsers` can ask before it calls one, and every caller reads the same\n * answer.\n */\nexport function mobilePushEnabled(env: Record<string, string | undefined> = process.env): boolean {\n return (env['MOBILE_PUSH_ENABLED'] ?? '').trim() !== '0'\n}\n\n/** The notification a push sender is handed, as the fan-out wrote it. */\nexport type MobilePushPayload = Omit<AglynNotification, '$id' | 'createdAt' | 'readAt'>\n\n/**\n * A push transport (APNs, FCM): sends one notification to the registered\n * devices (`users/{uid}/devices`) of `uids`. Must not throw on a delivery\n * failure; the console notification is the record and a push is a courtesy.\n */\nexport type MobilePushSender = (\n uids: readonly string[],\n payload: MobilePushPayload,\n context: { db: Firestore },\n) => Promise<void>\n\nconst senders = new Set<MobilePushSender>()\n\n/**\n * Adds a push transport to the fan-out. `notifyUsers` hands every registered\n * sender the recipients whose preferences say push, after the feed entries\n * are committed and only while the kill switch is up. With none registered,\n * nothing is pushed. Returns the unregister function.\n */\nexport function registerMobilePushSender(sender: MobilePushSender): () => void {\n senders.add(sender)\n return () => {\n senders.delete(sender)\n }\n}\n\n/** The registered push transports, in registration order. */\nexport function mobilePushSenders(): MobilePushSender[] {\n return [...senders]\n}\n"],"names":["mobilePushEnabled","env","process","trim","senders","Set","registerMobilePushSender","sender","add","delete","mobilePushSenders"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAKD;;;;;;;CAOC,GACD,OAAO,SAASA,kBAAkBC,MAA0CC,QAAQD,GAAG;QAC7EA;IAAR,OAAO,EAACA,2BAAAA,GAAG,CAAC,sBAAsB,YAA1BA,2BAA8B,IAAIE,IAAI,OAAO;AACvD;AAgBA,MAAMC,UAAU,IAAIC;
|
|
1
|
+
{"version":3,"sources":["../../../../../../../../libs/tenant/data/admin/src/lib/server/mobile-push-switch.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\nimport type { AglynNotification } from '@aglyn/aglyn/server'\nimport type { Firestore } from 'firebase-admin/firestore'\n\n/**\n * Whether this deployment sends mobile push (AGL-3648).\n *\n * On unless `MOBILE_PUSH_ENABLED=0`: the variable is a kill switch, not an\n * opt-in, and it governs every push sender. Kept apart from the senders so\n * `notifyUsers` can ask before it calls one, and every caller reads the same\n * answer.\n */\nexport function mobilePushEnabled(env: Record<string, string | undefined> = process.env): boolean {\n return (env['MOBILE_PUSH_ENABLED'] ?? '').trim() !== '0'\n}\n\n/** The notification a push sender is handed, as the fan-out wrote it. */\nexport type MobilePushPayload = Omit<AglynNotification, '$id' | 'createdAt' | 'readAt'>\n\n/**\n * A push transport (APNs, FCM): sends one notification to the registered\n * devices (`users/{uid}/devices`) of `uids`. Must not throw on a delivery\n * failure; the console notification is the record and a push is a courtesy.\n */\nexport type MobilePushSender = (\n uids: readonly string[],\n payload: MobilePushPayload,\n context: { db: Firestore },\n) => Promise<void>\n\n/*\n * The registry lives on `globalThis`, not in this module: `instrumentation.ts`\n * registers the senders in its own copy of this module, and a route handler's\n * `notifyUsers` reads another one (AGL-3412). A module-scoped set is empty\n * where it is read, and every push is silently skipped.\n */\nconst SENDERS_KEY = Symbol.for('@aglyn/tenant-data-admin:mobile-push-senders')\n\nconst globalScope = globalThis as typeof globalThis & {\n [SENDERS_KEY]?: Set<MobilePushSender>\n}\n\nfunction senders(): Set<MobilePushSender> {\n const existing = globalScope[SENDERS_KEY]\n if (existing) return existing\n const created = new Set<MobilePushSender>()\n globalScope[SENDERS_KEY] = created\n return created\n}\n\n/**\n * Adds a push transport to the fan-out. `notifyUsers` hands every registered\n * sender the recipients whose preferences say push, after the feed entries\n * are committed and only while the kill switch is up. With none registered,\n * nothing is pushed. Returns the unregister function.\n */\nexport function registerMobilePushSender(sender: MobilePushSender): () => void {\n senders().add(sender)\n return () => {\n senders().delete(sender)\n }\n}\n\n/** The registered push transports, in registration order. */\nexport function mobilePushSenders(): MobilePushSender[] {\n return [...senders()]\n}\n"],"names":["mobilePushEnabled","env","process","trim","SENDERS_KEY","Symbol","for","globalScope","globalThis","senders","existing","created","Set","registerMobilePushSender","sender","add","delete","mobilePushSenders"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAKD;;;;;;;CAOC,GACD,OAAO,SAASA,kBAAkBC,MAA0CC,QAAQD,GAAG;QAC7EA;IAAR,OAAO,EAACA,2BAAAA,GAAG,CAAC,sBAAsB,YAA1BA,2BAA8B,IAAIE,IAAI,OAAO;AACvD;AAgBA;;;;;CAKC,GACD,MAAMC,cAAcC,OAAOC,GAAG,CAAC;AAE/B,MAAMC,cAAcC;AAIpB,SAASC;IACP,MAAMC,WAAWH,WAAW,CAACH,YAAY;IACzC,IAAIM,UAAU,OAAOA;IACrB,MAAMC,UAAU,IAAIC;IACpBL,WAAW,CAACH,YAAY,GAAGO;IAC3B,OAAOA;AACT;AAEA;;;;;CAKC,GACD,OAAO,SAASE,yBAAyBC,MAAwB;IAC/DL,UAAUM,GAAG,CAACD;IACd,OAAO;QACLL,UAAUO,MAAM,CAACF;IACnB;AACF;AAEA,2DAA2D,GAC3D,OAAO,SAASG;IACd,OAAO;WAAIR;KAAU;AACvB"}
|
|
@@ -22,10 +22,13 @@
|
|
|
22
22
|
* `push-delivery.ts`, and through it each transport, only when a
|
|
23
23
|
* notification has push recipients.
|
|
24
24
|
*/ import { registerMobilePushSender } from "./mobile-push-switch.js";
|
|
25
|
-
|
|
25
|
+
// On `globalThis` for the same reason as the registry itself: a second
|
|
26
|
+
// module copy must not register a second sender and push everything twice.
|
|
27
|
+
const REGISTERED_KEY = Symbol.for('@aglyn/tenant-data-admin:native-push-registered');
|
|
28
|
+
const globalScope = globalThis;
|
|
26
29
|
export function registerNativePushSenders() {
|
|
27
|
-
if (
|
|
28
|
-
|
|
30
|
+
if (globalScope[REGISTERED_KEY]) return;
|
|
31
|
+
globalScope[REGISTERED_KEY] = true;
|
|
29
32
|
registerMobilePushSender(async (uids, payload, context)=>{
|
|
30
33
|
const { nativePushSender } = await import("./push-delivery.js");
|
|
31
34
|
await nativePushSender(uids, payload, context);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../../libs/tenant/data/admin/src/lib/server/native-push.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 * Registers the native apps' push delivery (APNs and FCM, AGL-3651) with the\n * fan-out, once per server instance. A deployment's server entry calls it at\n * boot (the console's and the tenant's `instrumentation.ts`).\n *\n * This file imports nothing heavy: the sender it registers imports\n * `push-delivery.ts`, and through it each transport, only when a\n * notification has push recipients.\n */\n\nimport { registerMobilePushSender } from './mobile-push-switch'\n\
|
|
1
|
+
{"version":3,"sources":["../../../../../../../../libs/tenant/data/admin/src/lib/server/native-push.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 * Registers the native apps' push delivery (APNs and FCM, AGL-3651) with the\n * fan-out, once per server instance. A deployment's server entry calls it at\n * boot (the console's and the tenant's `instrumentation.ts`).\n *\n * This file imports nothing heavy: the sender it registers imports\n * `push-delivery.ts`, and through it each transport, only when a\n * notification has push recipients.\n */\n\nimport { registerMobilePushSender } from './mobile-push-switch'\n\n// On `globalThis` for the same reason as the registry itself: a second\n// module copy must not register a second sender and push everything twice.\nconst REGISTERED_KEY = Symbol.for('@aglyn/tenant-data-admin:native-push-registered')\n\nconst globalScope = globalThis as typeof globalThis & { [REGISTERED_KEY]?: true }\n\nexport function registerNativePushSenders(): void {\n if (globalScope[REGISTERED_KEY]) return\n globalScope[REGISTERED_KEY] = true\n registerMobilePushSender(async (uids, payload, context) => {\n const { nativePushSender } = await import('./push-delivery')\n await nativePushSender(uids, payload, context)\n })\n}\n"],"names":["registerMobilePushSender","REGISTERED_KEY","Symbol","for","globalScope","globalThis","registerNativePushSenders","uids","payload","context","nativePushSender"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED;;;;;;;;CAQC,GAED,SAASA,wBAAwB,QAAQ,0BAAsB;AAE/D,uEAAuE;AACvE,2EAA2E;AAC3E,MAAMC,iBAAiBC,OAAOC,GAAG,CAAC;AAElC,MAAMC,cAAcC;AAEpB,OAAO,SAASC;IACd,IAAIF,WAAW,CAACH,eAAe,EAAE;IACjCG,WAAW,CAACH,eAAe,GAAG;IAC9BD,yBAAyB,OAAOO,MAAMC,SAASC;QAC7C,MAAM,EAAEC,gBAAgB,EAAE,GAAG,MAAM,MAAM,CAAC;QAC1C,MAAMA,iBAAiBH,MAAMC,SAASC;IACxC;AACF"}
|
|
@@ -77,7 +77,7 @@ export declare function formatNotificationEmailDetails(details: readonly Notific
|
|
|
77
77
|
* (AGL-3224) — sends one email beside it. Never throws: a notification miss,
|
|
78
78
|
* or a send that failed, must not break the mutation that emitted it.
|
|
79
79
|
*/
|
|
80
|
-
|
|
80
|
+
declare function notifyUsersNow(uids: Iterable<string>, payload: NotificationPayload, options?: NotifyUsersOptions): Promise<void>;
|
|
81
81
|
/**
|
|
82
82
|
* Notifies every staff-claim holder (AGL-850) — the support-desk audience.
|
|
83
83
|
* Staff are not org members, so `notifyOrgAdmins`/`notifyHostManagers` never
|
|
@@ -90,13 +90,13 @@ export declare function notifyUsers(uids: Iterable<string>, payload: Notificatio
|
|
|
90
90
|
* instead: the same console notification, plus staff's switches, the
|
|
91
91
|
* operator email or digest, and the webhook. See `raiseOperatorAlert`.
|
|
92
92
|
*/
|
|
93
|
-
|
|
93
|
+
declare function notifyStaffNow(payload: NotificationPayload): Promise<void>;
|
|
94
94
|
/**
|
|
95
95
|
* The staff console notification alone, with the per-person email channel
|
|
96
96
|
* it carries: what `notifyStaff` did before the registry, and what
|
|
97
97
|
* `raiseOperatorAlert` writes as its first step. Never throws.
|
|
98
98
|
*/
|
|
99
|
-
|
|
99
|
+
declare function notifyStaffConsoleNow(payload: NotificationPayload, options?: {
|
|
100
100
|
skipEmail?: boolean;
|
|
101
101
|
}): Promise<void>;
|
|
102
102
|
/**
|
|
@@ -106,7 +106,7 @@ export declare function notifyStaffConsole(payload: NotificationPayload, options
|
|
|
106
106
|
* itself — the usage alerts do (AGL-3431) — so an admin who switched the
|
|
107
107
|
* email channel on is not sent it twice.
|
|
108
108
|
*/
|
|
109
|
-
|
|
109
|
+
declare function notifyOrgAdminsNow(orgId: string, payload: NotificationPayload, options?: Pick<NotifyUsersOptions, 'skipEmail'>): Promise<void>;
|
|
110
110
|
/**
|
|
111
111
|
* The name a site's managers know it by: the name they gave it, then its
|
|
112
112
|
* address, then — for a doc with neither — its id.
|
|
@@ -141,7 +141,7 @@ export declare function withSiteName(text: string, site: string): string;
|
|
|
141
141
|
* read. Spread conditionally: the field is optional on the host model and
|
|
142
142
|
* Firestore rejects `undefined`.
|
|
143
143
|
*/
|
|
144
|
-
|
|
144
|
+
declare function notifyHostManagersNow(hostId: string, payload: NotificationPayload, options?: {
|
|
145
145
|
/**
|
|
146
146
|
* Mail the workspace owner nothing through the email channel, because
|
|
147
147
|
* the caller sends them their own email about the same event.
|
|
@@ -150,3 +150,14 @@ export declare function notifyHostManagers(hostId: string, payload: Notification
|
|
|
150
150
|
/** What the email alone carries; see {@link NotifyUsersOptions.emailDetails}. */
|
|
151
151
|
emailDetails?: readonly NotificationEmailDetail[];
|
|
152
152
|
}): Promise<void>;
|
|
153
|
+
/** {@link notifyUsersNow}, kept alive past the response. */
|
|
154
|
+
export declare function notifyUsers(...args: Parameters<typeof notifyUsersNow>): Promise<void>;
|
|
155
|
+
/** {@link notifyStaffNow}, kept alive past the response. */
|
|
156
|
+
export declare function notifyStaff(...args: Parameters<typeof notifyStaffNow>): Promise<void>;
|
|
157
|
+
/** {@link notifyStaffConsoleNow}, kept alive past the response. */
|
|
158
|
+
export declare function notifyStaffConsole(...args: Parameters<typeof notifyStaffConsoleNow>): Promise<void>;
|
|
159
|
+
/** {@link notifyOrgAdminsNow}, kept alive past the response. */
|
|
160
|
+
export declare function notifyOrgAdmins(...args: Parameters<typeof notifyOrgAdminsNow>): Promise<void>;
|
|
161
|
+
/** {@link notifyHostManagersNow}, kept alive past the response. */
|
|
162
|
+
export declare function notifyHostManagers(...args: Parameters<typeof notifyHostManagersNow>): Promise<void>;
|
|
163
|
+
export {};
|
|
@@ -25,6 +25,7 @@ import { findUserByUidAcrossPools, listStaffUidsAcrossPools } from "./auth-pools
|
|
|
25
25
|
import { filterSuppressedEmails } from "./email-suppression.js";
|
|
26
26
|
import { meterOrgEmail, meterPlatformEmail } from "./email-metering.js";
|
|
27
27
|
import firebaseAdmin from "./firebase-admin.js";
|
|
28
|
+
import { keepAlive } from "./keep-alive.js";
|
|
28
29
|
import { mobilePushEnabled, mobilePushSenders } from "./mobile-push-switch.js";
|
|
29
30
|
import { listOrgMembers } from "./organizations.js";
|
|
30
31
|
import { loadSystemEmail, renderLoadedSystemEmail, systemEmailBrand, systemEmailContent } from "./render-system-email.js";
|
|
@@ -47,10 +48,10 @@ const firestore = ()=>firebaseAdmin.app().firestore();
|
|
|
47
48
|
* At most this many directory lookups per fan-out, and at most this many
|
|
48
49
|
* notification emails.
|
|
49
50
|
*
|
|
50
|
-
* Both ceilings only
|
|
51
|
-
*
|
|
52
|
-
*
|
|
53
|
-
*
|
|
51
|
+
* Both ceilings bite only on a fan-out where many recipients are emailed —
|
|
52
|
+
* the email channel defaults off outside the types in
|
|
53
|
+
* `NOTIFICATION_TYPE_CHANNEL_DEFAULTS`, and every fan-out goes to one
|
|
54
|
+
* workspace's or one site's people, or to staff. They are here because a
|
|
54
55
|
* fan-out takes up to 400 uids and `notifyUsers` runs inside the mutation
|
|
55
56
|
* that emitted it: a notification must not be able to turn one write into
|
|
56
57
|
* four hundred directory reads and four hundred sends.
|
|
@@ -199,7 +200,7 @@ const NOTIFY_EMAIL_MAX_SENDS = 50;
|
|
|
199
200
|
* `users/{uid}/notifications`, and — for recipients who asked for it
|
|
200
201
|
* (AGL-3224) — sends one email beside it. Never throws: a notification miss,
|
|
201
202
|
* or a send that failed, must not break the mutation that emitted it.
|
|
202
|
-
*/
|
|
203
|
+
*/ async function notifyUsersNow(uids, payload, options = {}) {
|
|
203
204
|
try {
|
|
204
205
|
const db = firestore();
|
|
205
206
|
const targets = [
|
|
@@ -315,7 +316,7 @@ async function listStaffUids() {
|
|
|
315
316
|
* alert, a dispute nobody owns, a support ticket — is raised as that alert
|
|
316
317
|
* instead: the same console notification, plus staff's switches, the
|
|
317
318
|
* operator email or digest, and the webhook. See `raiseOperatorAlert`.
|
|
318
|
-
*/
|
|
319
|
+
*/ async function notifyStaffNow(payload) {
|
|
319
320
|
try {
|
|
320
321
|
const alert = operatorAlertForNotificationType(payload.type);
|
|
321
322
|
if (alert) {
|
|
@@ -344,7 +345,7 @@ async function listStaffUids() {
|
|
|
344
345
|
* The staff console notification alone, with the per-person email channel
|
|
345
346
|
* it carries: what `notifyStaff` did before the registry, and what
|
|
346
347
|
* `raiseOperatorAlert` writes as its first step. Never throws.
|
|
347
|
-
*/
|
|
348
|
+
*/ async function notifyStaffConsoleNow(payload, options = {}) {
|
|
348
349
|
try {
|
|
349
350
|
await notifyUsers(await listStaffUids(), payload, _extends({
|
|
350
351
|
audience: 'staff'
|
|
@@ -361,7 +362,7 @@ async function listStaffUids() {
|
|
|
361
362
|
* `skipEmail` is for a caller that emails the same notice to the same people
|
|
362
363
|
* itself — the usage alerts do (AGL-3431) — so an admin who switched the
|
|
363
364
|
* email channel on is not sent it twice.
|
|
364
|
-
*/
|
|
365
|
+
*/ async function notifyOrgAdminsNow(orgId, payload, options = {}) {
|
|
365
366
|
try {
|
|
366
367
|
const members = await listOrgMembers(orgId);
|
|
367
368
|
const admins = members.filter((member)=>member.role === 'owner' || member.role === 'admin');
|
|
@@ -423,7 +424,7 @@ async function listStaffUids() {
|
|
|
423
424
|
* The host doc already carries `orgId` (AGL-233), so this costs no extra
|
|
424
425
|
* read. Spread conditionally: the field is optional on the host model and
|
|
425
426
|
* Firestore rejects `undefined`.
|
|
426
|
-
*/
|
|
427
|
+
*/ async function notifyHostManagersNow(hostId, payload, options = {}) {
|
|
427
428
|
try {
|
|
428
429
|
var _host_get, _payload_orgId;
|
|
429
430
|
const host = await firestore().collection('hosts').doc(hostId).get();
|
|
@@ -451,5 +452,25 @@ async function listStaffUids() {
|
|
|
451
452
|
console.error('host manager notification failed', error);
|
|
452
453
|
}
|
|
453
454
|
}
|
|
455
|
+
/*
|
|
456
|
+
* The fan-out's public doors. Most callers start a notification with
|
|
457
|
+
* `void notify…()` beside the mutation it reports, so each door hands its
|
|
458
|
+
* work to the platform's `waitUntil` (`keep-alive.ts`): the push and the email
|
|
459
|
+
* after the feed write are no longer cut off when the response goes out.
|
|
460
|
+
*/ /** {@link notifyUsersNow}, kept alive past the response. */ export function notifyUsers(...args) {
|
|
461
|
+
return keepAlive(notifyUsersNow(...args));
|
|
462
|
+
}
|
|
463
|
+
/** {@link notifyStaffNow}, kept alive past the response. */ export function notifyStaff(...args) {
|
|
464
|
+
return keepAlive(notifyStaffNow(...args));
|
|
465
|
+
}
|
|
466
|
+
/** {@link notifyStaffConsoleNow}, kept alive past the response. */ export function notifyStaffConsole(...args) {
|
|
467
|
+
return keepAlive(notifyStaffConsoleNow(...args));
|
|
468
|
+
}
|
|
469
|
+
/** {@link notifyOrgAdminsNow}, kept alive past the response. */ export function notifyOrgAdmins(...args) {
|
|
470
|
+
return keepAlive(notifyOrgAdminsNow(...args));
|
|
471
|
+
}
|
|
472
|
+
/** {@link notifyHostManagersNow}, kept alive past the response. */ export function notifyHostManagers(...args) {
|
|
473
|
+
return keepAlive(notifyHostManagersNow(...args));
|
|
474
|
+
}
|
|
454
475
|
|
|
455
476
|
//# sourceMappingURL=notifications.js.map
|