@aglyn/aglyn 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 (188) hide show
  1. package/package.json +12 -12
  2. package/src/lib/app-utils/actions.d.ts +1 -59
  3. package/src/lib/app-utils/actions.js +7 -72
  4. package/src/lib/app-utils/actions.js.map +1 -1
  5. package/src/lib/app-utils/consent-groups.d.ts +1 -1
  6. package/src/lib/app-utils/consent-groups.js.map +1 -1
  7. package/src/lib/app-utils/console-routes.d.ts +0 -36
  8. package/src/lib/app-utils/console-routes.js +2 -37
  9. package/src/lib/app-utils/console-routes.js.map +1 -1
  10. package/src/lib/app-utils/contacts.d.ts +1 -1
  11. package/src/lib/app-utils/contacts.js.map +1 -1
  12. package/src/lib/app-utils/container-membership.d.ts +93 -0
  13. package/src/lib/app-utils/container-membership.js +165 -0
  14. package/src/lib/app-utils/container-membership.js.map +1 -0
  15. package/src/lib/app-utils/crm-task-reminders.d.ts +23 -6
  16. package/src/lib/app-utils/crm-task-reminders.js +32 -11
  17. package/src/lib/app-utils/crm-task-reminders.js.map +1 -1
  18. package/src/lib/app-utils/crm.d.ts +24 -4
  19. package/src/lib/app-utils/crm.js +23 -0
  20. package/src/lib/app-utils/crm.js.map +1 -1
  21. package/src/lib/app-utils/csv-upload.d.ts +1 -1
  22. package/src/lib/app-utils/csv-upload.js +1 -1
  23. package/src/lib/app-utils/csv-upload.js.map +1 -1
  24. package/src/lib/app-utils/{dataset-csv.d.ts → csv.d.ts} +10 -43
  25. package/src/lib/app-utils/{dataset-csv.js → csv.js} +10 -81
  26. package/src/lib/app-utils/csv.js.map +1 -0
  27. package/src/lib/app-utils/datasets.d.ts +0 -19
  28. package/src/lib/app-utils/datasets.js +0 -13
  29. package/src/lib/app-utils/datasets.js.map +1 -1
  30. package/src/lib/app-utils/docs-help.generated.d.ts +4 -4
  31. package/src/lib/app-utils/docs-help.generated.js +5 -0
  32. package/src/lib/app-utils/docs-help.generated.js.map +1 -1
  33. package/src/lib/app-utils/docs-index.generated.js +71 -36
  34. package/src/lib/app-utils/docs-index.generated.js.map +1 -1
  35. package/src/lib/app-utils/dynamic-list-rule.js +3 -3
  36. package/src/lib/app-utils/dynamic-list-rule.js.map +1 -1
  37. package/src/lib/app-utils/email-text.d.ts +80 -0
  38. package/src/lib/app-utils/email-text.js +189 -0
  39. package/src/lib/app-utils/email-text.js.map +1 -0
  40. package/src/lib/app-utils/{list-assignment-policy.d.ts → enrollment-basis.d.ts} +4 -0
  41. package/src/lib/app-utils/{list-assignment-policy.js → enrollment-basis.js} +5 -1
  42. package/src/lib/app-utils/enrollment-basis.js.map +1 -0
  43. package/src/lib/app-utils/expand-repeatables.js +4 -4
  44. package/src/lib/app-utils/expand-repeatables.js.map +1 -1
  45. package/src/lib/app-utils/forms.js +2 -2
  46. package/src/lib/app-utils/forms.js.map +1 -1
  47. package/src/lib/app-utils/health-report.d.ts +2 -3
  48. package/src/lib/app-utils/health-report.js +30 -6
  49. package/src/lib/app-utils/health-report.js.map +1 -1
  50. package/src/lib/app-utils/inbound-mail-domain.d.ts +27 -0
  51. package/src/lib/app-utils/inbound-mail-domain.js +31 -0
  52. package/src/lib/app-utils/inbound-mail-domain.js.map +1 -0
  53. package/src/lib/app-utils/leaving-notice.d.ts +96 -0
  54. package/src/lib/app-utils/leaving-notice.js +157 -0
  55. package/src/lib/app-utils/leaving-notice.js.map +1 -0
  56. package/src/lib/app-utils/lockdown.d.ts +11 -0
  57. package/src/lib/app-utils/lockdown.js +33 -3
  58. package/src/lib/app-utils/lockdown.js.map +1 -1
  59. package/src/lib/app-utils/member-email-aliases.d.ts +25 -0
  60. package/src/lib/app-utils/member-email-aliases.js +29 -0
  61. package/src/lib/app-utils/member-email-aliases.js.map +1 -1
  62. package/src/lib/app-utils/notifications.d.ts +69 -0
  63. package/src/lib/app-utils/notifications.js +119 -0
  64. package/src/lib/app-utils/notifications.js.map +1 -1
  65. package/src/lib/app-utils/operator-alerts.d.ts +15 -1
  66. package/src/lib/app-utils/operator-alerts.js +64 -10
  67. package/src/lib/app-utils/operator-alerts.js.map +1 -1
  68. package/src/lib/app-utils/plan-entitlements.d.ts +164 -178
  69. package/src/lib/app-utils/plan-entitlements.js +200 -224
  70. package/src/lib/app-utils/plan-entitlements.js.map +1 -1
  71. package/src/lib/app-utils/platform-marketing-consent.d.ts +56 -2
  72. package/src/lib/app-utils/platform-marketing-consent.js +6 -5
  73. package/src/lib/app-utils/platform-marketing-consent.js.map +1 -1
  74. package/src/lib/app-utils/plugin-starter-templates.generated.d.ts +12 -0
  75. package/src/lib/app-utils/plugin-starter-templates.generated.js +711 -0
  76. package/src/lib/app-utils/plugin-starter-templates.generated.js.map +1 -0
  77. package/src/lib/app-utils/repeat-query.d.ts +56 -0
  78. package/src/lib/app-utils/{dataset-query.js → repeat-query.js} +16 -16
  79. package/src/lib/app-utils/repeat-query.js.map +1 -0
  80. package/src/lib/app-utils/screen-link-context-value.d.ts +7 -0
  81. package/src/lib/app-utils/screen-link-context-value.js.map +1 -1
  82. package/src/lib/app-utils/screen-link-context.js +14 -2
  83. package/src/lib/app-utils/screen-link-context.js.map +1 -1
  84. package/src/lib/app-utils/screen-route.d.ts +3 -0
  85. package/src/lib/app-utils/screen-route.js +6 -1
  86. package/src/lib/app-utils/screen-route.js.map +1 -1
  87. package/src/lib/app-utils/server.d.ts +7 -9
  88. package/src/lib/app-utils/server.js +18 -26
  89. package/src/lib/app-utils/server.js.map +1 -1
  90. package/src/lib/app-utils/starter-template-nodes.d.ts +137 -0
  91. package/src/lib/app-utils/starter-template-nodes.js +129 -0
  92. package/src/lib/app-utils/starter-template-nodes.js.map +1 -0
  93. package/src/lib/app-utils/starter-templates.d.ts +11 -34
  94. package/src/lib/app-utils/starter-templates.js +19 -230
  95. package/src/lib/app-utils/starter-templates.js.map +1 -1
  96. package/src/lib/app-utils/{email-topics.d.ts → subscription-topics.d.ts} +64 -48
  97. package/src/lib/app-utils/{email-topics.js → subscription-topics.js} +39 -109
  98. package/src/lib/app-utils/subscription-topics.js.map +1 -0
  99. package/src/lib/app-utils/usage-budget.d.ts +73 -188
  100. package/src/lib/app-utils/usage-budget.js +58 -194
  101. package/src/lib/app-utils/usage-budget.js.map +1 -1
  102. package/src/lib/app-utils/utm-attribution.d.ts +1 -1
  103. package/src/lib/app-utils/utm-attribution.js +1 -1
  104. package/src/lib/app-utils/utm-attribution.js.map +1 -1
  105. package/src/lib/app-utils/{campaign-touch.d.ts → utm-touch.d.ts} +21 -13
  106. package/src/lib/app-utils/{campaign-touch.js → utm-touch.js} +30 -22
  107. package/src/lib/app-utils/utm-touch.js.map +1 -0
  108. package/src/lib/app-utils/variables.d.ts +9 -9
  109. package/src/lib/app-utils/variables.js.map +1 -1
  110. package/src/lib/app-utils/visitor-record-ceiling.d.ts +32 -0
  111. package/src/lib/app-utils/visitor-record-ceiling.js +39 -0
  112. package/src/lib/app-utils/visitor-record-ceiling.js.map +1 -1
  113. package/src/lib/foundation/definitions/org-billing.types.d.ts +2 -2
  114. package/src/lib/foundation/definitions/org-billing.types.js.map +1 -1
  115. package/src/lib/plugin-manager/feature-plugins.d.ts +80 -79
  116. package/src/lib/plugin-manager/feature-plugins.js +22 -35
  117. package/src/lib/plugin-manager/feature-plugins.js.map +1 -1
  118. package/src/lib/plugin-manager/first-party-plugins.generated.d.ts +18 -1
  119. package/src/lib/plugin-manager/first-party-plugins.generated.js +62 -2
  120. package/src/lib/plugin-manager/first-party-plugins.generated.js.map +1 -1
  121. package/src/lib/plugin-manager/index.d.ts +0 -1
  122. package/src/lib/plugin-manager/index.js +0 -1
  123. package/src/lib/plugin-manager/index.js.map +1 -1
  124. package/src/lib/plugin-manager/plugin-contact-capture.d.ts +9 -4
  125. package/src/lib/plugin-manager/plugin-contact-capture.js.map +1 -1
  126. package/src/lib/plugin-manager/plugin-containers.d.ts +68 -0
  127. package/src/lib/plugin-manager/plugin-containers.generated.d.ts +10 -0
  128. package/src/lib/plugin-manager/plugin-containers.generated.js +20 -0
  129. package/src/lib/plugin-manager/plugin-containers.generated.js.map +1 -0
  130. package/src/lib/plugin-manager/plugin-containers.js +27 -0
  131. package/src/lib/plugin-manager/plugin-containers.js.map +1 -0
  132. package/src/lib/plugin-manager/plugin-entitlements.d.ts +8 -0
  133. package/src/lib/plugin-manager/plugin-entitlements.js.map +1 -1
  134. package/src/lib/plugin-manager/plugin-membership-detach.js +1 -1
  135. package/src/lib/plugin-manager/plugin-membership-detach.js.map +1 -1
  136. package/src/lib/plugin-manager/plugin-record-email-state.d.ts +69 -0
  137. package/src/lib/plugin-manager/plugin-record-email-state.js +32 -0
  138. package/src/lib/plugin-manager/plugin-record-email-state.js.map +1 -1
  139. package/src/lib/plugin-manager/plugin-record-lists.d.ts +115 -0
  140. package/src/lib/plugin-manager/plugin-record-lists.js +75 -0
  141. package/src/lib/plugin-manager/plugin-record-lists.js.map +1 -0
  142. package/src/lib/plugin-manager/plugin-record-timeline.d.ts +42 -8
  143. package/src/lib/plugin-manager/plugin-record-timeline.js.map +1 -1
  144. package/src/lib/plugin-manager/plugin-server-steps.d.ts +170 -0
  145. package/src/lib/plugin-manager/plugin-server-steps.js +116 -0
  146. package/src/lib/plugin-manager/plugin-server-steps.js.map +1 -0
  147. package/src/lib/plugin-manager/plugin-site-beacons.d.ts +71 -0
  148. package/src/lib/plugin-manager/plugin-site-beacons.js +54 -0
  149. package/src/lib/plugin-manager/plugin-site-beacons.js.map +1 -0
  150. package/src/lib/plugin-manager/plugin-subscription-topics.generated.d.ts +11 -0
  151. package/src/lib/plugin-manager/plugin-subscription-topics.generated.js +41 -0
  152. package/src/lib/plugin-manager/plugin-subscription-topics.generated.js.map +1 -0
  153. package/src/lib/plugin-manager/plugin-usage-axes.d.ts +212 -0
  154. package/src/lib/plugin-manager/plugin-usage-axes.js +82 -3
  155. package/src/lib/plugin-manager/plugin-usage-axes.js.map +1 -1
  156. package/src/lib/plugin-manager/plugin-usage-meters.d.ts +108 -0
  157. package/src/lib/plugin-manager/plugin-usage-meters.js +105 -0
  158. package/src/lib/plugin-manager/plugin-usage-meters.js.map +1 -0
  159. package/src/lib/plugin-manager/usage-alert-contributors.d.ts +76 -6
  160. package/src/lib/plugin-manager/usage-alert-contributors.js +23 -10
  161. package/src/lib/plugin-manager/usage-alert-contributors.js.map +1 -1
  162. package/src/lib/app-utils/assist-credits.d.ts +0 -482
  163. package/src/lib/app-utils/assist-credits.js +0 -510
  164. package/src/lib/app-utils/assist-credits.js.map +0 -1
  165. package/src/lib/app-utils/campaign-membership.d.ts +0 -121
  166. package/src/lib/app-utils/campaign-membership.js +0 -189
  167. package/src/lib/app-utils/campaign-membership.js.map +0 -1
  168. package/src/lib/app-utils/campaign-touch.js.map +0 -1
  169. package/src/lib/app-utils/console-record-links.d.ts +0 -84
  170. package/src/lib/app-utils/console-record-links.js +0 -76
  171. package/src/lib/app-utils/console-record-links.js.map +0 -1
  172. package/src/lib/app-utils/crm-booking.d.ts +0 -75
  173. package/src/lib/app-utils/crm-booking.js +0 -133
  174. package/src/lib/app-utils/crm-booking.js.map +0 -1
  175. package/src/lib/app-utils/crm-inbound.d.ts +0 -245
  176. package/src/lib/app-utils/crm-inbound.js +0 -407
  177. package/src/lib/app-utils/crm-inbound.js.map +0 -1
  178. package/src/lib/app-utils/crm-reports.d.ts +0 -479
  179. package/src/lib/app-utils/crm-reports.js +0 -680
  180. package/src/lib/app-utils/crm-reports.js.map +0 -1
  181. package/src/lib/app-utils/dataset-csv.js.map +0 -1
  182. package/src/lib/app-utils/dataset-query.d.ts +0 -57
  183. package/src/lib/app-utils/dataset-query.js.map +0 -1
  184. package/src/lib/app-utils/email-topics.js.map +0 -1
  185. package/src/lib/app-utils/list-assignment-policy.js.map +0 -1
  186. package/src/lib/plugin-manager/plugin-metered-lines.d.ts +0 -97
  187. package/src/lib/plugin-manager/plugin-metered-lines.js +0 -123
  188. package/src/lib/plugin-manager/plugin-metered-lines.js.map +0 -1
