@aglyn/aglyn 1.0.0-beta.160 → 1.0.0-beta.162
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +10 -10
- package/src/lib/app-utils/contacts.d.ts +8 -0
- package/src/lib/app-utils/contacts.js.map +1 -1
- package/src/lib/app-utils/crm.d.ts +13 -0
- package/src/lib/app-utils/crm.js.map +1 -1
- package/src/lib/app-utils/docs-help.generated.d.ts +3 -3
- package/src/lib/app-utils/docs-help.generated.js +3 -0
- package/src/lib/app-utils/docs-help.generated.js.map +1 -1
- package/src/lib/app-utils/docs-index.generated.js +7 -7
- package/src/lib/app-utils/docs-index.generated.js.map +1 -1
- package/src/lib/app-utils/email-state.d.ts +57 -0
- package/src/lib/app-utils/email-state.js +136 -0
- package/src/lib/app-utils/email-state.js.map +1 -0
- package/src/lib/app-utils/health-report.d.ts +28 -0
- package/src/lib/app-utils/health-report.js +36 -0
- package/src/lib/app-utils/health-report.js.map +1 -1
- package/src/lib/app-utils/notifications.d.ts +58 -0
- package/src/lib/app-utils/notifications.js +62 -8
- package/src/lib/app-utils/notifications.js.map +1 -1
- package/src/lib/app-utils/search-indexing.d.ts +37 -2
- package/src/lib/app-utils/search-indexing.js +23 -1
- package/src/lib/app-utils/search-indexing.js.map +1 -1
- package/src/lib/app-utils/server.d.ts +1 -0
- package/src/lib/app-utils/server.js +3 -0
- package/src/lib/app-utils/server.js.map +1 -1
- package/src/lib/plugin-manager/plugin-record-email-state.d.ts +85 -0
- package/src/lib/plugin-manager/plugin-record-email-state.js +53 -0
- package/src/lib/plugin-manager/plugin-record-email-state.js.map +1 -0
- package/src/lib/plugin-manager/plugin-record-timeline.d.ts +22 -0
- package/src/lib/plugin-manager/plugin-record-timeline.js.map +1 -1
|
@@ -0,0 +1,57 @@
|
|
|
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
|
+
/** Every state, weakest first. */
|
|
18
|
+
export declare const EMAIL_STATE_STATUSES: readonly ["ok", "bounced", "blocked", "unsubscribed", "complained", "do_not_contact"];
|
|
19
|
+
export type EmailStateStatus = (typeof EMAIL_STATE_STATUSES)[number];
|
|
20
|
+
/** Which sender's verdict it is. */
|
|
21
|
+
export type EmailStateSource = 'sequence' | 'campaign' | 'member';
|
|
22
|
+
/** The field on the lead and the contact. */
|
|
23
|
+
export declare const EMAIL_STATE_FIELD = "emailState";
|
|
24
|
+
export interface EmailState {
|
|
25
|
+
status: EmailStateStatus;
|
|
26
|
+
/** When the verdict was given, epoch ms. */
|
|
27
|
+
atMs: number;
|
|
28
|
+
source: EmailStateSource;
|
|
29
|
+
/** The sender's own words: the bounce's diagnostic, a member's note. */
|
|
30
|
+
detail: string | null;
|
|
31
|
+
/** The sequence enrollment it came from, when one did. */
|
|
32
|
+
enrollmentId?: string;
|
|
33
|
+
}
|
|
34
|
+
/** How a state reads on the chip — typed so a state cannot ship unlabeled. */
|
|
35
|
+
export declare const EMAIL_STATE_LABELS: Record<EmailStateStatus, string>;
|
|
36
|
+
export declare function isEmailStateStatus(value: unknown): value is EmailStateStatus;
|
|
37
|
+
/** The state a record holds, held to its shape, or `null` when it holds none. */
|
|
38
|
+
export declare function readEmailState(record: Record<string, unknown> | null | undefined): EmailState | null;
|
|
39
|
+
/** Whether the state means the address must not be emailed by hand. */
|
|
40
|
+
export declare function emailStateForbidsEmail(state: EmailState | null | undefined): boolean;
|
|
41
|
+
/**
|
|
42
|
+
* The state a record holds after one more verdict: the incoming one when it
|
|
43
|
+
* ranks at least as high as the current — a fresh bounce replaces an old
|
|
44
|
+
* one, a block replaces a bounce, a member's mark replaces anything — else
|
|
45
|
+
* the current. `force` writes the incoming state whatever stands, which is
|
|
46
|
+
* how a release lands.
|
|
47
|
+
*/
|
|
48
|
+
export declare function nextEmailState(current: EmailState | null | undefined, incoming: EmailState, options?: {
|
|
49
|
+
force?: boolean;
|
|
50
|
+
}): EmailState;
|
|
51
|
+
/** When the verdict was given, as the chip's tooltip and the refusal say it. */
|
|
52
|
+
export declare function emailStateWhen(state: Pick<EmailState, 'atMs'>): string;
|
|
53
|
+
/**
|
|
54
|
+
* Why the record must not be emailed, in one sentence a disabled button or
|
|
55
|
+
* a refused enrollment can show — or `null` when it may be.
|
|
56
|
+
*/
|
|
57
|
+
export declare function emailStateRefusal(state: EmailState | null | undefined): string | null;
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import { _ as _extends } from "@swc/helpers/_/_extends";
|
|
2
|
+
/**
|
|
3
|
+
* @license
|
|
4
|
+
* Copyright 2026 Aglyn LLC
|
|
5
|
+
*
|
|
6
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
7
|
+
* you may not use this file except in compliance with the License.
|
|
8
|
+
* You may obtain a copy of the License at
|
|
9
|
+
*
|
|
10
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
11
|
+
*
|
|
12
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
13
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
14
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
15
|
+
* See the License for the specific language governing permissions and
|
|
16
|
+
* limitations under the License.
|
|
17
|
+
*/ /*==========================================
|
|
18
|
+
* A RECORD'S EMAIL STATE (AGL-3245).
|
|
19
|
+
*
|
|
20
|
+
* The lists the senders consult — the platform suppression list, a site's
|
|
21
|
+
* suppressions, an organization's do-not-contact list — are keyed by a
|
|
22
|
+
* hash nobody can read. The record a person opens is the lead or the
|
|
23
|
+
* contact, and until now it said nothing: an address that hard-bounced at
|
|
24
|
+
* 10:30 still read as "Sent" at 12:30, and a member reading it would have
|
|
25
|
+
* emailed the person again by hand.
|
|
26
|
+
*
|
|
27
|
+
* So the record carries `emailState`: what the last verdict on its address
|
|
28
|
+
* was, when, from which sender, and in the sender's own words. It is
|
|
29
|
+
* WRITTEN BY THE PLATFORM ONLY — the outreach runtime, the campaign
|
|
30
|
+
* webhook, a member's do-not-contact mark — never by a form, an import or
|
|
31
|
+
* the record's own editor, and the rules hold clients off the field. It
|
|
32
|
+
* is a mirror of the lists, not a list: a send still asks the lists, and
|
|
33
|
+
* the record says what they hold in words a person can act on.
|
|
34
|
+
*
|
|
35
|
+
* Absent means nothing is known. `ok` is a verdict too — a release, a
|
|
36
|
+
* confirmed re-subscription — and it is the one state that never
|
|
37
|
+
* overwrites a refusal on its own: the ranking below decides.
|
|
38
|
+
*==========================================*/ /** Every state, weakest first. */ export const EMAIL_STATE_STATUSES = [
|
|
39
|
+
/** The address may be emailed: released, or confirmed again. */ 'ok',
|
|
40
|
+
/** Mail to it bounced for good: the mailbox does not exist. */ 'bounced',
|
|
41
|
+
/** The recipient's mail gateway refused the sender on policy or reputation. */ 'blocked',
|
|
42
|
+
/** The unsubscribe link or header was used. */ 'unsubscribed',
|
|
43
|
+
/** A reply called the email spam, or the recipient reported it. */ 'complained',
|
|
44
|
+
/** On the organization's do-not-contact list — a member's or the runtime's entry. */ 'do_not_contact'
|
|
45
|
+
];
|
|
46
|
+
/** The field on the lead and the contact. */ export const EMAIL_STATE_FIELD = 'emailState';
|
|
47
|
+
/** How a state reads on the chip — typed so a state cannot ship unlabeled. */ export const EMAIL_STATE_LABELS = {
|
|
48
|
+
ok: 'Email OK',
|
|
49
|
+
bounced: 'Bounced',
|
|
50
|
+
blocked: 'Blocked by their mail gateway',
|
|
51
|
+
unsubscribed: 'Unsubscribed',
|
|
52
|
+
complained: 'Marked as spam',
|
|
53
|
+
do_not_contact: 'Do not contact'
|
|
54
|
+
};
|
|
55
|
+
/**
|
|
56
|
+
* The rank a later verdict must reach to replace an earlier one. A member's
|
|
57
|
+
* do-not-contact mark stands over every automatic verdict; a complaint
|
|
58
|
+
* over an unsubscribe, which is the person's own act over a server's; a
|
|
59
|
+
* block over a bounce, since the block names the whole domain; and `ok`
|
|
60
|
+
* over nothing — a release is written on purpose, by a path that forces it.
|
|
61
|
+
*/ const EMAIL_STATE_RANK = {
|
|
62
|
+
ok: 0,
|
|
63
|
+
bounced: 1,
|
|
64
|
+
blocked: 2,
|
|
65
|
+
unsubscribed: 3,
|
|
66
|
+
complained: 4,
|
|
67
|
+
do_not_contact: 5
|
|
68
|
+
};
|
|
69
|
+
export function isEmailStateStatus(value) {
|
|
70
|
+
return typeof value === 'string' && EMAIL_STATE_STATUSES.includes(value);
|
|
71
|
+
}
|
|
72
|
+
/** The state a record holds, held to its shape, or `null` when it holds none. */ export function readEmailState(record) {
|
|
73
|
+
const raw = record == null ? void 0 : record[EMAIL_STATE_FIELD];
|
|
74
|
+
if (!raw || typeof raw !== 'object') return null;
|
|
75
|
+
const state = raw;
|
|
76
|
+
if (!isEmailStateStatus(state['status'])) return null;
|
|
77
|
+
const atMs = Number(state['atMs']);
|
|
78
|
+
const source = state['source'];
|
|
79
|
+
const enrollmentId = typeof state['enrollmentId'] === 'string' && state['enrollmentId'] ? state['enrollmentId'] : undefined;
|
|
80
|
+
return _extends({
|
|
81
|
+
status: state['status'],
|
|
82
|
+
atMs: Number.isFinite(atMs) && atMs > 0 ? atMs : 0,
|
|
83
|
+
source: source === 'sequence' || source === 'campaign' || source === 'member' ? source : 'campaign',
|
|
84
|
+
detail: typeof state['detail'] === 'string' && state['detail'] ? state['detail'] : null
|
|
85
|
+
}, enrollmentId ? {
|
|
86
|
+
enrollmentId
|
|
87
|
+
} : {});
|
|
88
|
+
}
|
|
89
|
+
/** Whether the state means the address must not be emailed by hand. */ export function emailStateForbidsEmail(state) {
|
|
90
|
+
return Boolean(state) && (state == null ? void 0 : state.status) !== 'ok';
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* The state a record holds after one more verdict: the incoming one when it
|
|
94
|
+
* ranks at least as high as the current — a fresh bounce replaces an old
|
|
95
|
+
* one, a block replaces a bounce, a member's mark replaces anything — else
|
|
96
|
+
* the current. `force` writes the incoming state whatever stands, which is
|
|
97
|
+
* how a release lands.
|
|
98
|
+
*/ export function nextEmailState(current, incoming, options = {}) {
|
|
99
|
+
if (!current || options.force) return incoming;
|
|
100
|
+
return EMAIL_STATE_RANK[incoming.status] >= EMAIL_STATE_RANK[current.status] ? incoming : current;
|
|
101
|
+
}
|
|
102
|
+
/** When the verdict was given, as the chip's tooltip and the refusal say it. */ export function emailStateWhen(state) {
|
|
103
|
+
if (!state.atMs) return '';
|
|
104
|
+
try {
|
|
105
|
+
return new Intl.DateTimeFormat('en-US', {
|
|
106
|
+
dateStyle: 'medium'
|
|
107
|
+
}).format(state.atMs);
|
|
108
|
+
} catch (unused) {
|
|
109
|
+
return new Date(state.atMs).toDateString();
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Why the record must not be emailed, in one sentence a disabled button or
|
|
114
|
+
* a refused enrollment can show — or `null` when it may be.
|
|
115
|
+
*/ export function emailStateRefusal(state) {
|
|
116
|
+
if (!state || !emailStateForbidsEmail(state)) return null;
|
|
117
|
+
const when = emailStateWhen(state);
|
|
118
|
+
const since = when ? ` on ${when}` : '';
|
|
119
|
+
const said = state.detail ? ` ${state.detail}` : '';
|
|
120
|
+
switch(state.status){
|
|
121
|
+
case 'bounced':
|
|
122
|
+
return `Email to this address bounced${since}: the mailbox does not exist.${said}`;
|
|
123
|
+
case 'blocked':
|
|
124
|
+
return `Their mail gateway blocked email to this address${since}.${said}`;
|
|
125
|
+
case 'unsubscribed':
|
|
126
|
+
return `They unsubscribed${since}.${said}`;
|
|
127
|
+
case 'complained':
|
|
128
|
+
return `They marked an email as spam${since}.${said}`;
|
|
129
|
+
case 'do_not_contact':
|
|
130
|
+
return `On the do-not-contact list${since}.${said}`;
|
|
131
|
+
default:
|
|
132
|
+
return null;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
//# sourceMappingURL=email-state.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../libs/aglyn/src/lib/app-utils/email-state.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 * A RECORD'S EMAIL STATE (AGL-3245).\n *\n * The lists the senders consult — the platform suppression list, a site's\n * suppressions, an organization's do-not-contact list — are keyed by a\n * hash nobody can read. The record a person opens is the lead or the\n * contact, and until now it said nothing: an address that hard-bounced at\n * 10:30 still read as \"Sent\" at 12:30, and a member reading it would have\n * emailed the person again by hand.\n *\n * So the record carries `emailState`: what the last verdict on its address\n * was, when, from which sender, and in the sender's own words. It is\n * WRITTEN BY THE PLATFORM ONLY — the outreach runtime, the campaign\n * webhook, a member's do-not-contact mark — never by a form, an import or\n * the record's own editor, and the rules hold clients off the field. It\n * is a mirror of the lists, not a list: a send still asks the lists, and\n * the record says what they hold in words a person can act on.\n *\n * Absent means nothing is known. `ok` is a verdict too — a release, a\n * confirmed re-subscription — and it is the one state that never\n * overwrites a refusal on its own: the ranking below decides.\n *==========================================*/\n\n/** Every state, weakest first. */\nexport const EMAIL_STATE_STATUSES = [\n /** The address may be emailed: released, or confirmed again. */\n 'ok',\n /** Mail to it bounced for good: the mailbox does not exist. */\n 'bounced',\n /** The recipient's mail gateway refused the sender on policy or reputation. */\n 'blocked',\n /** The unsubscribe link or header was used. */\n 'unsubscribed',\n /** A reply called the email spam, or the recipient reported it. */\n 'complained',\n /** On the organization's do-not-contact list — a member's or the runtime's entry. */\n 'do_not_contact',\n] as const\n\nexport type EmailStateStatus = (typeof EMAIL_STATE_STATUSES)[number]\n\n/** Which sender's verdict it is. */\nexport type EmailStateSource = 'sequence' | 'campaign' | 'member'\n\n/** The field on the lead and the contact. */\nexport const EMAIL_STATE_FIELD = 'emailState'\n\nexport interface EmailState {\n status: EmailStateStatus\n /** When the verdict was given, epoch ms. */\n atMs: number\n source: EmailStateSource\n /** The sender's own words: the bounce's diagnostic, a member's note. */\n detail: string | null\n /** The sequence enrollment it came from, when one did. */\n enrollmentId?: string\n}\n\n/** How a state reads on the chip — typed so a state cannot ship unlabeled. */\nexport const EMAIL_STATE_LABELS: Record<EmailStateStatus, string> = {\n ok: 'Email OK',\n bounced: 'Bounced',\n blocked: 'Blocked by their mail gateway',\n unsubscribed: 'Unsubscribed',\n complained: 'Marked as spam',\n do_not_contact: 'Do not contact',\n}\n\n/**\n * The rank a later verdict must reach to replace an earlier one. A member's\n * do-not-contact mark stands over every automatic verdict; a complaint\n * over an unsubscribe, which is the person's own act over a server's; a\n * block over a bounce, since the block names the whole domain; and `ok`\n * over nothing — a release is written on purpose, by a path that forces it.\n */\nconst EMAIL_STATE_RANK: Record<EmailStateStatus, number> = {\n ok: 0,\n bounced: 1,\n blocked: 2,\n unsubscribed: 3,\n complained: 4,\n do_not_contact: 5,\n}\n\nexport function isEmailStateStatus(value: unknown): value is EmailStateStatus {\n return typeof value === 'string' && (EMAIL_STATE_STATUSES as readonly string[]).includes(value)\n}\n\n/** The state a record holds, held to its shape, or `null` when it holds none. */\nexport function readEmailState(record: Record<string, unknown> | null | undefined): EmailState | null {\n const raw = record?.[EMAIL_STATE_FIELD]\n if (!raw || typeof raw !== 'object') return null\n const state = raw as Record<string, unknown>\n if (!isEmailStateStatus(state['status'])) return null\n const atMs = Number(state['atMs'])\n const source = state['source']\n const enrollmentId = typeof state['enrollmentId'] === 'string' && state['enrollmentId'] ? state['enrollmentId'] : undefined\n return {\n status: state['status'],\n atMs: Number.isFinite(atMs) && atMs > 0 ? atMs : 0,\n source: source === 'sequence' || source === 'campaign' || source === 'member' ? source : 'campaign',\n detail: typeof state['detail'] === 'string' && state['detail'] ? state['detail'] : null,\n ...(enrollmentId ? { enrollmentId } : {}),\n }\n}\n\n/** Whether the state means the address must not be emailed by hand. */\nexport function emailStateForbidsEmail(state: EmailState | null | undefined): boolean {\n return Boolean(state) && state?.status !== 'ok'\n}\n\n/**\n * The state a record holds after one more verdict: the incoming one when it\n * ranks at least as high as the current — a fresh bounce replaces an old\n * one, a block replaces a bounce, a member's mark replaces anything — else\n * the current. `force` writes the incoming state whatever stands, which is\n * how a release lands.\n */\nexport function nextEmailState(\n current: EmailState | null | undefined,\n incoming: EmailState,\n options: { force?: boolean } = {},\n): EmailState {\n if (!current || options.force) return incoming\n return EMAIL_STATE_RANK[incoming.status] >= EMAIL_STATE_RANK[current.status] ? incoming : current\n}\n\n/** When the verdict was given, as the chip's tooltip and the refusal say it. */\nexport function emailStateWhen(state: Pick<EmailState, 'atMs'>): string {\n if (!state.atMs) return ''\n try {\n return new Intl.DateTimeFormat('en-US', { dateStyle: 'medium' }).format(state.atMs)\n } catch {\n return new Date(state.atMs).toDateString()\n }\n}\n\n/**\n * Why the record must not be emailed, in one sentence a disabled button or\n * a refused enrollment can show — or `null` when it may be.\n */\nexport function emailStateRefusal(state: EmailState | null | undefined): string | null {\n if (!state || !emailStateForbidsEmail(state)) return null\n const when = emailStateWhen(state)\n const since = when ? ` on ${when}` : ''\n const said = state.detail ? ` ${state.detail}` : ''\n switch (state.status) {\n case 'bounced':\n return `Email to this address bounced${since}: the mailbox does not exist.${said}`\n case 'blocked':\n return `Their mail gateway blocked email to this address${since}.${said}`\n case 'unsubscribed':\n return `They unsubscribed${since}.${said}`\n case 'complained':\n return `They marked an email as spam${since}.${said}`\n case 'do_not_contact':\n return `On the do-not-contact list${since}.${said}`\n default:\n return null\n }\n}\n"],"names":["EMAIL_STATE_STATUSES","EMAIL_STATE_FIELD","EMAIL_STATE_LABELS","ok","bounced","blocked","unsubscribed","complained","do_not_contact","EMAIL_STATE_RANK","isEmailStateStatus","value","includes","readEmailState","record","raw","state","atMs","Number","source","enrollmentId","undefined","status","isFinite","detail","emailStateForbidsEmail","Boolean","nextEmailState","current","incoming","options","force","emailStateWhen","Intl","DateTimeFormat","dateStyle","format","Date","toDateString","emailStateRefusal","when","since","said"],"mappings":"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"}
|
|
@@ -1517,6 +1517,34 @@ export declare const SIGNUP_CANARY_ORG_SLUG_PREFIX = "signup-canary-";
|
|
|
1517
1517
|
* number looks wrong.
|
|
1518
1518
|
*/
|
|
1519
1519
|
export declare function isSignupCanaryOrgSlug(slug: string | null | undefined): boolean;
|
|
1520
|
+
/**
|
|
1521
|
+
* The plus tag every canary address carries.
|
|
1522
|
+
*
|
|
1523
|
+
* Identity Platform will not reuse an address, so the walk stamps a fresh one
|
|
1524
|
+
* per run by appending to the LOCAL PART of `SIGNUP_CANARY_EMAIL`:
|
|
1525
|
+
* `name+signup-canary@example.com` becomes
|
|
1526
|
+
* `name+signup-canary-m2rso9ab12@example.com`, which still reaches the same
|
|
1527
|
+
* mailbox. The tag is therefore the only part of a canary account that the
|
|
1528
|
+
* console can recognize at the moment it first sees one — there is no org yet,
|
|
1529
|
+
* no claim, and nothing on the token that says CI.
|
|
1530
|
+
*
|
|
1531
|
+
* The address is operator-supplied, so this is a convention rather than a
|
|
1532
|
+
* fact, and `tools/e2e/signup-canary.mjs` is what makes it one: it refuses to
|
|
1533
|
+
* start on a base address whose tag is not this. Held together by
|
|
1534
|
+
* `apps/console/specs/signup-canary-marker-wiring.spec.ts`, exactly as the
|
|
1535
|
+
* slug prefix above is.
|
|
1536
|
+
*/
|
|
1537
|
+
export declare const SIGNUP_CANARY_EMAIL_TAG = "signup-canary";
|
|
1538
|
+
/**
|
|
1539
|
+
* Is this sign-up the canary's own?
|
|
1540
|
+
*
|
|
1541
|
+
* The cost mirrors the slug prefix's: a stranger who signs up as
|
|
1542
|
+
* `them+signup-canary@example.com` goes unannounced. That is bounded to the
|
|
1543
|
+
* one address, it cannot hide anything staff need — the account still exists,
|
|
1544
|
+
* still appears in the admin user list, and still bills — and it is the price
|
|
1545
|
+
* of a check that needs no secret and no second code path.
|
|
1546
|
+
*/
|
|
1547
|
+
export declare function isSignupCanaryEmail(email: string | null | undefined): boolean;
|
|
1520
1548
|
/**
|
|
1521
1549
|
* How old the last walk may be before the check goes red.
|
|
1522
1550
|
*
|
|
@@ -1806,6 +1806,42 @@ export async function writeCronBeat(store, jobId, now = Date.now()) {
|
|
|
1806
1806
|
*/ export function isSignupCanaryOrgSlug(slug) {
|
|
1807
1807
|
return typeof slug === 'string' && slug.startsWith(SIGNUP_CANARY_ORG_SLUG_PREFIX);
|
|
1808
1808
|
}
|
|
1809
|
+
/**
|
|
1810
|
+
* The plus tag every canary address carries.
|
|
1811
|
+
*
|
|
1812
|
+
* Identity Platform will not reuse an address, so the walk stamps a fresh one
|
|
1813
|
+
* per run by appending to the LOCAL PART of `SIGNUP_CANARY_EMAIL`:
|
|
1814
|
+
* `name+signup-canary@example.com` becomes
|
|
1815
|
+
* `name+signup-canary-m2rso9ab12@example.com`, which still reaches the same
|
|
1816
|
+
* mailbox. The tag is therefore the only part of a canary account that the
|
|
1817
|
+
* console can recognize at the moment it first sees one — there is no org yet,
|
|
1818
|
+
* no claim, and nothing on the token that says CI.
|
|
1819
|
+
*
|
|
1820
|
+
* The address is operator-supplied, so this is a convention rather than a
|
|
1821
|
+
* fact, and `tools/e2e/signup-canary.mjs` is what makes it one: it refuses to
|
|
1822
|
+
* start on a base address whose tag is not this. Held together by
|
|
1823
|
+
* `apps/console/specs/signup-canary-marker-wiring.spec.ts`, exactly as the
|
|
1824
|
+
* slug prefix above is.
|
|
1825
|
+
*/ export const SIGNUP_CANARY_EMAIL_TAG = 'signup-canary';
|
|
1826
|
+
/**
|
|
1827
|
+
* Is this sign-up the canary's own?
|
|
1828
|
+
*
|
|
1829
|
+
* The cost mirrors the slug prefix's: a stranger who signs up as
|
|
1830
|
+
* `them+signup-canary@example.com` goes unannounced. That is bounded to the
|
|
1831
|
+
* one address, it cannot hide anything staff need — the account still exists,
|
|
1832
|
+
* still appears in the admin user list, and still bills — and it is the price
|
|
1833
|
+
* of a check that needs no secret and no second code path.
|
|
1834
|
+
*/ export function isSignupCanaryEmail(email) {
|
|
1835
|
+
var _email_toLowerCase_split_;
|
|
1836
|
+
if (typeof email !== 'string') return false;
|
|
1837
|
+
const local = (_email_toLowerCase_split_ = email.toLowerCase().split('@')[0]) != null ? _email_toLowerCase_split_ : '';
|
|
1838
|
+
const plus = local.indexOf('+');
|
|
1839
|
+
if (plus < 0) return false;
|
|
1840
|
+
// `startsWith`, not equality: the walk's own address is the base tag plus a
|
|
1841
|
+
// stamp, and the base address itself must match too — a human reading the
|
|
1842
|
+
// mailbox should not find the two classified differently.
|
|
1843
|
+
return local.slice(plus + 1).startsWith(SIGNUP_CANARY_EMAIL_TAG);
|
|
1844
|
+
}
|
|
1809
1845
|
/**
|
|
1810
1846
|
* How old the last walk may be before the check goes red.
|
|
1811
1847
|
*
|