@aglyn/plugins-outreach 1.0.0-beta.143

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 (282) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +93 -0
  3. package/package.json +52 -0
  4. package/src/index.d.ts +22 -0
  5. package/src/index.js +29 -0
  6. package/src/index.js.map +1 -0
  7. package/src/lib/components/compliance-section.d.ts +20 -0
  8. package/src/lib/components/compliance-section.js +291 -0
  9. package/src/lib/components/compliance-section.js.map +1 -0
  10. package/src/lib/components/enroll-dialog.d.ts +36 -0
  11. package/src/lib/components/enroll-dialog.js +656 -0
  12. package/src/lib/components/enroll-dialog.js.map +1 -0
  13. package/src/lib/components/enrollments-table.d.ts +28 -0
  14. package/src/lib/components/enrollments-table.js +375 -0
  15. package/src/lib/components/enrollments-table.js.map +1 -0
  16. package/src/lib/components/mailbox-card.d.ts +46 -0
  17. package/src/lib/components/mailbox-card.js +517 -0
  18. package/src/lib/components/mailbox-card.js.map +1 -0
  19. package/src/lib/components/mailboxes-section.d.ts +24 -0
  20. package/src/lib/components/mailboxes-section.js +258 -0
  21. package/src/lib/components/mailboxes-section.js.map +1 -0
  22. package/src/lib/components/outreach-console-page.d.ts +17 -0
  23. package/src/lib/components/outreach-console-page.js +81 -0
  24. package/src/lib/components/outreach-console-page.js.map +1 -0
  25. package/src/lib/components/outreach-console-sections.d.ts +43 -0
  26. package/src/lib/components/outreach-console-sections.js +55 -0
  27. package/src/lib/components/outreach-console-sections.js.map +1 -0
  28. package/src/lib/components/outreach-ui.d.ts +51 -0
  29. package/src/lib/components/outreach-ui.js +171 -0
  30. package/src/lib/components/outreach-ui.js.map +1 -0
  31. package/src/lib/components/send-window-fields.d.ts +25 -0
  32. package/src/lib/components/send-window-fields.js +178 -0
  33. package/src/lib/components/send-window-fields.js.map +1 -0
  34. package/src/lib/components/sequence-detail.d.ts +54 -0
  35. package/src/lib/components/sequence-detail.js +435 -0
  36. package/src/lib/components/sequence-detail.js.map +1 -0
  37. package/src/lib/components/sequence-editor.d.ts +37 -0
  38. package/src/lib/components/sequence-editor.js +485 -0
  39. package/src/lib/components/sequence-editor.js.map +1 -0
  40. package/src/lib/components/sequence-mailbox-picker.d.ts +44 -0
  41. package/src/lib/components/sequence-mailbox-picker.js +137 -0
  42. package/src/lib/components/sequence-mailbox-picker.js.map +1 -0
  43. package/src/lib/components/sequence-preview.d.ts +27 -0
  44. package/src/lib/components/sequence-preview.js +146 -0
  45. package/src/lib/components/sequence-preview.js.map +1 -0
  46. package/src/lib/components/sequence-step-card.d.ts +31 -0
  47. package/src/lib/components/sequence-step-card.js +326 -0
  48. package/src/lib/components/sequence-step-card.js.map +1 -0
  49. package/src/lib/components/sequences-section.d.ts +40 -0
  50. package/src/lib/components/sequences-section.js +310 -0
  51. package/src/lib/components/sequences-section.js.map +1 -0
  52. package/src/lib/components/use-outreach-api.d.ts +38 -0
  53. package/src/lib/components/use-outreach-api.js +139 -0
  54. package/src/lib/components/use-outreach-api.js.map +1 -0
  55. package/src/lib/components/use-outreach-crm.d.ts +50 -0
  56. package/src/lib/components/use-outreach-crm.js +200 -0
  57. package/src/lib/components/use-outreach-crm.js.map +1 -0
  58. package/src/lib/components/use-outreach-data.d.ts +55 -0
  59. package/src/lib/components/use-outreach-data.js +199 -0
  60. package/src/lib/components/use-outreach-data.js.map +1 -0
  61. package/src/lib/components/use-outreach-mailbox-api.d.ts +30 -0
  62. package/src/lib/components/use-outreach-mailbox-api.js +101 -0
  63. package/src/lib/components/use-outreach-mailbox-api.js.map +1 -0
  64. package/src/lib/components/use-outreach-mailboxes.d.ts +22 -0
  65. package/src/lib/components/use-outreach-mailboxes.js +68 -0
  66. package/src/lib/components/use-outreach-mailboxes.js.map +1 -0
  67. package/src/lib/components/use-outreach-settings.d.ts +17 -0
  68. package/src/lib/components/use-outreach-settings.js +64 -0
  69. package/src/lib/components/use-outreach-settings.js.map +1 -0
  70. package/src/lib/constants/api-routes.d.ts +68 -0
  71. package/src/lib/constants/api-routes.js +57 -0
  72. package/src/lib/constants/api-routes.js.map +1 -0
  73. package/src/lib/constants/bundle-common.d.ts +46 -0
  74. package/src/lib/constants/bundle-common.js +55 -0
  75. package/src/lib/constants/bundle-common.js.map +1 -0
  76. package/src/lib/constants/runtime-jobs.d.ts +24 -0
  77. package/src/lib/constants/runtime-jobs.js +24 -0
  78. package/src/lib/constants/runtime-jobs.js.map +1 -0
  79. package/src/lib/declarations.console-server.d.ts +38 -0
  80. package/src/lib/declarations.console-server.js +99 -0
  81. package/src/lib/declarations.console-server.js.map +1 -0
  82. package/src/lib/engine/compose.d.ts +86 -0
  83. package/src/lib/engine/compose.js +212 -0
  84. package/src/lib/engine/compose.js.map +1 -0
  85. package/src/lib/engine/delivery-status.d.ts +53 -0
  86. package/src/lib/engine/delivery-status.js +184 -0
  87. package/src/lib/engine/delivery-status.js.map +1 -0
  88. package/src/lib/engine/do-not-contact.d.ts +18 -0
  89. package/src/lib/engine/do-not-contact.js +34 -0
  90. package/src/lib/engine/do-not-contact.js.map +1 -0
  91. package/src/lib/engine/enrollment-state.d.ts +156 -0
  92. package/src/lib/engine/enrollment-state.js +303 -0
  93. package/src/lib/engine/enrollment-state.js.map +1 -0
  94. package/src/lib/engine/gates.d.ts +125 -0
  95. package/src/lib/engine/gates.js +232 -0
  96. package/src/lib/engine/gates.js.map +1 -0
  97. package/src/lib/engine/index.d.ts +28 -0
  98. package/src/lib/engine/index.js +33 -0
  99. package/src/lib/engine/index.js.map +1 -0
  100. package/src/lib/engine/mailbox-health.d.ts +49 -0
  101. package/src/lib/engine/mailbox-health.js +83 -0
  102. package/src/lib/engine/mailbox-health.js.map +1 -0
  103. package/src/lib/engine/opt-out-intent.d.ts +41 -0
  104. package/src/lib/engine/opt-out-intent.js +220 -0
  105. package/src/lib/engine/opt-out-intent.js.map +1 -0
  106. package/src/lib/engine/recipient-country.d.ts +41 -0
  107. package/src/lib/engine/recipient-country.js +171 -0
  108. package/src/lib/engine/recipient-country.js.map +1 -0
  109. package/src/lib/engine/schedule.d.ts +70 -0
  110. package/src/lib/engine/schedule.js +115 -0
  111. package/src/lib/engine/schedule.js.map +1 -0
  112. package/src/lib/engine/sending-capacity.d.ts +96 -0
  113. package/src/lib/engine/sending-capacity.js +172 -0
  114. package/src/lib/engine/sending-capacity.js.map +1 -0
  115. package/src/lib/engine/sequence-validation.d.ts +90 -0
  116. package/src/lib/engine/sequence-validation.js +291 -0
  117. package/src/lib/engine/sequence-validation.js.map +1 -0
  118. package/src/lib/engine/thread-classification.d.ts +78 -0
  119. package/src/lib/engine/thread-classification.js +224 -0
  120. package/src/lib/engine/thread-classification.js.map +1 -0
  121. package/src/lib/engine/thread-message.d.ts +80 -0
  122. package/src/lib/engine/thread-message.js +143 -0
  123. package/src/lib/engine/thread-message.js.map +1 -0
  124. package/src/lib/enrollment/enroll-people.d.ts +81 -0
  125. package/src/lib/enrollment/enroll-people.js +250 -0
  126. package/src/lib/enrollment/enroll-people.js.map +1 -0
  127. package/src/lib/enrollment/gate-lookups.d.ts +32 -0
  128. package/src/lib/enrollment/gate-lookups.js +154 -0
  129. package/src/lib/enrollment/gate-lookups.js.map +1 -0
  130. package/src/lib/mailboxes/mailbox-api.d.ts +126 -0
  131. package/src/lib/mailboxes/mailbox-api.js +66 -0
  132. package/src/lib/mailboxes/mailbox-api.js.map +1 -0
  133. package/src/lib/mailboxes/mailbox-credentials.d.ts +95 -0
  134. package/src/lib/mailboxes/mailbox-credentials.js +102 -0
  135. package/src/lib/mailboxes/mailbox-credentials.js.map +1 -0
  136. package/src/lib/mailboxes/mailbox-erasure.d.ts +69 -0
  137. package/src/lib/mailboxes/mailbox-erasure.js +148 -0
  138. package/src/lib/mailboxes/mailbox-erasure.js.map +1 -0
  139. package/src/lib/mailboxes/mailbox-gate.d.ts +75 -0
  140. package/src/lib/mailboxes/mailbox-gate.js +98 -0
  141. package/src/lib/mailboxes/mailbox-gate.js.map +1 -0
  142. package/src/lib/mailboxes/mailbox-revoke.d.ts +59 -0
  143. package/src/lib/mailboxes/mailbox-revoke.js +41 -0
  144. package/src/lib/mailboxes/mailbox-revoke.js.map +1 -0
  145. package/src/lib/mailboxes/mailbox-routes.d.ts +119 -0
  146. package/src/lib/mailboxes/mailbox-routes.js +605 -0
  147. package/src/lib/mailboxes/mailbox-routes.js.map +1 -0
  148. package/src/lib/mailboxes/mailbox-settings.d.ts +110 -0
  149. package/src/lib/mailboxes/mailbox-settings.js +212 -0
  150. package/src/lib/mailboxes/mailbox-settings.js.map +1 -0
  151. package/src/lib/mailboxes/mailbox-transport.d.ts +64 -0
  152. package/src/lib/mailboxes/mailbox-transport.js +94 -0
  153. package/src/lib/mailboxes/mailbox-transport.js.map +1 -0
  154. package/src/lib/mailboxes/oauth-redirect.d.ts +41 -0
  155. package/src/lib/mailboxes/oauth-redirect.js +63 -0
  156. package/src/lib/mailboxes/oauth-redirect.js.map +1 -0
  157. package/src/lib/mailboxes/oauth-state.d.ts +152 -0
  158. package/src/lib/mailboxes/oauth-state.js +212 -0
  159. package/src/lib/mailboxes/oauth-state.js.map +1 -0
  160. package/src/lib/mailboxes/outreach-config.d.ts +64 -0
  161. package/src/lib/mailboxes/outreach-config.js +77 -0
  162. package/src/lib/mailboxes/outreach-config.js.map +1 -0
  163. package/src/lib/mailboxes/register-mailbox-routes.d.ts +47 -0
  164. package/src/lib/mailboxes/register-mailbox-routes.js +135 -0
  165. package/src/lib/mailboxes/register-mailbox-routes.js.map +1 -0
  166. package/src/lib/model/compliance-settings.d.ts +78 -0
  167. package/src/lib/model/compliance-settings.js +155 -0
  168. package/src/lib/model/compliance-settings.js.map +1 -0
  169. package/src/lib/model/outreach-api.d.ts +210 -0
  170. package/src/lib/model/outreach-api.js +28 -0
  171. package/src/lib/model/outreach-api.js.map +1 -0
  172. package/src/lib/model/outreach.types.d.ts +563 -0
  173. package/src/lib/model/outreach.types.js +225 -0
  174. package/src/lib/model/outreach.types.js.map +1 -0
  175. package/src/lib/model/sequence-draft.d.ts +84 -0
  176. package/src/lib/model/sequence-draft.js +239 -0
  177. package/src/lib/model/sequence-draft.js.map +1 -0
  178. package/src/lib/model/step-preview.d.ts +45 -0
  179. package/src/lib/model/step-preview.js +98 -0
  180. package/src/lib/model/step-preview.js.map +1 -0
  181. package/src/lib/model/stored-records.d.ts +23 -0
  182. package/src/lib/model/stored-records.js +84 -0
  183. package/src/lib/model/stored-records.js.map +1 -0
  184. package/src/lib/plugin.d.ts +46 -0
  185. package/src/lib/plugin.js +86 -0
  186. package/src/lib/plugin.js.map +1 -0
  187. package/src/lib/routes/enroll-routes.d.ts +64 -0
  188. package/src/lib/routes/enroll-routes.js +350 -0
  189. package/src/lib/routes/enroll-routes.js.map +1 -0
  190. package/src/lib/routes/enrollment-routes.d.ts +19 -0
  191. package/src/lib/routes/enrollment-routes.js +171 -0
  192. package/src/lib/routes/enrollment-routes.js.map +1 -0
  193. package/src/lib/routes/platform-deps.d.ts +51 -0
  194. package/src/lib/routes/platform-deps.js +66 -0
  195. package/src/lib/routes/platform-deps.js.map +1 -0
  196. package/src/lib/routes/preview-routes.d.ts +33 -0
  197. package/src/lib/routes/preview-routes.js +117 -0
  198. package/src/lib/routes/preview-routes.js.map +1 -0
  199. package/src/lib/routes/register-routes.d.ts +22 -0
  200. package/src/lib/routes/register-routes.js +92 -0
  201. package/src/lib/routes/register-routes.js.map +1 -0
  202. package/src/lib/routes/route-deps.d.ts +49 -0
  203. package/src/lib/routes/route-deps.js +27 -0
  204. package/src/lib/routes/route-deps.js.map +1 -0
  205. package/src/lib/routes/route-gate.d.ts +94 -0
  206. package/src/lib/routes/route-gate.js +87 -0
  207. package/src/lib/routes/route-gate.js.map +1 -0
  208. package/src/lib/routes/route-http.d.ts +32 -0
  209. package/src/lib/routes/route-http.js +62 -0
  210. package/src/lib/routes/route-http.js.map +1 -0
  211. package/src/lib/routes/sequence-routes.d.ts +32 -0
  212. package/src/lib/routes/sequence-routes.js +359 -0
  213. package/src/lib/routes/sequence-routes.js.map +1 -0
  214. package/src/lib/routes/settings-routes.d.ts +30 -0
  215. package/src/lib/routes/settings-routes.js +73 -0
  216. package/src/lib/routes/settings-routes.js.map +1 -0
  217. package/src/lib/runtime/enrollment-events.d.ts +86 -0
  218. package/src/lib/runtime/enrollment-events.js +141 -0
  219. package/src/lib/runtime/enrollment-events.js.map +1 -0
  220. package/src/lib/runtime/fixtures/fake-gmail.d.ts +121 -0
  221. package/src/lib/runtime/fixtures/fake-gmail.js +312 -0
  222. package/src/lib/runtime/fixtures/fake-gmail.js.map +1 -0
  223. package/src/lib/runtime/mailbox-health-store.d.ts +47 -0
  224. package/src/lib/runtime/mailbox-health-store.js +168 -0
  225. package/src/lib/runtime/mailbox-health-store.js.map +1 -0
  226. package/src/lib/runtime/person-erasure.d.ts +39 -0
  227. package/src/lib/runtime/person-erasure.js +57 -0
  228. package/src/lib/runtime/person-erasure.js.map +1 -0
  229. package/src/lib/runtime/platform-runtime-deps.d.ts +29 -0
  230. package/src/lib/runtime/platform-runtime-deps.js +98 -0
  231. package/src/lib/runtime/platform-runtime-deps.js.map +1 -0
  232. package/src/lib/runtime/runtime-deps.d.ts +77 -0
  233. package/src/lib/runtime/runtime-deps.js +18 -0
  234. package/src/lib/runtime/runtime-deps.js.map +1 -0
  235. package/src/lib/runtime/send-job.d.ts +42 -0
  236. package/src/lib/runtime/send-job.js +780 -0
  237. package/src/lib/runtime/send-job.js.map +1 -0
  238. package/src/lib/runtime/sync-job.d.ts +41 -0
  239. package/src/lib/runtime/sync-job.js +480 -0
  240. package/src/lib/runtime/sync-job.js.map +1 -0
  241. package/src/lib/runtime/timeline.d.ts +67 -0
  242. package/src/lib/runtime/timeline.js +93 -0
  243. package/src/lib/runtime/timeline.js.map +1 -0
  244. package/src/lib/runtime/unsubscribe-link.d.ts +75 -0
  245. package/src/lib/runtime/unsubscribe-link.js +121 -0
  246. package/src/lib/runtime/unsubscribe-link.js.map +1 -0
  247. package/src/lib/runtime/unsubscribe-route.d.ts +24 -0
  248. package/src/lib/runtime/unsubscribe-route.js +103 -0
  249. package/src/lib/runtime/unsubscribe-route.js.map +1 -0
  250. package/src/lib/server.d.ts +53 -0
  251. package/src/lib/server.js +94 -0
  252. package/src/lib/server.js.map +1 -0
  253. package/src/lib/storage/compliance-settings-store.d.ts +44 -0
  254. package/src/lib/storage/compliance-settings-store.js +78 -0
  255. package/src/lib/storage/compliance-settings-store.js.map +1 -0
  256. package/src/lib/storage/do-not-contact-store.d.ts +60 -0
  257. package/src/lib/storage/do-not-contact-store.js +168 -0
  258. package/src/lib/storage/do-not-contact-store.js.map +1 -0
  259. package/src/lib/storage/outreach-records.d.ts +33 -0
  260. package/src/lib/storage/outreach-records.js +35 -0
  261. package/src/lib/storage/outreach-records.js.map +1 -0
  262. package/src/lib/subprocessors.d.ts +48 -0
  263. package/src/lib/subprocessors.js +70 -0
  264. package/src/lib/subprocessors.js.map +1 -0
  265. package/src/lib/transport/gmail-client.d.ts +145 -0
  266. package/src/lib/transport/gmail-client.js +229 -0
  267. package/src/lib/transport/gmail-client.js.map +1 -0
  268. package/src/lib/transport/gmail-errors.d.ts +84 -0
  269. package/src/lib/transport/gmail-errors.js +138 -0
  270. package/src/lib/transport/gmail-errors.js.map +1 -0
  271. package/src/lib/transport/google-oauth.d.ts +138 -0
  272. package/src/lib/transport/google-oauth.js +247 -0
  273. package/src/lib/transport/google-oauth.js.map +1 -0
  274. package/src/lib/transport/http.d.ts +53 -0
  275. package/src/lib/transport/http.js +79 -0
  276. package/src/lib/transport/http.js.map +1 -0
  277. package/src/lib/transport/rfc5322.d.ts +116 -0
  278. package/src/lib/transport/rfc5322.js +330 -0
  279. package/src/lib/transport/rfc5322.js.map +1 -0
  280. package/src/lib/transport/send-message.d.ts +69 -0
  281. package/src/lib/transport/send-message.js +77 -0
  282. package/src/lib/transport/send-message.js.map +1 -0