@@ -0,0 +1,27 @@
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
+ /** Where the platform receives mail when the deployment names no domain. */
18
+ export declare const INBOUND_MAIL_DEFAULT_DOMAIN = "in.aglyn.com";
19
+ /**
20
+ * The domain the platform receives mail on for the workspaces it serves —
21
+ * the one its mail provider's inbound route answers, and so one no member
22
+ * may claim an address on (AGL-2657, AGL-2975). `CRM_INBOUND_DOMAIN`, the
23
+ * name the deployment has always set it under, or the platform's default
24
+ * when it names none or names something that is not a hostname. Lowercased,
25
+ * because a domain is.
26
+ */
27
+ export declare function inboundMailDomain(env?: Record<string, string | undefined>): string;
@@ -0,0 +1,31 @@
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
+ */ /** Where the platform receives mail when the deployment names no domain. */ export const INBOUND_MAIL_DEFAULT_DOMAIN = 'in.aglyn.com';
17
+ const DOMAIN_PATTERN = /^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)+$/;
18
+ /**
19
+ * The domain the platform receives mail on for the workspaces it serves —
20
+ * the one its mail provider's inbound route answers, and so one no member
21
+ * may claim an address on (AGL-2657, AGL-2975). `CRM_INBOUND_DOMAIN`, the
22
+ * name the deployment has always set it under, or the platform's default
23
+ * when it names none or names something that is not a hostname. Lowercased,
24
+ * because a domain is.
25
+ */ export function inboundMailDomain(env = process.env) {
26
+ var _env_CRM_INBOUND_DOMAIN;
27
+ const configured = String((_env_CRM_INBOUND_DOMAIN = env['CRM_INBOUND_DOMAIN']) != null ? _env_CRM_INBOUND_DOMAIN : '').trim().toLowerCase();
28
+ return DOMAIN_PATTERN.test(configured) ? configured : INBOUND_MAIL_DEFAULT_DOMAIN;
29
+ }
30
+
31
+ //# sourceMappingURL=inbound-mail-domain.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../libs/aglyn/src/lib/app-utils/inbound-mail-domain.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/** Where the platform receives mail when the deployment names no domain. */\nexport const INBOUND_MAIL_DEFAULT_DOMAIN = 'in.aglyn.com'\n\nconst DOMAIN_PATTERN = /^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)+$/\n\n/**\n * The domain the platform receives mail on for the workspaces it serves —\n * the one its mail provider's inbound route answers, and so one no member\n * may claim an address on (AGL-2657, AGL-2975). `CRM_INBOUND_DOMAIN`, the\n * name the deployment has always set it under, or the platform's default\n * when it names none or names something that is not a hostname. Lowercased,\n * because a domain is.\n */\nexport function inboundMailDomain(\n env: Record<string, string | undefined> = process.env,\n): string {\n const configured = String(env['CRM_INBOUND_DOMAIN'] ?? '')\n .trim()\n .toLowerCase()\n return DOMAIN_PATTERN.test(configured) ? configured : INBOUND_MAIL_DEFAULT_DOMAIN\n}\n"],"names":["INBOUND_MAIL_DEFAULT_DOMAIN","DOMAIN_PATTERN","inboundMailDomain","env","process","configured","String","trim","toLowerCase","test"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED,0EAA0E,GAC1E,OAAO,MAAMA,8BAA8B,eAAc;AAEzD,MAAMC,iBAAiB;AAEvB;;;;;;;CAOC,GACD,OAAO,SAASC,kBACdC,MAA0CC,QAAQD,GAAG;QAE3BA;IAA1B,MAAME,aAAaC,QAAOH,0BAAAA,GAAG,CAAC,qBAAqB,YAAzBA,0BAA6B,IACpDI,IAAI,GACJC,WAAW;IACd,OAAOP,eAAeQ,IAAI,CAACJ,cAAcA,aAAaL;AACxD"}
@@ -0,0 +1,96 @@
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
+ /** The first path segment the notice owns on every site (a reserved slug). */
18
+ export declare const LEAVING_NOTICE_ROUTE_SEGMENT = "_aglyn";
19
+ /** Where the notice is served, on the site's own host. */
20
+ export declare const LEAVING_NOTICE_PATH = "/_aglyn/leaving";
21
+ /** The query parameter carrying the destination. */
22
+ export declare const LEAVING_NOTICE_DESTINATION_PARAM = "to";
23
+ /** The query parameter carrying the destination's signature. */
24
+ export declare const LEAVING_NOTICE_SIGNATURE_PARAM = "sig";
25
+ /** What a page in its notice window carries, from the server. */
26
+ export interface LeavingNoticeConfig {
27
+ /**
28
+ * Epoch ms the window closes. For people and specs reading the payload:
29
+ * nothing here compares it to a clock, because the page that carries this
30
+ * config was rendered inside the window and its href has to match the
31
+ * server's byte for byte.
32
+ */
33
+ until: number;
34
+ /**
35
+ * The hosts a link may go to without the notice: the site's own subdomain
36
+ * and custom domain, and the platform's own. An entry starting with `.` is
37
+ * that domain AND every name under it.
38
+ */
39
+ hosts: readonly string[];
40
+ /**
41
+ * Destination (as {@link normalizeLeavingDestination} writes it) → its
42
+ * signature, for every outside address the server found on this page. A
43
+ * destination with none still goes through the notice, which refuses it.
44
+ */
45
+ sigs: Readonly<Record<string, string>>;
46
+ }
47
+ /**
48
+ * The absolute http(s) address an href navigates to when it names another
49
+ * host, or `null` for anything that stays on the page's own origin or is not
50
+ * a navigation at all.
51
+ *
52
+ * Resolved the way a browser resolves it rather than pattern-matched, because
53
+ * a pattern is exactly what a disguised link is built to get past: `//evil`
54
+ * and `/\evil` are both protocol-relative to a browser while reading as
55
+ * site paths to a `^/` test. `mailto:`, `tel:` and `sms:` hand off to another
56
+ * app and are not navigations, so they answer `null` with every other scheme.
57
+ *
58
+ * The answer is the URL's `href` — lowercased host, default port dropped,
59
+ * root path filled in — so the server that signs a destination and the
60
+ * client that looks the signature up agree on one spelling.
61
+ */
62
+ export declare function normalizeLeavingDestination(raw: unknown): string | null;
63
+ /**
64
+ * Whether a hostname is one a link may reach without the notice: listed in
65
+ * `hosts` (exactly, or under a `.domain` entry), or a development machine's
66
+ * own `localhost`, which sends a visitor nowhere.
67
+ */
68
+ export declare function isLeavingNoticeExempt(hostname: string, hosts: readonly string[]): boolean;
69
+ /**
70
+ * The normalized destination of an href that leaves for a host outside
71
+ * `hosts`, or `null` when the href stays, is not a navigation, or is exempt.
72
+ */
73
+ export declare function leavingDestination(raw: unknown, hosts: readonly string[]): string | null;
74
+ /** The notice's own address for a destination, signed when a signature is known. */
75
+ export declare function leavingNoticeHref(destination: string, signature?: string | null): string;
76
+ /**
77
+ * The href a link should carry instead of `raw` — the notice for its
78
+ * destination — or `undefined` to leave it as it is: no config (the site is
79
+ * not in its window), a destination on an exempt host, or not a navigation.
80
+ */
81
+ export declare function routeThroughLeavingNotice(raw: unknown, config: LeavingNoticeConfig | null | undefined): string | undefined;
82
+ /**
83
+ * Points the anchor an event landed in at the notice, when it leaves.
84
+ *
85
+ * The catch-all for every link `useLinkTarget` never saw — Markdown and rich
86
+ * text bodies, collection blocks, a plugin's own anchors. The href is
87
+ * rewritten IN PLACE, before the browser acts on the event, so the visitor's
88
+ * own gesture still decides how it opens: a modified click, a middle click
89
+ * and "Open in new tab" all take the notice with them. Next's `Link` leaves
90
+ * a non-local href to the browser, so the browser is what reads the change.
91
+ *
92
+ * Returns whether it rewrote anything.
93
+ */
94
+ export declare function rerouteLeavingAnchor(target: EventTarget | null, config: LeavingNoticeConfig | null | undefined): boolean;
95
+ /** Installs {@link rerouteLeavingAnchor} on a document; returns the uninstall. */
96
+ export declare function installLeavingNoticeInterceptor(doc: Document, config: LeavingNoticeConfig): () => void;
@@ -0,0 +1,157 @@
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
+ * THE "YOU'RE LEAVING THIS SITE" NOTICE, CLIENT HALF (AGL-3452).
18
+ *
19
+ * Both phishing pages found on free sites on 2026-10-01 had one button, and it
20
+ * went straight to an outside credential harvester: the site's address was
21
+ * only the trusted-looking hop. So for a FREE workspace in its first 14 days,
22
+ * a link to another domain goes through a notice on the site's own host
23
+ * first, which names the destination in plain text and says the platform
24
+ * does not operate it.
25
+ *
26
+ * The server decides whether a page is in that window and hands the page a
27
+ * {@link LeavingNoticeConfig}; this module only applies it. Nothing here
28
+ * reads the clock, so the server render and the hydrating client always
29
+ * produce the same href.
30
+ *
31
+ * A leaf with no imports: it is on every published page through
32
+ * `useLinkTarget`, and the hosts and signatures it needs arrive as data.
33
+ *=========================================*/ /** The first path segment the notice owns on every site (a reserved slug). */ export const LEAVING_NOTICE_ROUTE_SEGMENT = '_aglyn';
34
+ /** Where the notice is served, on the site's own host. */ export const LEAVING_NOTICE_PATH = `/${LEAVING_NOTICE_ROUTE_SEGMENT}/leaving`;
35
+ /** The query parameter carrying the destination. */ export const LEAVING_NOTICE_DESTINATION_PARAM = 'to';
36
+ /** The query parameter carrying the destination's signature. */ export const LEAVING_NOTICE_SIGNATURE_PARAM = 'sig';
37
+ /** The origin a site-relative href is resolved against to tell it apart. */ const SITE_RELATIVE_BASE = 'https://site-relative.invalid';
38
+ const SITE_RELATIVE_HOSTNAME = 'site-relative.invalid';
39
+ /**
40
+ * The absolute http(s) address an href navigates to when it names another
41
+ * host, or `null` for anything that stays on the page's own origin or is not
42
+ * a navigation at all.
43
+ *
44
+ * Resolved the way a browser resolves it rather than pattern-matched, because
45
+ * a pattern is exactly what a disguised link is built to get past: `//evil`
46
+ * and `/\evil` are both protocol-relative to a browser while reading as
47
+ * site paths to a `^/` test. `mailto:`, `tel:` and `sms:` hand off to another
48
+ * app and are not navigations, so they answer `null` with every other scheme.
49
+ *
50
+ * The answer is the URL's `href` — lowercased host, default port dropped,
51
+ * root path filled in — so the server that signs a destination and the
52
+ * client that looks the signature up agree on one spelling.
53
+ */ export function normalizeLeavingDestination(raw) {
54
+ if (typeof raw !== 'string') return null;
55
+ const trimmed = raw.trim();
56
+ if (!trimmed) return null;
57
+ let url;
58
+ try {
59
+ url = new URL(trimmed, SITE_RELATIVE_BASE);
60
+ } catch (unused) {
61
+ return null;
62
+ }
63
+ if (url.protocol !== 'http:' && url.protocol !== 'https:') return null;
64
+ if (!url.hostname || url.hostname === SITE_RELATIVE_HOSTNAME) return null;
65
+ return url.href;
66
+ }
67
+ /**
68
+ * Whether a hostname is one a link may reach without the notice: listed in
69
+ * `hosts` (exactly, or under a `.domain` entry), or a development machine's
70
+ * own `localhost`, which sends a visitor nowhere.
71
+ */ export function isLeavingNoticeExempt(hostname, hosts) {
72
+ const name = String(hostname != null ? hostname : '').toLowerCase().replace(/\.+$/, '');
73
+ if (!name) return true;
74
+ if (name === 'localhost' || name.endsWith('.localhost')) return true;
75
+ for (const entry of hosts){
76
+ if (entry.startsWith('.')) {
77
+ if (name === entry.slice(1) || name.endsWith(entry)) return true;
78
+ } else if (name === entry) {
79
+ return true;
80
+ }
81
+ }
82
+ return false;
83
+ }
84
+ /**
85
+ * The normalized destination of an href that leaves for a host outside
86
+ * `hosts`, or `null` when the href stays, is not a navigation, or is exempt.
87
+ */ export function leavingDestination(raw, hosts) {
88
+ const destination = normalizeLeavingDestination(raw);
89
+ if (!destination) return null;
90
+ return isLeavingNoticeExempt(new URL(destination).hostname, hosts) ? null : destination;
91
+ }
92
+ /** The notice's own address for a destination, signed when a signature is known. */ export function leavingNoticeHref(destination, signature) {
93
+ const params = new URLSearchParams();
94
+ params.set(LEAVING_NOTICE_DESTINATION_PARAM, destination);
95
+ if (signature) params.set(LEAVING_NOTICE_SIGNATURE_PARAM, signature);
96
+ return `${LEAVING_NOTICE_PATH}?${params.toString()}`;
97
+ }
98
+ /**
99
+ * The href a link should carry instead of `raw` — the notice for its
100
+ * destination — or `undefined` to leave it as it is: no config (the site is
101
+ * not in its window), a destination on an exempt host, or not a navigation.
102
+ */ export function routeThroughLeavingNotice(raw, config) {
103
+ var _config_sigs;
104
+ if (!config) return undefined;
105
+ const destination = leavingDestination(raw, config.hosts);
106
+ if (!destination) return undefined;
107
+ return leavingNoticeHref(destination, (_config_sigs = config.sigs) == null ? void 0 : _config_sigs[destination]);
108
+ }
109
+ /**
110
+ * Points the anchor an event landed in at the notice, when it leaves.
111
+ *
112
+ * The catch-all for every link `useLinkTarget` never saw — Markdown and rich
113
+ * text bodies, collection blocks, a plugin's own anchors. The href is
114
+ * rewritten IN PLACE, before the browser acts on the event, so the visitor's
115
+ * own gesture still decides how it opens: a modified click, a middle click
116
+ * and "Open in new tab" all take the notice with them. Next's `Link` leaves
117
+ * a non-local href to the browser, so the browser is what reads the change.
118
+ *
119
+ * Returns whether it rewrote anything.
120
+ */ export function rerouteLeavingAnchor(target, config) {
121
+ var _node_parentElement;
122
+ var _element_closest;
123
+ if (!config || !target) return false;
124
+ const node = target;
125
+ const element = node.nodeType === 1 ? node : (_node_parentElement = node.parentElement) != null ? _node_parentElement : null;
126
+ const anchor = element == null ? void 0 : (_element_closest = element.closest) == null ? void 0 : _element_closest.call(element, 'a[href], area[href]');
127
+ if (!anchor) return false;
128
+ const rerouted = routeThroughLeavingNotice(anchor.getAttribute('href'), config);
129
+ if (!rerouted) return false;
130
+ anchor.setAttribute('href', rerouted);
131
+ return true;
132
+ }
133
+ /**
134
+ * Every gesture that can follow, open or copy a link, caught on the way
135
+ * down so the href is the notice's before anything reads it.
136
+ */ const LEAVING_NOTICE_EVENTS = [
137
+ 'pointerdown',
138
+ 'click',
139
+ 'auxclick',
140
+ 'contextmenu',
141
+ 'focusin'
142
+ ];
143
+ /** Installs {@link rerouteLeavingAnchor} on a document; returns the uninstall. */ export function installLeavingNoticeInterceptor(doc, config) {
144
+ const handler = (event)=>{
145
+ rerouteLeavingAnchor(event.target, config);
146
+ };
147
+ for (const type of LEAVING_NOTICE_EVENTS){
148
+ doc.addEventListener(type, handler, true);
149
+ }
150
+ return ()=>{
151
+ for (const type of LEAVING_NOTICE_EVENTS){
152
+ doc.removeEventListener(type, handler, true);
153
+ }
154
+ };
155
+ }
156
+
157
+ //# sourceMappingURL=leaving-notice.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../libs/aglyn/src/lib/app-utils/leaving-notice.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 * THE \"YOU'RE LEAVING THIS SITE\" NOTICE, CLIENT HALF (AGL-3452).\n *\n * Both phishing pages found on free sites on 2026-10-01 had one button, and it\n * went straight to an outside credential harvester: the site's address was\n * only the trusted-looking hop. So for a FREE workspace in its first 14 days,\n * a link to another domain goes through a notice on the site's own host\n * first, which names the destination in plain text and says the platform\n * does not operate it.\n *\n * The server decides whether a page is in that window and hands the page a\n * {@link LeavingNoticeConfig}; this module only applies it. Nothing here\n * reads the clock, so the server render and the hydrating client always\n * produce the same href.\n *\n * A leaf with no imports: it is on every published page through\n * `useLinkTarget`, and the hosts and signatures it needs arrive as data.\n *=========================================*/\n\n/** The first path segment the notice owns on every site (a reserved slug). */\nexport const LEAVING_NOTICE_ROUTE_SEGMENT = '_aglyn'\n\n/** Where the notice is served, on the site's own host. */\nexport const LEAVING_NOTICE_PATH = `/${LEAVING_NOTICE_ROUTE_SEGMENT}/leaving`\n\n/** The query parameter carrying the destination. */\nexport const LEAVING_NOTICE_DESTINATION_PARAM = 'to'\n\n/** The query parameter carrying the destination's signature. */\nexport const LEAVING_NOTICE_SIGNATURE_PARAM = 'sig'\n\n/** What a page in its notice window carries, from the server. */\nexport interface LeavingNoticeConfig {\n /**\n * Epoch ms the window closes. For people and specs reading the payload:\n * nothing here compares it to a clock, because the page that carries this\n * config was rendered inside the window and its href has to match the\n * server's byte for byte.\n */\n until: number\n /**\n * The hosts a link may go to without the notice: the site's own subdomain\n * and custom domain, and the platform's own. An entry starting with `.` is\n * that domain AND every name under it.\n */\n hosts: readonly string[]\n /**\n * Destination (as {@link normalizeLeavingDestination} writes it) → its\n * signature, for every outside address the server found on this page. A\n * destination with none still goes through the notice, which refuses it.\n */\n sigs: Readonly<Record<string, string>>\n}\n\n/** The origin a site-relative href is resolved against to tell it apart. */\nconst SITE_RELATIVE_BASE = 'https://site-relative.invalid'\nconst SITE_RELATIVE_HOSTNAME = 'site-relative.invalid'\n\n/**\n * The absolute http(s) address an href navigates to when it names another\n * host, or `null` for anything that stays on the page's own origin or is not\n * a navigation at all.\n *\n * Resolved the way a browser resolves it rather than pattern-matched, because\n * a pattern is exactly what a disguised link is built to get past: `//evil`\n * and `/\\evil` are both protocol-relative to a browser while reading as\n * site paths to a `^/` test. `mailto:`, `tel:` and `sms:` hand off to another\n * app and are not navigations, so they answer `null` with every other scheme.\n *\n * The answer is the URL's `href` — lowercased host, default port dropped,\n * root path filled in — so the server that signs a destination and the\n * client that looks the signature up agree on one spelling.\n */\nexport function normalizeLeavingDestination(raw: unknown): string | null {\n if (typeof raw !== 'string') return null\n const trimmed = raw.trim()\n if (!trimmed) return null\n let url: URL\n try {\n url = new URL(trimmed, SITE_RELATIVE_BASE)\n } catch {\n return null\n }\n if (url.protocol !== 'http:' && url.protocol !== 'https:') return null\n if (!url.hostname || url.hostname === SITE_RELATIVE_HOSTNAME) return null\n return url.href\n}\n\n/**\n * Whether a hostname is one a link may reach without the notice: listed in\n * `hosts` (exactly, or under a `.domain` entry), or a development machine's\n * own `localhost`, which sends a visitor nowhere.\n */\nexport function isLeavingNoticeExempt(\n hostname: string,\n hosts: readonly string[],\n): boolean {\n const name = String(hostname ?? '')\n .toLowerCase()\n .replace(/\\.+$/, '')\n if (!name) return true\n if (name === 'localhost' || name.endsWith('.localhost')) return true\n for (const entry of hosts) {\n if (entry.startsWith('.')) {\n if (name === entry.slice(1) || name.endsWith(entry)) return true\n } else if (name === entry) {\n return true\n }\n }\n return false\n}\n\n/**\n * The normalized destination of an href that leaves for a host outside\n * `hosts`, or `null` when the href stays, is not a navigation, or is exempt.\n */\nexport function leavingDestination(\n raw: unknown,\n hosts: readonly string[],\n): string | null {\n const destination = normalizeLeavingDestination(raw)\n if (!destination) return null\n return isLeavingNoticeExempt(new URL(destination).hostname, hosts)\n ? null\n : destination\n}\n\n/** The notice's own address for a destination, signed when a signature is known. */\nexport function leavingNoticeHref(\n destination: string,\n signature?: string | null,\n): string {\n const params = new URLSearchParams()\n params.set(LEAVING_NOTICE_DESTINATION_PARAM, destination)\n if (signature) params.set(LEAVING_NOTICE_SIGNATURE_PARAM, signature)\n return `${LEAVING_NOTICE_PATH}?${params.toString()}`\n}\n\n/**\n * The href a link should carry instead of `raw` — the notice for its\n * destination — or `undefined` to leave it as it is: no config (the site is\n * not in its window), a destination on an exempt host, or not a navigation.\n */\nexport function routeThroughLeavingNotice(\n raw: unknown,\n config: LeavingNoticeConfig | null | undefined,\n): string | undefined {\n if (!config) return undefined\n const destination = leavingDestination(raw, config.hosts)\n if (!destination) return undefined\n return leavingNoticeHref(destination, config.sigs?.[destination])\n}\n\n/**\n * Points the anchor an event landed in at the notice, when it leaves.\n *\n * The catch-all for every link `useLinkTarget` never saw — Markdown and rich\n * text bodies, collection blocks, a plugin's own anchors. The href is\n * rewritten IN PLACE, before the browser acts on the event, so the visitor's\n * own gesture still decides how it opens: a modified click, a middle click\n * and \"Open in new tab\" all take the notice with them. Next's `Link` leaves\n * a non-local href to the browser, so the browser is what reads the change.\n *\n * Returns whether it rewrote anything.\n */\nexport function rerouteLeavingAnchor(\n target: EventTarget | null,\n config: LeavingNoticeConfig | null | undefined,\n): boolean {\n if (!config || !target) return false\n const node = target as Node\n const element =\n node.nodeType === 1 ? (node as Element) : (node.parentElement ?? null)\n const anchor = element?.closest?.('a[href], area[href]')\n if (!anchor) return false\n const rerouted = routeThroughLeavingNotice(anchor.getAttribute('href'), config)\n if (!rerouted) return false\n anchor.setAttribute('href', rerouted)\n return true\n}\n\n/**\n * Every gesture that can follow, open or copy a link, caught on the way\n * down so the href is the notice's before anything reads it.\n */\nconst LEAVING_NOTICE_EVENTS = [\n 'pointerdown',\n 'click',\n 'auxclick',\n 'contextmenu',\n 'focusin',\n] as const\n\n/** Installs {@link rerouteLeavingAnchor} on a document; returns the uninstall. */\nexport function installLeavingNoticeInterceptor(\n doc: Document,\n config: LeavingNoticeConfig,\n): () => void {\n const handler = (event: Event) => {\n rerouteLeavingAnchor(event.target, config)\n }\n for (const type of LEAVING_NOTICE_EVENTS) {\n doc.addEventListener(type, handler, true)\n }\n return () => {\n for (const type of LEAVING_NOTICE_EVENTS) {\n doc.removeEventListener(type, handler, true)\n }\n }\n}\n"],"names":["LEAVING_NOTICE_ROUTE_SEGMENT","LEAVING_NOTICE_PATH","LEAVING_NOTICE_DESTINATION_PARAM","LEAVING_NOTICE_SIGNATURE_PARAM","SITE_RELATIVE_BASE","SITE_RELATIVE_HOSTNAME","normalizeLeavingDestination","raw","trimmed","trim","url","URL","protocol","hostname","href","isLeavingNoticeExempt","hosts","name","String","toLowerCase","replace","endsWith","entry","startsWith","slice","leavingDestination","destination","leavingNoticeHref","signature","params","URLSearchParams","set","toString","routeThroughLeavingNotice","config","undefined","sigs","rerouteLeavingAnchor","target","node","element","nodeType","parentElement","anchor","closest","rerouted","getAttribute","setAttribute","LEAVING_NOTICE_EVENTS","installLeavingNoticeInterceptor","doc","handler","event","type","addEventListener","removeEventListener"],"mappings":"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"}
@@ -53,6 +53,17 @@ export declare function lockdownFeatureDeclaration(key: unknown): PluginLockdown
53
53
  export declare function isLockdownFeatureKey(value: unknown): value is LockdownFeatureKey;
