@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.
- package/LICENSE +201 -0
- package/README.md +93 -0
- package/package.json +52 -0
- package/src/index.d.ts +22 -0
- package/src/index.js +29 -0
- package/src/index.js.map +1 -0
- package/src/lib/components/compliance-section.d.ts +20 -0
- package/src/lib/components/compliance-section.js +291 -0
- package/src/lib/components/compliance-section.js.map +1 -0
- package/src/lib/components/enroll-dialog.d.ts +36 -0
- package/src/lib/components/enroll-dialog.js +656 -0
- package/src/lib/components/enroll-dialog.js.map +1 -0
- package/src/lib/components/enrollments-table.d.ts +28 -0
- package/src/lib/components/enrollments-table.js +375 -0
- package/src/lib/components/enrollments-table.js.map +1 -0
- package/src/lib/components/mailbox-card.d.ts +46 -0
- package/src/lib/components/mailbox-card.js +517 -0
- package/src/lib/components/mailbox-card.js.map +1 -0
- package/src/lib/components/mailboxes-section.d.ts +24 -0
- package/src/lib/components/mailboxes-section.js +258 -0
- package/src/lib/components/mailboxes-section.js.map +1 -0
- package/src/lib/components/outreach-console-page.d.ts +17 -0
- package/src/lib/components/outreach-console-page.js +81 -0
- package/src/lib/components/outreach-console-page.js.map +1 -0
- package/src/lib/components/outreach-console-sections.d.ts +43 -0
- package/src/lib/components/outreach-console-sections.js +55 -0
- package/src/lib/components/outreach-console-sections.js.map +1 -0
- package/src/lib/components/outreach-ui.d.ts +51 -0
- package/src/lib/components/outreach-ui.js +171 -0
- package/src/lib/components/outreach-ui.js.map +1 -0
- package/src/lib/components/send-window-fields.d.ts +25 -0
- package/src/lib/components/send-window-fields.js +178 -0
- package/src/lib/components/send-window-fields.js.map +1 -0
- package/src/lib/components/sequence-detail.d.ts +54 -0
- package/src/lib/components/sequence-detail.js +435 -0
- package/src/lib/components/sequence-detail.js.map +1 -0
- package/src/lib/components/sequence-editor.d.ts +37 -0
- package/src/lib/components/sequence-editor.js +485 -0
- package/src/lib/components/sequence-editor.js.map +1 -0
- package/src/lib/components/sequence-mailbox-picker.d.ts +44 -0
- package/src/lib/components/sequence-mailbox-picker.js +137 -0
- package/src/lib/components/sequence-mailbox-picker.js.map +1 -0
- package/src/lib/components/sequence-preview.d.ts +27 -0
- package/src/lib/components/sequence-preview.js +146 -0
- package/src/lib/components/sequence-preview.js.map +1 -0
- package/src/lib/components/sequence-step-card.d.ts +31 -0
- package/src/lib/components/sequence-step-card.js +326 -0
- package/src/lib/components/sequence-step-card.js.map +1 -0
- package/src/lib/components/sequences-section.d.ts +40 -0
- package/src/lib/components/sequences-section.js +310 -0
- package/src/lib/components/sequences-section.js.map +1 -0
- package/src/lib/components/use-outreach-api.d.ts +38 -0
- package/src/lib/components/use-outreach-api.js +139 -0
- package/src/lib/components/use-outreach-api.js.map +1 -0
- package/src/lib/components/use-outreach-crm.d.ts +50 -0
- package/src/lib/components/use-outreach-crm.js +200 -0
- package/src/lib/components/use-outreach-crm.js.map +1 -0
- package/src/lib/components/use-outreach-data.d.ts +55 -0
- package/src/lib/components/use-outreach-data.js +199 -0
- package/src/lib/components/use-outreach-data.js.map +1 -0
- package/src/lib/components/use-outreach-mailbox-api.d.ts +30 -0
- package/src/lib/components/use-outreach-mailbox-api.js +101 -0
- package/src/lib/components/use-outreach-mailbox-api.js.map +1 -0
- package/src/lib/components/use-outreach-mailboxes.d.ts +22 -0
- package/src/lib/components/use-outreach-mailboxes.js +68 -0
- package/src/lib/components/use-outreach-mailboxes.js.map +1 -0
- package/src/lib/components/use-outreach-settings.d.ts +17 -0
- package/src/lib/components/use-outreach-settings.js +64 -0
- package/src/lib/components/use-outreach-settings.js.map +1 -0
- package/src/lib/constants/api-routes.d.ts +68 -0
- package/src/lib/constants/api-routes.js +57 -0
- package/src/lib/constants/api-routes.js.map +1 -0
- package/src/lib/constants/bundle-common.d.ts +46 -0
- package/src/lib/constants/bundle-common.js +55 -0
- package/src/lib/constants/bundle-common.js.map +1 -0
- package/src/lib/constants/runtime-jobs.d.ts +24 -0
- package/src/lib/constants/runtime-jobs.js +24 -0
- package/src/lib/constants/runtime-jobs.js.map +1 -0
- package/src/lib/declarations.console-server.d.ts +38 -0
- package/src/lib/declarations.console-server.js +99 -0
- package/src/lib/declarations.console-server.js.map +1 -0
- package/src/lib/engine/compose.d.ts +86 -0
- package/src/lib/engine/compose.js +212 -0
- package/src/lib/engine/compose.js.map +1 -0
- package/src/lib/engine/delivery-status.d.ts +53 -0
- package/src/lib/engine/delivery-status.js +184 -0
- package/src/lib/engine/delivery-status.js.map +1 -0
- package/src/lib/engine/do-not-contact.d.ts +18 -0
- package/src/lib/engine/do-not-contact.js +34 -0
- package/src/lib/engine/do-not-contact.js.map +1 -0
- package/src/lib/engine/enrollment-state.d.ts +156 -0
- package/src/lib/engine/enrollment-state.js +303 -0
- package/src/lib/engine/enrollment-state.js.map +1 -0
- package/src/lib/engine/gates.d.ts +125 -0
- package/src/lib/engine/gates.js +232 -0
- package/src/lib/engine/gates.js.map +1 -0
- package/src/lib/engine/index.d.ts +28 -0
- package/src/lib/engine/index.js +33 -0
- package/src/lib/engine/index.js.map +1 -0
- package/src/lib/engine/mailbox-health.d.ts +49 -0
- package/src/lib/engine/mailbox-health.js +83 -0
- package/src/lib/engine/mailbox-health.js.map +1 -0
- package/src/lib/engine/opt-out-intent.d.ts +41 -0
- package/src/lib/engine/opt-out-intent.js +220 -0
- package/src/lib/engine/opt-out-intent.js.map +1 -0
- package/src/lib/engine/recipient-country.d.ts +41 -0
- package/src/lib/engine/recipient-country.js +171 -0
- package/src/lib/engine/recipient-country.js.map +1 -0
- package/src/lib/engine/schedule.d.ts +70 -0
- package/src/lib/engine/schedule.js +115 -0
- package/src/lib/engine/schedule.js.map +1 -0
- package/src/lib/engine/sending-capacity.d.ts +96 -0
- package/src/lib/engine/sending-capacity.js +172 -0
- package/src/lib/engine/sending-capacity.js.map +1 -0
- package/src/lib/engine/sequence-validation.d.ts +90 -0
- package/src/lib/engine/sequence-validation.js +291 -0
- package/src/lib/engine/sequence-validation.js.map +1 -0
- package/src/lib/engine/thread-classification.d.ts +78 -0
- package/src/lib/engine/thread-classification.js +224 -0
- package/src/lib/engine/thread-classification.js.map +1 -0
- package/src/lib/engine/thread-message.d.ts +80 -0
- package/src/lib/engine/thread-message.js +143 -0
- package/src/lib/engine/thread-message.js.map +1 -0
- package/src/lib/enrollment/enroll-people.d.ts +81 -0
- package/src/lib/enrollment/enroll-people.js +250 -0
- package/src/lib/enrollment/enroll-people.js.map +1 -0
- package/src/lib/enrollment/gate-lookups.d.ts +32 -0
- package/src/lib/enrollment/gate-lookups.js +154 -0
- package/src/lib/enrollment/gate-lookups.js.map +1 -0
- package/src/lib/mailboxes/mailbox-api.d.ts +126 -0
- package/src/lib/mailboxes/mailbox-api.js +66 -0
- package/src/lib/mailboxes/mailbox-api.js.map +1 -0
- package/src/lib/mailboxes/mailbox-credentials.d.ts +95 -0
- package/src/lib/mailboxes/mailbox-credentials.js +102 -0
- package/src/lib/mailboxes/mailbox-credentials.js.map +1 -0
- package/src/lib/mailboxes/mailbox-erasure.d.ts +69 -0
- package/src/lib/mailboxes/mailbox-erasure.js +148 -0
- package/src/lib/mailboxes/mailbox-erasure.js.map +1 -0
- package/src/lib/mailboxes/mailbox-gate.d.ts +75 -0
- package/src/lib/mailboxes/mailbox-gate.js +98 -0
- package/src/lib/mailboxes/mailbox-gate.js.map +1 -0
- package/src/lib/mailboxes/mailbox-revoke.d.ts +59 -0
- package/src/lib/mailboxes/mailbox-revoke.js +41 -0
- package/src/lib/mailboxes/mailbox-revoke.js.map +1 -0
- package/src/lib/mailboxes/mailbox-routes.d.ts +119 -0
- package/src/lib/mailboxes/mailbox-routes.js +605 -0
- package/src/lib/mailboxes/mailbox-routes.js.map +1 -0
- package/src/lib/mailboxes/mailbox-settings.d.ts +110 -0
- package/src/lib/mailboxes/mailbox-settings.js +212 -0
- package/src/lib/mailboxes/mailbox-settings.js.map +1 -0
- package/src/lib/mailboxes/mailbox-transport.d.ts +64 -0
- package/src/lib/mailboxes/mailbox-transport.js +94 -0
- package/src/lib/mailboxes/mailbox-transport.js.map +1 -0
- package/src/lib/mailboxes/oauth-redirect.d.ts +41 -0
- package/src/lib/mailboxes/oauth-redirect.js +63 -0
- package/src/lib/mailboxes/oauth-redirect.js.map +1 -0
- package/src/lib/mailboxes/oauth-state.d.ts +152 -0
- package/src/lib/mailboxes/oauth-state.js +212 -0
- package/src/lib/mailboxes/oauth-state.js.map +1 -0
- package/src/lib/mailboxes/outreach-config.d.ts +64 -0
- package/src/lib/mailboxes/outreach-config.js +77 -0
- package/src/lib/mailboxes/outreach-config.js.map +1 -0
- package/src/lib/mailboxes/register-mailbox-routes.d.ts +47 -0
- package/src/lib/mailboxes/register-mailbox-routes.js +135 -0
- package/src/lib/mailboxes/register-mailbox-routes.js.map +1 -0
- package/src/lib/model/compliance-settings.d.ts +78 -0
- package/src/lib/model/compliance-settings.js +155 -0
- package/src/lib/model/compliance-settings.js.map +1 -0
- package/src/lib/model/outreach-api.d.ts +210 -0
- package/src/lib/model/outreach-api.js +28 -0
- package/src/lib/model/outreach-api.js.map +1 -0
- package/src/lib/model/outreach.types.d.ts +563 -0
- package/src/lib/model/outreach.types.js +225 -0
- package/src/lib/model/outreach.types.js.map +1 -0
- package/src/lib/model/sequence-draft.d.ts +84 -0
- package/src/lib/model/sequence-draft.js +239 -0
- package/src/lib/model/sequence-draft.js.map +1 -0
- package/src/lib/model/step-preview.d.ts +45 -0
- package/src/lib/model/step-preview.js +98 -0
- package/src/lib/model/step-preview.js.map +1 -0
- package/src/lib/model/stored-records.d.ts +23 -0
- package/src/lib/model/stored-records.js +84 -0
- package/src/lib/model/stored-records.js.map +1 -0
- package/src/lib/plugin.d.ts +46 -0
- package/src/lib/plugin.js +86 -0
- package/src/lib/plugin.js.map +1 -0
- package/src/lib/routes/enroll-routes.d.ts +64 -0
- package/src/lib/routes/enroll-routes.js +350 -0
- package/src/lib/routes/enroll-routes.js.map +1 -0
- package/src/lib/routes/enrollment-routes.d.ts +19 -0
- package/src/lib/routes/enrollment-routes.js +171 -0
- package/src/lib/routes/enrollment-routes.js.map +1 -0
- package/src/lib/routes/platform-deps.d.ts +51 -0
- package/src/lib/routes/platform-deps.js +66 -0
- package/src/lib/routes/platform-deps.js.map +1 -0
- package/src/lib/routes/preview-routes.d.ts +33 -0
- package/src/lib/routes/preview-routes.js +117 -0
- package/src/lib/routes/preview-routes.js.map +1 -0
- package/src/lib/routes/register-routes.d.ts +22 -0
- package/src/lib/routes/register-routes.js +92 -0
- package/src/lib/routes/register-routes.js.map +1 -0
- package/src/lib/routes/route-deps.d.ts +49 -0
- package/src/lib/routes/route-deps.js +27 -0
- package/src/lib/routes/route-deps.js.map +1 -0
- package/src/lib/routes/route-gate.d.ts +94 -0
- package/src/lib/routes/route-gate.js +87 -0
- package/src/lib/routes/route-gate.js.map +1 -0
- package/src/lib/routes/route-http.d.ts +32 -0
- package/src/lib/routes/route-http.js +62 -0
- package/src/lib/routes/route-http.js.map +1 -0
- package/src/lib/routes/sequence-routes.d.ts +32 -0
- package/src/lib/routes/sequence-routes.js +359 -0
- package/src/lib/routes/sequence-routes.js.map +1 -0
- package/src/lib/routes/settings-routes.d.ts +30 -0
- package/src/lib/routes/settings-routes.js +73 -0
- package/src/lib/routes/settings-routes.js.map +1 -0
- package/src/lib/runtime/enrollment-events.d.ts +86 -0
- package/src/lib/runtime/enrollment-events.js +141 -0
- package/src/lib/runtime/enrollment-events.js.map +1 -0
- package/src/lib/runtime/fixtures/fake-gmail.d.ts +121 -0
- package/src/lib/runtime/fixtures/fake-gmail.js +312 -0
- package/src/lib/runtime/fixtures/fake-gmail.js.map +1 -0
- package/src/lib/runtime/mailbox-health-store.d.ts +47 -0
- package/src/lib/runtime/mailbox-health-store.js +168 -0
- package/src/lib/runtime/mailbox-health-store.js.map +1 -0
- package/src/lib/runtime/person-erasure.d.ts +39 -0
- package/src/lib/runtime/person-erasure.js +57 -0
- package/src/lib/runtime/person-erasure.js.map +1 -0
- package/src/lib/runtime/platform-runtime-deps.d.ts +29 -0
- package/src/lib/runtime/platform-runtime-deps.js +98 -0
- package/src/lib/runtime/platform-runtime-deps.js.map +1 -0
- package/src/lib/runtime/runtime-deps.d.ts +77 -0
- package/src/lib/runtime/runtime-deps.js +18 -0
- package/src/lib/runtime/runtime-deps.js.map +1 -0
- package/src/lib/runtime/send-job.d.ts +42 -0
- package/src/lib/runtime/send-job.js +780 -0
- package/src/lib/runtime/send-job.js.map +1 -0
- package/src/lib/runtime/sync-job.d.ts +41 -0
- package/src/lib/runtime/sync-job.js +480 -0
- package/src/lib/runtime/sync-job.js.map +1 -0
- package/src/lib/runtime/timeline.d.ts +67 -0
- package/src/lib/runtime/timeline.js +93 -0
- package/src/lib/runtime/timeline.js.map +1 -0
- package/src/lib/runtime/unsubscribe-link.d.ts +75 -0
- package/src/lib/runtime/unsubscribe-link.js +121 -0
- package/src/lib/runtime/unsubscribe-link.js.map +1 -0
- package/src/lib/runtime/unsubscribe-route.d.ts +24 -0
- package/src/lib/runtime/unsubscribe-route.js +103 -0
- package/src/lib/runtime/unsubscribe-route.js.map +1 -0
- package/src/lib/server.d.ts +53 -0
- package/src/lib/server.js +94 -0
- package/src/lib/server.js.map +1 -0
- package/src/lib/storage/compliance-settings-store.d.ts +44 -0
- package/src/lib/storage/compliance-settings-store.js +78 -0
- package/src/lib/storage/compliance-settings-store.js.map +1 -0
- package/src/lib/storage/do-not-contact-store.d.ts +60 -0
- package/src/lib/storage/do-not-contact-store.js +168 -0
- package/src/lib/storage/do-not-contact-store.js.map +1 -0
- package/src/lib/storage/outreach-records.d.ts +33 -0
- package/src/lib/storage/outreach-records.js +35 -0
- package/src/lib/storage/outreach-records.js.map +1 -0
- package/src/lib/subprocessors.d.ts +48 -0
- package/src/lib/subprocessors.js +70 -0
- package/src/lib/subprocessors.js.map +1 -0
- package/src/lib/transport/gmail-client.d.ts +145 -0
- package/src/lib/transport/gmail-client.js +229 -0
- package/src/lib/transport/gmail-client.js.map +1 -0
- package/src/lib/transport/gmail-errors.d.ts +84 -0
- package/src/lib/transport/gmail-errors.js +138 -0
- package/src/lib/transport/gmail-errors.js.map +1 -0
- package/src/lib/transport/google-oauth.d.ts +138 -0
- package/src/lib/transport/google-oauth.js +247 -0
- package/src/lib/transport/google-oauth.js.map +1 -0
- package/src/lib/transport/http.d.ts +53 -0
- package/src/lib/transport/http.js +79 -0
- package/src/lib/transport/http.js.map +1 -0
- package/src/lib/transport/rfc5322.d.ts +116 -0
- package/src/lib/transport/rfc5322.js +330 -0
- package/src/lib/transport/rfc5322.js.map +1 -0
- package/src/lib/transport/send-message.d.ts +69 -0
- package/src/lib/transport/send-message.js +77 -0
- 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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"}
|