@@ -0,0 +1,64 @@
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 SecretBoxKeyring } from '@aglyn/shared-util-tools/secret-box';
18
+ /**
19
+ * THE ONE MODULE THAT READS OUTREACH'S CREDENTIALS FROM THE ENVIRONMENT
20
+ * (AGL-2978).
21
+ *
22
+ * - `GOOGLE_OUTREACH_CLIENT_ID` and `GOOGLE_OUTREACH_CLIENT_SECRET` — the
23
+ * OAuth client a rep's Google account grants mailbox access to.
24
+ * - `OUTREACH_TOKEN_KEY` — 32 random bytes, base64, sealing every stored
25
+ * refresh token. A comma-separated list rotates: the first key seals, the
26
+ * rest only open, and a token opened under an older key is sealed again
27
+ * under the first.
28
+ *
29
+ * ## Console-only, structurally
30
+ *
31
+ * The client secret and the token key together are the power to send as
32
+ * every connected rep, so neither may be reachable from the tenant runtime,
33
+ * which serves the public internet. They are read here and nowhere else; this
34
+ * module is imported only by Outreach's server half and its console-only
35
+ * declarations; and those are loaded only by the console's server manifests.
36
+ * `outreach-credential-isolation.spec.ts`, beside this plugin's other specs,
37
+ * holds all three facts, the way `sending-domain-credential-isolation.spec.ts`
38
+ * holds the Resend domains key's.
39
+ *
40
+ * Read in the bracket form, so Next never inlines a value into a build.
41
+ */
42
+ /** The env vars, named once for the refusal that lists what is missing. */
43
+ export declare const OUTREACH_ENV: {
44
+ readonly clientId: "GOOGLE_OUTREACH_CLIENT_ID";
45
+ readonly clientSecret: "GOOGLE_OUTREACH_CLIENT_SECRET";
46
+ readonly tokenKey: "OUTREACH_TOKEN_KEY";
47
+ };
48
+ export interface OutreachGoogleConfig {
49
+ clientId: string;
50
+ clientSecret: string;
51
+ keyring: SecretBoxKeyring;
52
+ }
53
+ export type OutreachGoogleConfigResult = {
54
+ configured: true;
55
+ config: OutreachGoogleConfig;
56
+ } | {
57
+ configured: false;
58
+ /** The env var names that are unset or unusable — never their values. */
59
+ missing: string[];
60
+ };
61
+ /** The sentence every route and the panel use for an unconfigured deployment. */
62
+ export declare const OUTREACH_NOT_CONFIGURED_MESSAGE = "Connecting a Google mailbox is not configured on this deployment.";
63
+ /** Reads the three variables. Never throws; an unusable key counts as missing. */
64
+ export declare function readOutreachGoogleConfig(): OutreachGoogleConfigResult;
@@ -0,0 +1,77 @@
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 { parseSecretBoxKeyring } from "@aglyn/shared-util-tools/secret-box";
17
+ /**
18
+ * THE ONE MODULE THAT READS OUTREACH'S CREDENTIALS FROM THE ENVIRONMENT
19
+ * (AGL-2978).
20
+ *
21
+ * - `GOOGLE_OUTREACH_CLIENT_ID` and `GOOGLE_OUTREACH_CLIENT_SECRET` — the
22
+ * OAuth client a rep's Google account grants mailbox access to.
23
+ * - `OUTREACH_TOKEN_KEY` — 32 random bytes, base64, sealing every stored
24
+ * refresh token. A comma-separated list rotates: the first key seals, the
25
+ * rest only open, and a token opened under an older key is sealed again
26
+ * under the first.
27
+ *
28
+ * ## Console-only, structurally
29
+ *
30
+ * The client secret and the token key together are the power to send as
31
+ * every connected rep, so neither may be reachable from the tenant runtime,
32
+ * which serves the public internet. They are read here and nowhere else; this
33
+ * module is imported only by Outreach's server half and its console-only
34
+ * declarations; and those are loaded only by the console's server manifests.
35
+ * `outreach-credential-isolation.spec.ts`, beside this plugin's other specs,
36
+ * holds all three facts, the way `sending-domain-credential-isolation.spec.ts`
37
+ * holds the Resend domains key's.
38
+ *
39
+ * Read in the bracket form, so Next never inlines a value into a build.
40
+ */ /** The env vars, named once for the refusal that lists what is missing. */ export const OUTREACH_ENV = {
41
+ clientId: 'GOOGLE_OUTREACH_CLIENT_ID',
42
+ clientSecret: 'GOOGLE_OUTREACH_CLIENT_SECRET',
43
+ tokenKey: 'OUTREACH_TOKEN_KEY'
44
+ };
45
+ /** The sentence every route and the panel use for an unconfigured deployment. */ export const OUTREACH_NOT_CONFIGURED_MESSAGE = 'Connecting a Google mailbox is not configured on this deployment.';
46
+ /** Reads the three variables. Never throws; an unusable key counts as missing. */ export function readOutreachGoogleConfig() {
47
+ var _process_env_GOOGLE_OUTREACH_CLIENT_ID, _process_env_GOOGLE_OUTREACH_CLIENT_SECRET, _process_env_OUTREACH_TOKEN_KEY;
48
+ const clientId = String((_process_env_GOOGLE_OUTREACH_CLIENT_ID = process.env['GOOGLE_OUTREACH_CLIENT_ID']) != null ? _process_env_GOOGLE_OUTREACH_CLIENT_ID : '').trim();
49
+ const clientSecret = String((_process_env_GOOGLE_OUTREACH_CLIENT_SECRET = process.env['GOOGLE_OUTREACH_CLIENT_SECRET']) != null ? _process_env_GOOGLE_OUTREACH_CLIENT_SECRET : '').trim();
50
+ const tokenKey = String((_process_env_OUTREACH_TOKEN_KEY = process.env['OUTREACH_TOKEN_KEY']) != null ? _process_env_OUTREACH_TOKEN_KEY : '').trim();
51
+ const missing = [];
52
+ if (!clientId) missing.push(OUTREACH_ENV.clientId);
53
+ if (!clientSecret) missing.push(OUTREACH_ENV.clientSecret);
54
+ let keyring = null;
55
+ if (tokenKey) {
56
+ try {
57
+ keyring = parseSecretBoxKeyring(tokenKey);
58
+ } catch (unused) {
59
+ keyring = null;
60
+ }
61
+ }
62
+ if (!keyring) missing.push(OUTREACH_ENV.tokenKey);
63
+ if (missing.length || !keyring) return {
64
+ configured: false,
65
+ missing
66
+ };
67
+ return {
68
+ configured: true,
69
+ config: {
70
+ clientId,
71
+ clientSecret,
72
+ keyring
73
+ }
74
+ };
75
+ }
76
+
77
+ //# sourceMappingURL=outreach-config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/outreach/src/lib/mailboxes/outreach-config.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 {\n parseSecretBoxKeyring,\n type SecretBoxKeyring,\n} from '@aglyn/shared-util-tools/secret-box'\n\n/**\n * THE ONE MODULE THAT READS OUTREACH'S CREDENTIALS FROM THE ENVIRONMENT\n * (AGL-2978).\n *\n * - `GOOGLE_OUTREACH_CLIENT_ID` and `GOOGLE_OUTREACH_CLIENT_SECRET` — the\n * OAuth client a rep's Google account grants mailbox access to.\n * - `OUTREACH_TOKEN_KEY` — 32 random bytes, base64, sealing every stored\n * refresh token. A comma-separated list rotates: the first key seals, the\n * rest only open, and a token opened under an older key is sealed again\n * under the first.\n *\n * ## Console-only, structurally\n *\n * The client secret and the token key together are the power to send as\n * every connected rep, so neither may be reachable from the tenant runtime,\n * which serves the public internet. They are read here and nowhere else; this\n * module is imported only by Outreach's server half and its console-only\n * declarations; and those are loaded only by the console's server manifests.\n * `outreach-credential-isolation.spec.ts`, beside this plugin's other specs,\n * holds all three facts, the way `sending-domain-credential-isolation.spec.ts`\n * holds the Resend domains key's.\n *\n * Read in the bracket form, so Next never inlines a value into a build.\n */\n\n/** The env vars, named once for the refusal that lists what is missing. */\nexport const OUTREACH_ENV = {\n clientId: 'GOOGLE_OUTREACH_CLIENT_ID',\n clientSecret: 'GOOGLE_OUTREACH_CLIENT_SECRET',\n tokenKey: 'OUTREACH_TOKEN_KEY',\n} as const\n\nexport interface OutreachGoogleConfig {\n clientId: string\n clientSecret: string\n keyring: SecretBoxKeyring\n}\n\nexport type OutreachGoogleConfigResult =\n | { configured: true; config: OutreachGoogleConfig }\n | {\n configured: false\n /** The env var names that are unset or unusable — never their values. */\n missing: string[]\n }\n\n/** The sentence every route and the panel use for an unconfigured deployment. */\nexport const OUTREACH_NOT_CONFIGURED_MESSAGE =\n 'Connecting a Google mailbox is not configured on this deployment.'\n\n/** Reads the three variables. Never throws; an unusable key counts as missing. */\nexport function readOutreachGoogleConfig(): OutreachGoogleConfigResult {\n const clientId = String(process.env['GOOGLE_OUTREACH_CLIENT_ID'] ?? '').trim()\n const clientSecret = String(process.env['GOOGLE_OUTREACH_CLIENT_SECRET'] ?? '').trim()\n const tokenKey = String(process.env['OUTREACH_TOKEN_KEY'] ?? '').trim()\n\n const missing: string[] = []\n if (!clientId) missing.push(OUTREACH_ENV.clientId)\n if (!clientSecret) missing.push(OUTREACH_ENV.clientSecret)\n let keyring: SecretBoxKeyring | null = null\n if (tokenKey) {\n try {\n keyring = parseSecretBoxKeyring(tokenKey)\n } catch {\n keyring = null\n }\n }\n if (!keyring) missing.push(OUTREACH_ENV.tokenKey)\n\n if (missing.length || !keyring) return { configured: false, missing }\n return { configured: true, config: { clientId, clientSecret, keyring } }\n}\n"],"names":["parseSecretBoxKeyring","OUTREACH_ENV","clientId","clientSecret","tokenKey","OUTREACH_NOT_CONFIGURED_MESSAGE","readOutreachGoogleConfig","process","String","env","trim","missing","push","keyring","length","configured","config"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED,SACEA,qBAAqB,QAEhB,sCAAqC;AAE5C;;;;;;;;;;;;;;;;;;;;;;;CAuBC,GAED,yEAAyE,GACzE,OAAO,MAAMC,eAAe;IAC1BC,UAAU;IACVC,cAAc;IACdC,UAAU;AACZ,EAAU;AAgBV,+EAA+E,GAC/E,OAAO,MAAMC,kCACX,oEAAmE;AAErE,gFAAgF,GAChF,OAAO,SAASC;QACUC,wCACIA,4CACJA;IAFxB,MAAML,WAAWM,QAAOD,yCAAAA,QAAQE,GAAG,CAAC,4BAA4B,YAAxCF,yCAA4C,IAAIG,IAAI;IAC5E,MAAMP,eAAeK,QAAOD,6CAAAA,QAAQE,GAAG,CAAC,gCAAgC,YAA5CF,6CAAgD,IAAIG,IAAI;IACpF,MAAMN,WAAWI,QAAOD,kCAAAA,QAAQE,GAAG,CAAC,qBAAqB,YAAjCF,kCAAqC,IAAIG,IAAI;IAErE,MAAMC,UAAoB,EAAE;IAC5B,IAAI,CAACT,UAAUS,QAAQC,IAAI,CAACX,aAAaC,QAAQ;IACjD,IAAI,CAACC,cAAcQ,QAAQC,IAAI,CAACX,aAAaE,YAAY;IACzD,IAAIU,UAAmC;IACvC,IAAIT,UAAU;QACZ,IAAI;YACFS,UAAUb,sBAAsBI;QAClC,EAAE,eAAM;YACNS,UAAU;QACZ;IACF;IACA,IAAI,CAACA,SAASF,QAAQC,IAAI,CAACX,aAAaG,QAAQ;IAEhD,IAAIO,QAAQG,MAAM,IAAI,CAACD,SAAS,OAAO;QAAEE,YAAY;QAAOJ;IAAQ;IACpE,OAAO;QAAEI,YAAY;QAAMC,QAAQ;YAAEd;YAAUC;YAAcU;QAAQ;IAAE;AACzE"}
@@ -0,0 +1,47 @@
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 PluginApiRequestSubject } from '@aglyn/aglyn/server';
18
+ import { type OutreachMailboxRouteDeps } from './mailbox-routes';
19
+ /**
20
+ * Wires the mailbox routes into the console dispatcher (AGL-2978), with the
21
+ * production dependencies and each route's release subject.
22
+ */
23
+ /**
24
+ * The platform's own dependencies. Specs build their own.
25
+ *
26
+ * The permission resolver, the lockdown verdict, the rate limiter, the
27
+ * activity log and the alias store are imported when a request first needs
28
+ * them rather than when the bundle registers: `org-permissions` reaches the
29
+ * whole tenant data barrel, and the manifest loads this bundle into every
30
+ * console API process whether or not a mailbox route is ever called.
31
+ */
32
+ export declare function defaultOutreachMailboxRouteDeps(): OutreachMailboxRouteDeps;
33
+ /**
34
+ * The org an authenticated mailbox request names — query or JSON body — for
35
+ * the release gate. Unverified, exactly as a `hostId` is: the gate only
36
+ * chooses which organization's rollout to read, and the route's own gate
37
+ * refuses anyone who is not a member of it.
38
+ */
39
+ export declare function outreachOrgSubject(request: Request): Promise<PluginApiRequestSubject | null>;
40
+ /**
41
+ * The org AND member Google's redirect is for, read off the signed state.
42
+ * Only a state whose signature verifies names anyone; an expired one still
43
+ * does, so the rep who took too long lands on a page that says so rather than
44
+ * on a 404.
45
+ */
46
+ export declare function outreachOAuthCallbackSubject(request: Request): PluginApiRequestSubject | null;
47
+ export declare function registerOutreachMailboxRoutes(deps?: OutreachMailboxRouteDeps): void;
@@ -0,0 +1,135 @@
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 { registerPluginApiRoute } from "@aglyn/aglyn/server";
17
+ import { firebaseAdmin } from "@aglyn/tenant-data-admin/server/firebase-admin";
18
+ import { tokenSigningSecret } from "@aglyn/tenant-data-admin/server/media-signing";
19
+ import { OUTREACH_API_ROUTES } from "../constants/api-routes.js";
20
+ import { readOrgId } from "./mailbox-gate.js";
21
+ import { createOutreachMailboxRoutes } from "./mailbox-routes.js";
22
+ import { readOutreachOAuthState } from "./oauth-state.js";
23
+ import { outreachOAuthRedirectUri } from "./oauth-redirect.js";
24
+ import { readOutreachGoogleConfig } from "./outreach-config.js";
25
+ /**
26
+ * Wires the mailbox routes into the console dispatcher (AGL-2978), with the
27
+ * production dependencies and each route's release subject.
28
+ */ /**
29
+ * The platform's own dependencies. Specs build their own.
30
+ *
31
+ * The permission resolver, the lockdown verdict, the rate limiter, the
32
+ * activity log and the alias store are imported when a request first needs
33
+ * them rather than when the bundle registers: `org-permissions` reaches the
34
+ * whole tenant data barrel, and the manifest loads this bundle into every
35
+ * console API process whether or not a mailbox route is ever called.
36
+ */ export function defaultOutreachMailboxRouteDeps() {
37
+ return {
38
+ firestore: ()=>firebaseAdmin.app().firestore(),
39
+ gate: {
40
+ verifyIdToken: (idToken)=>firebaseAdmin.app().auth().verifyIdToken(idToken),
41
+ resolveOrgPermissions: async (uid, context)=>(await import("@aglyn/tenant-runtime/org-permissions")).resolveOrgPermissions(uid, context),
42
+ readOrg: async (orgId)=>{
43
+ var _snapshot_data;
44
+ const snapshot = await firebaseAdmin.app().firestore().collection('orgs').doc(orgId).get();
45
+ return snapshot.exists ? (_snapshot_data = snapshot.data()) != null ? _snapshot_data : {} : null;
46
+ },
47
+ lockdownRefusal: async (options)=>(await import("@aglyn/tenant-data-admin/server/lockdown")).lockdownRefusal(options)
48
+ },
49
+ readConfig: readOutreachGoogleConfig,
50
+ stateSigningConfigured: ()=>{
51
+ try {
52
+ return Boolean(tokenSigningSecret());
53
+ } catch (unused) {
54
+ return false;
55
+ }
56
+ },
57
+ redirectUri: outreachOAuthRedirectUri,
58
+ now: Date.now,
59
+ transport: {},
60
+ consumeRateLimit: async (key, options)=>(await import("@aglyn/tenant-data-admin/server/rate-limit-store")).consumeRateLimit(key, options),
61
+ logOrgActivity: async (orgId, actor, action, target)=>(await import("@aglyn/tenant-data-admin/server/organizations")).logOrgActivity(orgId, actor, action, target),
62
+ confirmAliasesByProvider: async (firestore, input)=>(await import("@aglyn/tenant-data-admin/server/member-email-aliases")).confirmMemberEmailAliasesByProvider(firestore, input)
63
+ };
64
+ }
65
+ /**
66
+ * The org an authenticated mailbox request names — query or JSON body — for
67
+ * the release gate. Unverified, exactly as a `hostId` is: the gate only
68
+ * chooses which organization's rollout to read, and the route's own gate
69
+ * refuses anyone who is not a member of it.
70
+ */ export async function outreachOrgSubject(request) {
71
+ const fromQuery = readOrgId(new URL(request.url).searchParams.get('orgId'));
72
+ if (fromQuery) return {
73
+ orgId: fromQuery
74
+ };
75
+ if (request.method === 'GET' || request.method === 'HEAD') return null;
76
+ const body = await request.json().catch(()=>null);
77
+ const orgId = readOrgId(body == null ? void 0 : body.orgId);
78
+ return orgId ? {
79
+ orgId
80
+ } : null;
81
+ }
82
+ /**
83
+ * The org AND member Google's redirect is for, read off the signed state.
84
+ * Only a state whose signature verifies names anyone; an expired one still
85
+ * does, so the rep who took too long lands on a page that says so rather than
86
+ * on a 404.
87
+ */ export function outreachOAuthCallbackSubject(request) {
88
+ const read = readOutreachOAuthState(new URL(request.url).searchParams.get('state'), Date.now());
89
+ if (read.ok) return {
90
+ orgId: read.claims.orgId,
91
+ uid: read.claims.uid
92
+ };
93
+ if (read.ok === false && read.refusal === 'state-expired') {
94
+ const { claims } = read;
95
+ return {
96
+ orgId: claims.orgId,
97
+ uid: claims.uid
98
+ };
99
+ }
100
+ return null;
101
+ }
102
+ export function registerOutreachMailboxRoutes(deps = defaultOutreachMailboxRouteDeps()) {
103
+ const routes = createOutreachMailboxRoutes(deps);
104
+ const orgSubject = {
105
+ subject: outreachOrgSubject
106
+ };
107
+ registerPluginApiRoute(OUTREACH_API_ROUTES.mailboxesAvailability, {
108
+ web: routes.availability
109
+ }, orgSubject);
110
+ registerPluginApiRoute(OUTREACH_API_ROUTES.mailboxesConnect, {
111
+ web: routes.connect
112
+ }, orgSubject);
113
+ registerPluginApiRoute(OUTREACH_API_ROUTES.mailboxesOAuthCallback, {
114
+ web: routes.oauthCallback
115
+ }, {
116
+ subject: outreachOAuthCallbackSubject
117
+ });
118
+ registerPluginApiRoute(OUTREACH_API_ROUTES.mailboxesConnectComplete, {
119
+ web: routes.connectComplete
120
+ }, orgSubject);
121
+ registerPluginApiRoute(OUTREACH_API_ROUTES.mailboxesSettings, {
122
+ web: routes.settings
123
+ }, orgSubject);
124
+ registerPluginApiRoute(OUTREACH_API_ROUTES.mailboxesStatus, {
125
+ web: routes.status
126
+ }, orgSubject);
127
+ registerPluginApiRoute(OUTREACH_API_ROUTES.mailboxesTest, {
128
+ web: routes.test
129
+ }, orgSubject);
130
+ registerPluginApiRoute(OUTREACH_API_ROUTES.mailboxesDisconnect, {
131
+ web: routes.disconnect
132
+ }, orgSubject);
133
+ }
134
+
135
+ //# sourceMappingURL=register-mailbox-routes.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/outreach/src/lib/mailboxes/register-mailbox-routes.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 {\n registerPluginApiRoute,\n type PluginApiRequestSubject,\n} from '@aglyn/aglyn/server'\nimport { firebaseAdmin } from '@aglyn/tenant-data-admin/server/firebase-admin'\nimport { tokenSigningSecret } from '@aglyn/tenant-data-admin/server/media-signing'\nimport { OUTREACH_API_ROUTES } from '../constants/api-routes'\nimport { readOrgId } from './mailbox-gate'\nimport {\n createOutreachMailboxRoutes,\n type OutreachMailboxRouteDeps,\n} from './mailbox-routes'\nimport { readOutreachOAuthState } from './oauth-state'\nimport { outreachOAuthRedirectUri } from './oauth-redirect'\nimport { readOutreachGoogleConfig } from './outreach-config'\n\n/**\n * Wires the mailbox routes into the console dispatcher (AGL-2978), with the\n * production dependencies and each route's release subject.\n */\n\n/**\n * The platform's own dependencies. Specs build their own.\n *\n * The permission resolver, the lockdown verdict, the rate limiter, the\n * activity log and the alias store are imported when a request first needs\n * them rather than when the bundle registers: `org-permissions` reaches the\n * whole tenant data barrel, and the manifest loads this bundle into every\n * console API process whether or not a mailbox route is ever called.\n */\nexport function defaultOutreachMailboxRouteDeps(): OutreachMailboxRouteDeps {\n return {\n firestore: () => firebaseAdmin.app().firestore(),\n gate: {\n verifyIdToken: (idToken) => firebaseAdmin.app().auth().verifyIdToken(idToken),\n resolveOrgPermissions: async (uid, context) =>\n (await import('@aglyn/tenant-runtime/org-permissions')).resolveOrgPermissions(uid, context),\n readOrg: async (orgId) => {\n const snapshot = await firebaseAdmin.app().firestore().collection('orgs').doc(orgId).get()\n return snapshot.exists ? (snapshot.data() ?? {}) : null\n },\n lockdownRefusal: async (options) =>\n (await import('@aglyn/tenant-data-admin/server/lockdown')).lockdownRefusal(options),\n },\n readConfig: readOutreachGoogleConfig,\n stateSigningConfigured: () => {\n try {\n return Boolean(tokenSigningSecret())\n } catch {\n return false\n }\n },\n redirectUri: outreachOAuthRedirectUri,\n now: Date.now,\n transport: {},\n consumeRateLimit: async (key, options) =>\n (await import('@aglyn/tenant-data-admin/server/rate-limit-store')).consumeRateLimit(key, options),\n logOrgActivity: async (orgId, actor, action, target) =>\n (await import('@aglyn/tenant-data-admin/server/organizations')).logOrgActivity(orgId, actor, action, target),\n confirmAliasesByProvider: async (firestore, input) =>\n (await import('@aglyn/tenant-data-admin/server/member-email-aliases')).confirmMemberEmailAliasesByProvider(\n firestore,\n input,\n ),\n }\n}\n\n/**\n * The org an authenticated mailbox request names — query or JSON body — for\n * the release gate. Unverified, exactly as a `hostId` is: the gate only\n * chooses which organization's rollout to read, and the route's own gate\n * refuses anyone who is not a member of it.\n */\nexport async function outreachOrgSubject(request: Request): Promise<PluginApiRequestSubject | null> {\n const fromQuery = readOrgId(new URL(request.url).searchParams.get('orgId'))\n if (fromQuery) return { orgId: fromQuery }\n if (request.method === 'GET' || request.method === 'HEAD') return null\n const body = (await request.json().catch(() => null)) as { orgId?: unknown } | null\n const orgId = readOrgId(body?.orgId)\n return orgId ? { orgId } : null\n}\n\n/**\n * The org AND member Google's redirect is for, read off the signed state.\n * Only a state whose signature verifies names anyone; an expired one still\n * does, so the rep who took too long lands on a page that says so rather than\n * on a 404.\n */\nexport function outreachOAuthCallbackSubject(request: Request): PluginApiRequestSubject | null {\n const read = readOutreachOAuthState(new URL(request.url).searchParams.get('state'), Date.now())\n if (read.ok) return { orgId: read.claims.orgId, uid: read.claims.uid }\n if (read.ok === false && read.refusal === 'state-expired') {\n const { claims } = read as { claims: { orgId: string; uid: string } }\n return { orgId: claims.orgId, uid: claims.uid }\n }\n return null\n}\n\nexport function registerOutreachMailboxRoutes(\n deps: OutreachMailboxRouteDeps = defaultOutreachMailboxRouteDeps(),\n): void {\n const routes = createOutreachMailboxRoutes(deps)\n const orgSubject = { subject: outreachOrgSubject }\n registerPluginApiRoute(OUTREACH_API_ROUTES.mailboxesAvailability, { web: routes.availability }, orgSubject)\n registerPluginApiRoute(OUTREACH_API_ROUTES.mailboxesConnect, { web: routes.connect }, orgSubject)\n registerPluginApiRoute(\n OUTREACH_API_ROUTES.mailboxesOAuthCallback,\n { web: routes.oauthCallback },\n { subject: outreachOAuthCallbackSubject },\n )\n registerPluginApiRoute(OUTREACH_API_ROUTES.mailboxesConnectComplete, { web: routes.connectComplete }, orgSubject)\n registerPluginApiRoute(OUTREACH_API_ROUTES.mailboxesSettings, { web: routes.settings }, orgSubject)\n registerPluginApiRoute(OUTREACH_API_ROUTES.mailboxesStatus, { web: routes.status }, orgSubject)\n registerPluginApiRoute(OUTREACH_API_ROUTES.mailboxesTest, { web: routes.test }, orgSubject)\n registerPluginApiRoute(OUTREACH_API_ROUTES.mailboxesDisconnect, { web: routes.disconnect }, orgSubject)\n}\n"],"names":["registerPluginApiRoute","firebaseAdmin","tokenSigningSecret","OUTREACH_API_ROUTES","readOrgId","createOutreachMailboxRoutes","readOutreachOAuthState","outreachOAuthRedirectUri","readOutreachGoogleConfig","defaultOutreachMailboxRouteDeps","firestore","app","gate","verifyIdToken","idToken","auth","resolveOrgPermissions","uid","context","readOrg","orgId","snapshot","collection","doc","get","exists","data","lockdownRefusal","options","readConfig","stateSigningConfigured","Boolean","redirectUri","now","Date","transport","consumeRateLimit","key","logOrgActivity","actor","action","target","confirmAliasesByProvider","input","confirmMemberEmailAliasesByProvider","outreachOrgSubject","request","fromQuery","URL","url","searchParams","method","body","json","catch","outreachOAuthCallbackSubject","read","ok","claims","refusal","registerOutreachMailboxRoutes","deps","routes","orgSubject","subject","mailboxesAvailability","web","availability","mailboxesConnect","connect","mailboxesOAuthCallback","oauthCallback","mailboxesConnectComplete","connectComplete","mailboxesSettings","settings","mailboxesStatus","status","mailboxesTest","test","mailboxesDisconnect","disconnect"],"mappings":"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"}
@@ -0,0 +1,78 @@
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 OutreachComplianceSettings, type OutreachComplianceSettingsDocument } from './outreach.types';
18
+ /** The longest legal name the footer prints. */
19
+ export declare const OUTREACH_LEGAL_NAME_MAX = 120;
20
+ /** The longest brand name the solicitation sentence uses. */
21
+ export declare const OUTREACH_BRAND_NAME_MAX = 120;
22
+ /** The longest postal address, all of its lines together. */
23
+ export declare const OUTREACH_POSTAL_ADDRESS_MAX = 300;
24
+ /** The most lines a postal address is written over. */
25
+ export declare const OUTREACH_POSTAL_ADDRESS_MAX_LINES = 6;
26
+ /**
27
+ * Every ISO-3166-1 alpha-2 code assigned to a country or territory, sorted.
28
+ *
29
+ * A fixed list rather than whatever `Intl` can name: `Intl.DisplayNames`
30
+ * also names `EU`, `UN`, `ZZ` and retired codes such as `YU`, none of which
31
+ * a recipient's address can be in.
32
+ */
33
+ export declare const OUTREACH_COUNTRY_CODES: readonly string[];
34
+ /** Whether `code` is an assigned country code, uppercase. */
35
+ export declare function isOutreachCountryCode(code: unknown): boolean;
36
+ /**
37
+ * A country's English name as a list shows it — "United States", "Canada" —
38
+ * or the code where the runtime cannot name regions.
39
+ */
40
+ export declare function outreachCountryLabel(code: string): string;
41
+ /** One country as the picker offers it. */
42
+ export interface OutreachCountryOption {
43
+ code: string;
44
+ name: string;
45
+ }
46
+ /** Every country, by name, for the picker. */
47
+ export declare function outreachCountryOptions(): OutreachCountryOption[];
48
+ /**
49
+ * A stored compliance document read into its shape, defaults applied: the
50
+ * United States alone when no countries were ever saved. A list that was
51
+ * saved is read as saved, codes that are not countries dropped, so an
52
+ * emptied list reads as nobody rather than as the default.
53
+ */
54
+ export declare function readOutreachComplianceSettings(raw: unknown): OutreachComplianceSettingsDocument;
55
+ /** A field the Compliance page shows an issue beside. */
56
+ export type OutreachComplianceField = 'legalName' | 'brandName' | 'postalAddress' | 'allowedCountries';
57
+ export interface OutreachComplianceIssue {
58
+ field: OutreachComplianceField;
59
+ message: string;
60
+ }
61
+ export interface OutreachComplianceValidation {
62
+ /** The settings as they would be stored: trimmed, one-lined, codes uppercased. */
63
+ settings: OutreachComplianceSettings;
64
+ /** Every reason the save is refused; empty when it may be saved. */
65
+ issues: OutreachComplianceIssue[];
66
+ }
67
+ /**
68
+ * What a save stores, and every reason it is refused — see the module note
69
+ * for why an empty legal name or postal address is not one of them.
70
+ */
71
+ export declare function validateOutreachComplianceSettings(input: unknown): OutreachComplianceValidation;
72
+ /** Whether two settings say the same thing — what makes a save a change. */
73
+ export declare function outreachComplianceSettingsEqual(a: OutreachComplianceSettings, b: OutreachComplianceSettings): boolean;
74
+ /**
75
+ * The countries a sequence may send to under its organization's ceiling:
76
+ * the sequence's own, in its order, less any the organization does not allow.
77
+ */
78
+ export declare function effectiveOutreachAllowedCountries(sequenceCountries: readonly string[] | null | undefined, orgCountries: readonly string[] | null | undefined): string[];
@@ -0,0 +1,155 @@
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 ORGANIZATION'S COMPLIANCE SETTINGS, AS DATA (AGL-2980).
18
+ *
19
+ * What the Compliance page edits and the settings route stores: the legal
20
+ * name and postal address every email's footer prints, the brand name its
21
+ * solicitation sentence uses, and the countries Outreach may send to at
22
+ * all. Client-safe — the page validates as the person types with the same
23
+ * function the route refuses a save with, so the two cannot disagree.
24
+ *
25
+ * Empty is allowed. An organization can save its countries before it has a
26
+ * postal address; what an empty address stops is ACTIVATION
27
+ * (`validateOutreachSequenceActivation`) and every send
28
+ * (`composeOutreachFooter`), and the page says so beside the field.
29
+ *==========================================*/ import { normalizeOutreachAllowedCountries } from "../engine/sequence-validation.js";
30
+ import { OUTREACH_DEFAULT_ALLOWED_COUNTRIES } from "./outreach.types.js";
31
+ /** The longest legal name the footer prints. */ export const OUTREACH_LEGAL_NAME_MAX = 120;
32
+ /** The longest brand name the solicitation sentence uses. */ export const OUTREACH_BRAND_NAME_MAX = 120;
33
+ /** The longest postal address, all of its lines together. */ export const OUTREACH_POSTAL_ADDRESS_MAX = 300;
34
+ /** The most lines a postal address is written over. */ export const OUTREACH_POSTAL_ADDRESS_MAX_LINES = 6;
35
+ /**
36
+ * Every ISO-3166-1 alpha-2 code assigned to a country or territory, sorted.
37
+ *
38
+ * A fixed list rather than whatever `Intl` can name: `Intl.DisplayNames`
39
+ * also names `EU`, `UN`, `ZZ` and retired codes such as `YU`, none of which
40
+ * a recipient's address can be in.
41
+ */ export const OUTREACH_COUNTRY_CODES = ('AD AE AF AG AI AL AM AO AQ AR AS AT AU AW AX AZ BA BB BD BE BF BG BH BI ' + 'BJ BL BM BN BO BQ BR BS BT BV BW BY BZ CA CC CD CF CG CH CI CK CL CM CN ' + 'CO CR CU CV CW CX CY CZ DE DJ DK DM DO DZ EC EE EG EH ER ES ET FI FJ FK ' + 'FM FO FR GA GB GD GE GF GG GH GI GL GM GN GP GQ GR GS GT GU GW GY HK HM ' + 'HN HR HT HU ID IE IL IM IN IO IQ IR IS IT JE JM JO JP KE KG KH KI KM KN ' + 'KP KR KW KY KZ LA LB LC LI LK LR LS LT LU LV LY MA MC MD ME MF MG MH MK ' + 'ML MM MN MO MP MQ MR MS MT MU MV MW MX MY MZ NA NC NE NF NG NI NL NO NP ' + 'NR NU NZ OM PA PE PF PG PH PK PL PM PN PR PS PT PW PY QA RE RO RS RU RW ' + 'SA SB SC SD SE SG SH SI SJ SK SL SM SN SO SR SS ST SV SX SY SZ TC TD TF ' + 'TG TH TJ TK TL TM TN TO TR TT TV TW TZ UA UG UM US UY UZ VA VC VE VG VI ' + 'VN VU WF WS YE YT ZA ZM ZW').split(' ');
42
+ const COUNTRY_CODES = new Set(OUTREACH_COUNTRY_CODES);
43
+ /** Whether `code` is an assigned country code, uppercase. */ export function isOutreachCountryCode(code) {
44
+ return typeof code === 'string' && COUNTRY_CODES.has(code);
45
+ }
46
+ let regionNames;
47
+ /**
48
+ * A country's English name as a list shows it — "United States", "Canada" —
49
+ * or the code where the runtime cannot name regions.
50
+ */ export function outreachCountryLabel(code) {
51
+ if (regionNames === undefined) {
52
+ try {
53
+ regionNames = new Intl.DisplayNames([
54
+ 'en'
55
+ ], {
56
+ type: 'region'
57
+ });
58
+ } catch (unused) {
59
+ regionNames = null;
60
+ }
61
+ }
62
+ try {
63
+ var _ref;
64
+ return (_ref = regionNames == null ? void 0 : regionNames.of(code)) != null ? _ref : code;
65
+ } catch (unused) {
66
+ return code;
67
+ }
68
+ }
69
+ /** Every country, by name, for the picker. */ export function outreachCountryOptions() {
70
+ return OUTREACH_COUNTRY_CODES.map((code)=>({
71
+ code,
72
+ name: outreachCountryLabel(code)
73
+ })).sort((a, b)=>a.name.localeCompare(b.name, 'en'));
74
+ }
75
+ const text = (value)=>typeof value === 'string' ? value : '';
76
+ /** One line: whitespace collapsed, ends trimmed. */ const oneLine = (value)=>text(value).replace(/\s+/g, ' ').trim();
77
+ /** The lines of an address, each settled, the empty ones dropped. */ function addressLines(value) {
78
+ return text(value).split(/\r\n|\r|\n/).map(oneLine).filter(Boolean);
79
+ }
80
+ /**
81
+ * A stored compliance document read into its shape, defaults applied: the
82
+ * United States alone when no countries were ever saved. A list that was
83
+ * saved is read as saved, codes that are not countries dropped, so an
84
+ * emptied list reads as nobody rather than as the default.
85
+ */ export function readOutreachComplianceSettings(raw) {
86
+ const data = raw && typeof raw === 'object' ? raw : {};
87
+ const countries = data['allowedCountries'];
88
+ const updatedAtMs = Number(data['updatedAtMs']);
89
+ return {
90
+ legalName: oneLine(data['legalName']),
91
+ brandName: oneLine(data['brandName']),
92
+ postalAddress: addressLines(data['postalAddress']).join('\n'),
93
+ allowedCountries: Array.isArray(countries) ? normalizeOutreachAllowedCountries(countries).filter(isOutreachCountryCode) : [
94
+ ...OUTREACH_DEFAULT_ALLOWED_COUNTRIES
95
+ ],
96
+ updatedAtMs: Number.isFinite(updatedAtMs) && updatedAtMs > 0 ? updatedAtMs : 0,
97
+ updatedByUid: typeof data['updatedByUid'] === 'string' && data['updatedByUid'] ? data['updatedByUid'] : null
98
+ };
99
+ }
100
+ /**
101
+ * What a save stores, and every reason it is refused — see the module note
102
+ * for why an empty legal name or postal address is not one of them.
103
+ */ export function validateOutreachComplianceSettings(input) {
104
+ const data = input && typeof input === 'object' ? input : {};
105
+ const issues = [];
106
+ const issue = (field, message)=>issues.push({
107
+ field,
108
+ message
109
+ });
110
+ const legalName = oneLine(data['legalName']);
111
+ if (legalName.length > OUTREACH_LEGAL_NAME_MAX) {
112
+ issue('legalName', `Keep the legal name under ${OUTREACH_LEGAL_NAME_MAX} characters.`);
113
+ }
114
+ const brandName = oneLine(data['brandName']);
115
+ if (brandName.length > OUTREACH_BRAND_NAME_MAX) {
116
+ issue('brandName', `Keep the brand name under ${OUTREACH_BRAND_NAME_MAX} characters.`);
117
+ }
118
+ const lines = addressLines(data['postalAddress']);
119
+ const postalAddress = lines.join('\n');
120
+ if (lines.length > OUTREACH_POSTAL_ADDRESS_MAX_LINES) {
121
+ issue('postalAddress', `Write the address on ${OUTREACH_POSTAL_ADDRESS_MAX_LINES} lines or fewer.`);
122
+ }
123
+ if (postalAddress.length > OUTREACH_POSTAL_ADDRESS_MAX) {
124
+ issue('postalAddress', `Keep the address under ${OUTREACH_POSTAL_ADDRESS_MAX} characters.`);
125
+ }
126
+ const rawCountries = data['allowedCountries'];
127
+ const allowedCountries = normalizeOutreachAllowedCountries(rawCountries);
128
+ const unknown = (Array.isArray(rawCountries) ? rawCountries : []).map((entry)=>text(entry).trim().toUpperCase()).filter((code)=>!isOutreachCountryCode(code));
129
+ if (!Array.isArray(rawCountries) || !allowedCountries.length) {
130
+ issue('allowedCountries', 'Choose at least one country a sequence may send to.');
131
+ } else if (unknown.length) {
132
+ issue('allowedCountries', `${unknown.map((code)=>`"${code}"`).join(', ')} ${unknown.length === 1 ? "isn't a country code" : "aren't country codes"}.`);
133
+ }
134
+ return {
135
+ settings: {
136
+ legalName,
137
+ brandName,
138
+ postalAddress,
139
+ allowedCountries: allowedCountries.filter(isOutreachCountryCode)
140
+ },
141
+ issues
142
+ };
143
+ }
144
+ /** Whether two settings say the same thing — what makes a save a change. */ export function outreachComplianceSettingsEqual(a, b) {
145
+ return a.legalName === b.legalName && a.brandName === b.brandName && a.postalAddress === b.postalAddress && a.allowedCountries.length === b.allowedCountries.length && a.allowedCountries.every((code, index)=>code === b.allowedCountries[index]);
146
+ }
147
+ /**
148
+ * The countries a sequence may send to under its organization's ceiling:
149
+ * the sequence's own, in its order, less any the organization does not allow.
150
+ */ export function effectiveOutreachAllowedCountries(sequenceCountries, orgCountries) {
151
+ const allowed = new Set(orgCountries != null ? orgCountries : []);
152
+ return (sequenceCountries != null ? sequenceCountries : []).filter((code)=>allowed.has(code));
153
+ }
154
+
155
+ //# sourceMappingURL=compliance-settings.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/outreach/src/lib/model/compliance-settings.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 ORGANIZATION'S COMPLIANCE SETTINGS, AS DATA (AGL-2980).\n *\n * What the Compliance page edits and the settings route stores: the legal\n * name and postal address every email's footer prints, the brand name its\n * solicitation sentence uses, and the countries Outreach may send to at\n * all. Client-safe — the page validates as the person types with the same\n * function the route refuses a save with, so the two cannot disagree.\n *\n * Empty is allowed. An organization can save its countries before it has a\n * postal address; what an empty address stops is ACTIVATION\n * (`validateOutreachSequenceActivation`) and every send\n * (`composeOutreachFooter`), and the page says so beside the field.\n *==========================================*/\n\nimport { normalizeOutreachAllowedCountries } from '../engine/sequence-validation'\nimport {\n OUTREACH_DEFAULT_ALLOWED_COUNTRIES,\n type OutreachComplianceSettings,\n type OutreachComplianceSettingsDocument,\n} from './outreach.types'\n\n/** The longest legal name the footer prints. */\nexport const OUTREACH_LEGAL_NAME_MAX = 120\n\n/** The longest brand name the solicitation sentence uses. */\nexport const OUTREACH_BRAND_NAME_MAX = 120\n\n/** The longest postal address, all of its lines together. */\nexport const OUTREACH_POSTAL_ADDRESS_MAX = 300\n\n/** The most lines a postal address is written over. */\nexport const OUTREACH_POSTAL_ADDRESS_MAX_LINES = 6\n\n/**\n * Every ISO-3166-1 alpha-2 code assigned to a country or territory, sorted.\n *\n * A fixed list rather than whatever `Intl` can name: `Intl.DisplayNames`\n * also names `EU`, `UN`, `ZZ` and retired codes such as `YU`, none of which\n * a recipient's address can be in.\n */\nexport const OUTREACH_COUNTRY_CODES: readonly string[] = (\n 'AD AE AF AG AI AL AM AO AQ AR AS AT AU AW AX AZ BA BB BD BE BF BG BH BI ' +\n 'BJ BL BM BN BO BQ BR BS BT BV BW BY BZ CA CC CD CF CG CH CI CK CL CM CN ' +\n 'CO CR CU CV CW CX CY CZ DE DJ DK DM DO DZ EC EE EG EH ER ES ET FI FJ FK ' +\n 'FM FO FR GA GB GD GE GF GG GH GI GL GM GN GP GQ GR GS GT GU GW GY HK HM ' +\n 'HN HR HT HU ID IE IL IM IN IO IQ IR IS IT JE JM JO JP KE KG KH KI KM KN ' +\n 'KP KR KW KY KZ LA LB LC LI LK LR LS LT LU LV LY MA MC MD ME MF MG MH MK ' +\n 'ML MM MN MO MP MQ MR MS MT MU MV MW MX MY MZ NA NC NE NF NG NI NL NO NP ' +\n 'NR NU NZ OM PA PE PF PG PH PK PL PM PN PR PS PT PW PY QA RE RO RS RU RW ' +\n 'SA SB SC SD SE SG SH SI SJ SK SL SM SN SO SR SS ST SV SX SY SZ TC TD TF ' +\n 'TG TH TJ TK TL TM TN TO TR TT TV TW TZ UA UG UM US UY UZ VA VC VE VG VI ' +\n 'VN VU WF WS YE YT ZA ZM ZW'\n).split(' ')\n\nconst COUNTRY_CODES = new Set(OUTREACH_COUNTRY_CODES)\n\n/** Whether `code` is an assigned country code, uppercase. */\nexport function isOutreachCountryCode(code: unknown): boolean {\n return typeof code === 'string' && COUNTRY_CODES.has(code)\n}\n\nlet regionNames: Intl.DisplayNames | null | undefined\n\n/**\n * A country's English name as a list shows it — \"United States\", \"Canada\" —\n * or the code where the runtime cannot name regions.\n */\nexport function outreachCountryLabel(code: string): string {\n if (regionNames === undefined) {\n try {\n regionNames = new Intl.DisplayNames(['en'], { type: 'region' })\n } catch {\n regionNames = null\n }\n }\n try {\n return regionNames?.of(code) ?? code\n } catch {\n return code\n }\n}\n\n/** One country as the picker offers it. */\nexport interface OutreachCountryOption {\n code: string\n name: string\n}\n\n/** Every country, by name, for the picker. */\nexport function outreachCountryOptions(): OutreachCountryOption[] {\n return OUTREACH_COUNTRY_CODES.map((code) => ({\n code,\n name: outreachCountryLabel(code),\n })).sort((a, b) => a.name.localeCompare(b.name, 'en'))\n}\n\nconst text = (value: unknown): string => (typeof value === 'string' ? value : '')\n\n/** One line: whitespace collapsed, ends trimmed. */\nconst oneLine = (value: unknown): string => text(value).replace(/\\s+/g, ' ').trim()\n\n/** The lines of an address, each settled, the empty ones dropped. */\nfunction addressLines(value: unknown): string[] {\n return text(value)\n .split(/\\r\\n|\\r|\\n/)\n .map(oneLine)\n .filter(Boolean)\n}\n\n/**\n * A stored compliance document read into its shape, defaults applied: the\n * United States alone when no countries were ever saved. A list that was\n * saved is read as saved, codes that are not countries dropped, so an\n * emptied list reads as nobody rather than as the default.\n */\nexport function readOutreachComplianceSettings(\n raw: unknown,\n): OutreachComplianceSettingsDocument {\n const data = (raw && typeof raw === 'object' ? raw : {}) as Record<string, unknown>\n const countries = data['allowedCountries']\n const updatedAtMs = Number(data['updatedAtMs'])\n return {\n legalName: oneLine(data['legalName']),\n brandName: oneLine(data['brandName']),\n postalAddress: addressLines(data['postalAddress']).join('\\n'),\n allowedCountries: Array.isArray(countries)\n ? normalizeOutreachAllowedCountries(countries).filter(isOutreachCountryCode)\n : [...OUTREACH_DEFAULT_ALLOWED_COUNTRIES],\n updatedAtMs: Number.isFinite(updatedAtMs) && updatedAtMs > 0 ? updatedAtMs : 0,\n updatedByUid:\n typeof data['updatedByUid'] === 'string' && data['updatedByUid']\n ? data['updatedByUid']\n : null,\n }\n}\n\n/** A field the Compliance page shows an issue beside. */\nexport type OutreachComplianceField =\n | 'legalName'\n | 'brandName'\n | 'postalAddress'\n | 'allowedCountries'\n\nexport interface OutreachComplianceIssue {\n field: OutreachComplianceField\n message: string\n}\n\nexport interface OutreachComplianceValidation {\n /** The settings as they would be stored: trimmed, one-lined, codes uppercased. */\n settings: OutreachComplianceSettings\n /** Every reason the save is refused; empty when it may be saved. */\n issues: OutreachComplianceIssue[]\n}\n\n/**\n * What a save stores, and every reason it is refused — see the module note\n * for why an empty legal name or postal address is not one of them.\n */\nexport function validateOutreachComplianceSettings(\n input: unknown,\n): OutreachComplianceValidation {\n const data = (input && typeof input === 'object' ? input : {}) as Record<string, unknown>\n const issues: OutreachComplianceIssue[] = []\n const issue = (field: OutreachComplianceField, message: string) =>\n issues.push({ field, message })\n\n const legalName = oneLine(data['legalName'])\n if (legalName.length > OUTREACH_LEGAL_NAME_MAX) {\n issue('legalName', `Keep the legal name under ${OUTREACH_LEGAL_NAME_MAX} characters.`)\n }\n const brandName = oneLine(data['brandName'])\n if (brandName.length > OUTREACH_BRAND_NAME_MAX) {\n issue('brandName', `Keep the brand name under ${OUTREACH_BRAND_NAME_MAX} characters.`)\n }\n const lines = addressLines(data['postalAddress'])\n const postalAddress = lines.join('\\n')\n if (lines.length > OUTREACH_POSTAL_ADDRESS_MAX_LINES) {\n issue(\n 'postalAddress',\n `Write the address on ${OUTREACH_POSTAL_ADDRESS_MAX_LINES} lines or fewer.`,\n )\n }\n if (postalAddress.length > OUTREACH_POSTAL_ADDRESS_MAX) {\n issue(\n 'postalAddress',\n `Keep the address under ${OUTREACH_POSTAL_ADDRESS_MAX} characters.`,\n )\n }\n\n const rawCountries = data['allowedCountries']\n const allowedCountries = normalizeOutreachAllowedCountries(rawCountries)\n const unknown = (Array.isArray(rawCountries) ? rawCountries : [])\n .map((entry) => text(entry).trim().toUpperCase())\n .filter((code) => !isOutreachCountryCode(code))\n if (!Array.isArray(rawCountries) || !allowedCountries.length) {\n issue('allowedCountries', 'Choose at least one country a sequence may send to.')\n } else if (unknown.length) {\n issue(\n 'allowedCountries',\n `${unknown.map((code) => `\"${code}\"`).join(', ')} ${unknown.length === 1 ? \"isn't a country code\" : \"aren't country codes\"}.`,\n )\n }\n\n return {\n settings: {\n legalName,\n brandName,\n postalAddress,\n allowedCountries: allowedCountries.filter(isOutreachCountryCode),\n },\n issues,\n }\n}\n\n/** Whether two settings say the same thing — what makes a save a change. */\nexport function outreachComplianceSettingsEqual(\n a: OutreachComplianceSettings,\n b: OutreachComplianceSettings,\n): boolean {\n return (\n a.legalName === b.legalName &&\n a.brandName === b.brandName &&\n a.postalAddress === b.postalAddress &&\n a.allowedCountries.length === b.allowedCountries.length &&\n a.allowedCountries.every((code, index) => code === b.allowedCountries[index])\n )\n}\n\n/**\n * The countries a sequence may send to under its organization's ceiling:\n * the sequence's own, in its order, less any the organization does not allow.\n */\nexport function effectiveOutreachAllowedCountries(\n sequenceCountries: readonly string[] | null | undefined,\n orgCountries: readonly string[] | null | undefined,\n): string[] {\n const allowed = new Set(orgCountries ?? [])\n return (sequenceCountries ?? []).filter((code) => allowed.has(code))\n}\n"],"names":["normalizeOutreachAllowedCountries","OUTREACH_DEFAULT_ALLOWED_COUNTRIES","OUTREACH_LEGAL_NAME_MAX","OUTREACH_BRAND_NAME_MAX","OUTREACH_POSTAL_ADDRESS_MAX","OUTREACH_POSTAL_ADDRESS_MAX_LINES","OUTREACH_COUNTRY_CODES","split","COUNTRY_CODES","Set","isOutreachCountryCode","code","has","regionNames","outreachCountryLabel","undefined","Intl","DisplayNames","type","of","outreachCountryOptions","map","name","sort","a","b","localeCompare","text","value","oneLine","replace","trim","addressLines","filter","Boolean","readOutreachComplianceSettings","raw","data","countries","updatedAtMs","Number","legalName","brandName","postalAddress","join","allowedCountries","Array","isArray","isFinite","updatedByUid","validateOutreachComplianceSettings","input","issues","issue","field","message","push","length","lines","rawCountries","unknown","entry","toUpperCase","settings","outreachComplianceSettingsEqual","every","index","effectiveOutreachAllowedCountries","sequenceCountries","orgCountries","allowed"],"mappings":"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"}