54
54
  /** Staff-surface label; the key stays the wire/API identity. */
55
55
  export declare function lockdownFeatureLabel(key: LockdownFeatureKey): string;
56
+ /**
57
+ * What a customer's mail calls a lever (`media uploads`), never the staff
58
+ * label. A key nothing declares, or a declaration without a name, reads as
59
+ * "a feature" rather than leak a key or a staff label.
60
+ */
61
+ export declare function lockdownFeatureCustomerName(key: LockdownFeatureKey): string;
62
+ /**
63
+ * Several levers named together in a customer's mail: `AI assist and AI
64
+ * generation`. One pause of several levers is one notice.
65
+ */
66
+ export declare function lockdownFeaturesCustomerText(keys: readonly LockdownFeatureKey[]): string;
56
67
  /**
57
68
  * Whether a verified staff claim passes this feature's lock. A key nothing
58
69
  * declared answers `false`: an undeclared lever has no bypass argument on
@@ -89,6 +89,7 @@ import { listPluginLockdownFeatures } from "../plugin-manager/plugin-entitlement
89
89
  {
90
90
  key: 'signups',
91
91
  label: 'New signups',
92
+ customerName: 'new signups',
92
93
  staffBypass: false,
93
94
  notice: {
94
95
  title: 'New signups are paused',
@@ -98,6 +99,7 @@ import { listPluginLockdownFeatures } from "../plugin-manager/plugin-entitlement
98
99
  {
99
100
  key: 'uploads',
100
101
  label: 'Media uploads',
102
+ customerName: 'media uploads',
101
103
  staffBypass: true,
102
104
  notice: {
103
105
  title: 'Uploads are paused',
@@ -107,6 +109,9 @@ import { listPluginLockdownFeatures } from "../plugin-manager/plugin-entitlement
107
109
  {
108
110
  key: 'checkout',
109
111
  label: 'Checkout (new subscriptions)',
112
+ // Both doors it closes start a purchase: a plan or add-on, and a paid
113
+ // marketplace install.
114
+ customerName: 'new purchases',
110
115
  staffBypass: false,
111
116
  notice: {
112
117
  title: 'Checkout is temporarily unavailable',
@@ -124,6 +129,7 @@ import { listPluginLockdownFeatures } from "../plugin-manager/plugin-entitlement
124
129
  {
125
130
  key: 'marketplace-installs',
126
131
  label: 'Marketplace installs',
132
+ customerName: 'marketplace installs',
127
133
  staffBypass: true,
128
134
  notice: {
129
135
  title: 'Marketplace installs are paused',
@@ -172,6 +178,26 @@ export function isLockdownFeatureKey(value) {
172
178
  var _lockdownFeatureDeclaration;
173
179
  return (_ref = (_lockdownFeatureDeclaration = lockdownFeatureDeclaration(key)) == null ? void 0 : _lockdownFeatureDeclaration.label) != null ? _ref : key;
174
180
  }
181
+ /**
182
+ * What a customer's mail calls a lever (`media uploads`), never the staff
183
+ * label. A key nothing declares, or a declaration without a name, reads as
184
+ * "a feature" rather than leak a key or a staff label.
185
+ */ export function lockdownFeatureCustomerName(key) {
186
+ var _lockdownFeatureDeclaration_customerName, _lockdownFeatureDeclaration;
187
+ return ((_lockdownFeatureDeclaration = lockdownFeatureDeclaration(key)) == null ? void 0 : (_lockdownFeatureDeclaration_customerName = _lockdownFeatureDeclaration.customerName) == null ? void 0 : _lockdownFeatureDeclaration_customerName.trim()) || 'a feature';
188
+ }
189
+ /**
190
+ * Several levers named together in a customer's mail: `AI assist and AI
191
+ * generation`. One pause of several levers is one notice.
192
+ */ export function lockdownFeaturesCustomerText(keys) {
193
+ const names = [
194
+ ...new Set(keys.map(lockdownFeatureCustomerName))
195
+ ];
196
+ return new Intl.ListFormat('en-US', {
197
+ style: 'long',
198
+ type: 'conjunction'
199
+ }).format(names);
200
+ }
175
201
  /**
176
202
  * Whether a verified staff claim passes this feature's lock. A key nothing
177
203
  * declared answers `false`: an undeclared lever has no bypass argument on
@@ -634,6 +660,10 @@ export const userLockdownDocId = (uid)=>`user--${uid}`;
634
660
  if (state.scope === 'domain') {
635
661
  return domainLockdownNotice(state, custom);
636
662
  }
663
+ // A SITE lock closes one site, not the account that owns it: its default
664
+ // copy says "this site", or the owners' notice tells them their account was
665
+ // closed when their other sites are serving (AGL-3432).
666
+ const subject = state.scope === 'host' ? 'site' : 'account';
637
667
  switch(state.reason){
638
668
  case 'maintenance':
639
669
  {
@@ -647,7 +677,7 @@ export const userLockdownDocId = (uid)=>`user--${uid}`;
647
677
  var _lockdownSupportEmail;
648
678
  return {
649
679
  title: 'Account on hold',
650
- body: custom != null ? custom : 'This account is on hold over an unresolved billing issue. ' + 'Updating the payment method in workspace billing settings ' + 'restores access.',
680
+ body: custom != null ? custom : `This ${subject} is on hold over an unresolved billing issue. ` + 'Updating the payment method in workspace billing settings ' + 'restores access.',
651
681
  contact: (_lockdownSupportEmail = lockdownSupportEmail()) != null ? _lockdownSupportEmail : undefined
652
682
  };
653
683
  case 'security':
@@ -663,7 +693,7 @@ export const userLockdownDocId = (uid)=>`user--${uid}`;
663
693
  var _lockdownSupportEmail2;
664
694
  return _extends({
665
695
  title: 'Unavailable',
666
- body: custom != null ? custom : 'This account has been closed for a violation of our Terms of ' + 'Service.',
696
+ body: custom != null ? custom : `This ${subject} has been closed for a violation of our Terms of ` + 'Service.',
667
697
  contact: (_lockdownSupportEmail2 = lockdownSupportEmail()) != null ? _lockdownSupportEmail2 : undefined
668
698
  }, state.scope !== 'platform' ? {
669
699
  appeal: true
@@ -673,7 +703,7 @@ export const userLockdownDocId = (uid)=>`user--${uid}`;
673
703
  var _lockdownSupportEmail3;
674
704
  return {
675
705
  title: 'Temporarily unavailable',
676
- body: custom != null ? custom : 'Access to this account is currently disabled.',
706
+ body: custom != null ? custom : `Access to this ${subject} is currently disabled.`,
677
707
  contact: (_lockdownSupportEmail3 = lockdownSupportEmail()) != null ? _lockdownSupportEmail3 : undefined
678
708
  };
679
709
  }