@wizeworks/lens-cloud 0.3.0-alpha.1 → 0.3.0-alpha.2
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/README.md +57 -5
- package/dist/alerts/evaluate.d.ts.map +1 -1
- package/dist/alerts/evaluate.js +17 -2
- package/dist/alerts/evaluate.js.map +1 -1
- package/dist/alerts/notify.d.ts +13 -2
- package/dist/alerts/notify.d.ts.map +1 -1
- package/dist/alerts/notify.js +22 -16
- package/dist/alerts/notify.js.map +1 -1
- package/dist/app.d.ts +10 -2
- package/dist/app.d.ts.map +1 -1
- package/dist/auth/auth.d.ts +51 -0
- package/dist/auth/auth.d.ts.map +1 -0
- package/dist/auth/auth.js +63 -0
- package/dist/auth/auth.js.map +1 -0
- package/dist/auth/bridge.d.ts +7 -0
- package/dist/auth/bridge.d.ts.map +1 -0
- package/dist/auth/bridge.js +39 -0
- package/dist/auth/bridge.js.map +1 -0
- package/dist/auth/crypto.d.ts +0 -5
- package/dist/auth/crypto.d.ts.map +1 -1
- package/dist/auth/crypto.js +3 -20
- package/dist/auth/crypto.js.map +1 -1
- package/dist/auth/delete-user.d.ts +16 -0
- package/dist/auth/delete-user.d.ts.map +1 -0
- package/dist/auth/delete-user.js +21 -0
- package/dist/auth/delete-user.js.map +1 -0
- package/dist/auth/deps.d.ts +27 -0
- package/dist/auth/deps.d.ts.map +1 -0
- package/dist/auth/deps.js +3 -0
- package/dist/auth/deps.js.map +1 -0
- package/dist/auth/email-token.d.ts +8 -0
- package/dist/auth/email-token.d.ts.map +1 -0
- package/dist/auth/email-token.js +15 -0
- package/dist/auth/email-token.js.map +1 -0
- package/dist/auth/hooks.d.ts +4 -0
- package/dist/auth/hooks.d.ts.map +1 -0
- package/dist/auth/hooks.js +143 -0
- package/dist/auth/hooks.js.map +1 -0
- package/dist/auth/options.d.ts +17 -0
- package/dist/auth/options.d.ts.map +1 -0
- package/dist/auth/options.js +98 -0
- package/dist/auth/options.js.map +1 -0
- package/dist/auth/password.d.ts +10 -0
- package/dist/auth/password.d.ts.map +1 -0
- package/dist/auth/password.js +22 -0
- package/dist/auth/password.js.map +1 -0
- package/dist/auth/plugins.d.ts +4 -0
- package/dist/auth/plugins.d.ts.map +1 -0
- package/dist/auth/plugins.js +45 -0
- package/dist/auth/plugins.js.map +1 -0
- package/dist/auth/principal.d.ts +8 -3
- package/dist/auth/principal.d.ts.map +1 -1
- package/dist/auth/principal.js +13 -8
- package/dist/auth/principal.js.map +1 -1
- package/dist/auth/roles.d.ts +43 -3
- package/dist/auth/roles.d.ts.map +1 -1
- package/dist/auth/roles.js +15 -6
- package/dist/auth/roles.js.map +1 -1
- package/dist/auth/schema-names.d.ts +130 -0
- package/dist/auth/schema-names.d.ts.map +1 -0
- package/dist/auth/schema-names.js +53 -0
- package/dist/auth/schema-names.js.map +1 -0
- package/dist/auth/secret.d.ts +12 -0
- package/dist/auth/secret.d.ts.map +1 -0
- package/dist/auth/secret.js +32 -0
- package/dist/auth/secret.js.map +1 -0
- package/dist/auth/security-notices.d.ts +34 -0
- package/dist/auth/security-notices.d.ts.map +1 -0
- package/dist/auth/security-notices.js +81 -0
- package/dist/auth/security-notices.js.map +1 -0
- package/dist/auth/signup.d.ts +23 -0
- package/dist/auth/signup.d.ts.map +1 -0
- package/dist/auth/signup.js +61 -0
- package/dist/auth/signup.js.map +1 -0
- package/dist/auth/sso.d.ts +21 -0
- package/dist/auth/sso.d.ts.map +1 -0
- package/dist/auth/sso.js +35 -0
- package/dist/auth/sso.js.map +1 -0
- package/dist/auth/wire.d.ts +16 -0
- package/dist/auth/wire.d.ts.map +1 -0
- package/dist/auth/wire.js +47 -0
- package/dist/auth/wire.js.map +1 -0
- package/dist/cli-args.d.ts +1 -1
- package/dist/cli-args.d.ts.map +1 -1
- package/dist/cli-args.js +55 -14
- package/dist/cli-args.js.map +1 -1
- package/dist/cli.js +18 -2
- package/dist/cli.js.map +1 -1
- package/dist/config.d.ts +26 -4
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +35 -0
- package/dist/config.js.map +1 -1
- package/dist/db/db.d.ts +5 -2
- package/dist/db/db.d.ts.map +1 -1
- package/dist/db/db.js +15 -8
- package/dist/db/db.js.map +1 -1
- package/dist/db/migrate.d.ts +2 -2
- package/dist/db/migrate.d.ts.map +1 -1
- package/dist/db/migrate.js +15 -5
- package/dist/db/migrate.js.map +1 -1
- package/dist/db/schema-auth.d.ts +2 -0
- package/dist/db/schema-auth.d.ts.map +1 -0
- package/dist/db/schema-auth.js +93 -0
- package/dist/db/schema-auth.js.map +1 -0
- package/dist/db/schema-mail.d.ts +2 -0
- package/dist/db/schema-mail.d.ts.map +1 -0
- package/dist/db/schema-mail.js +8 -0
- package/dist/db/schema-mail.js.map +1 -0
- package/dist/db/schema-notify.d.ts +2 -0
- package/dist/db/schema-notify.d.ts.map +1 -0
- package/dist/db/schema-notify.js +26 -0
- package/dist/db/schema-notify.js.map +1 -0
- package/dist/db/schema-sso.d.ts +2 -0
- package/dist/db/schema-sso.d.ts.map +1 -0
- package/dist/db/schema-sso.js +17 -0
- package/dist/db/schema-sso.js.map +1 -0
- package/dist/db/schema.d.ts.map +1 -1
- package/dist/db/schema.js +8 -0
- package/dist/db/schema.js.map +1 -1
- package/dist/http/http.d.ts +2 -0
- package/dist/http/http.d.ts.map +1 -1
- package/dist/http/http.js +7 -3
- package/dist/http/http.js.map +1 -1
- package/dist/jobs.d.ts +1 -1
- package/dist/jobs.d.ts.map +1 -1
- package/dist/jobs.js +14 -3
- package/dist/jobs.js.map +1 -1
- package/dist/mail/blocks.d.ts +14 -0
- package/dist/mail/blocks.d.ts.map +1 -0
- package/dist/mail/blocks.js +35 -0
- package/dist/mail/blocks.js.map +1 -0
- package/dist/mail/content.d.ts +68 -0
- package/dist/mail/content.d.ts.map +1 -0
- package/dist/mail/content.js +6 -0
- package/dist/mail/content.js.map +1 -0
- package/dist/mail/emails-digest.d.ts +10 -0
- package/dist/mail/emails-digest.d.ts.map +1 -0
- package/dist/mail/emails-digest.js +55 -0
- package/dist/mail/emails-digest.js.map +1 -0
- package/dist/mail/emails-health.d.ts +6 -0
- package/dist/mail/emails-health.d.ts.map +1 -0
- package/dist/mail/emails-health.js +36 -0
- package/dist/mail/emails-health.js.map +1 -0
- package/dist/mail/emails-security.d.ts +14 -0
- package/dist/mail/emails-security.d.ts.map +1 -0
- package/dist/mail/emails-security.js +27 -0
- package/dist/mail/emails-security.js.map +1 -0
- package/dist/mail/emails-team.d.ts +11 -0
- package/dist/mail/emails-team.d.ts.map +1 -0
- package/dist/mail/emails-team.js +45 -0
- package/dist/mail/emails-team.js.map +1 -0
- package/dist/mail/emails.d.ts +9 -0
- package/dist/mail/emails.d.ts.map +1 -0
- package/dist/mail/emails.js +49 -0
- package/dist/mail/emails.js.map +1 -0
- package/dist/mail/layout.d.ts +6 -0
- package/dist/mail/layout.d.ts.map +1 -0
- package/dist/mail/layout.js +10 -0
- package/dist/mail/layout.js.map +1 -0
- package/dist/mail/mailer.d.ts +25 -0
- package/dist/mail/mailer.d.ts.map +1 -0
- package/dist/mail/mailer.js +49 -0
- package/dist/mail/mailer.js.map +1 -0
- package/dist/mail/preview.d.ts +6 -0
- package/dist/mail/preview.d.ts.map +1 -0
- package/dist/mail/preview.js +26 -0
- package/dist/mail/preview.js.map +1 -0
- package/dist/mail/resend.d.ts +11 -0
- package/dist/mail/resend.d.ts.map +1 -0
- package/dist/mail/resend.js +34 -0
- package/dist/mail/resend.js.map +1 -0
- package/dist/mail/samples.d.ts +3 -0
- package/dist/mail/samples.d.ts.map +1 -0
- package/dist/mail/samples.js +63 -0
- package/dist/mail/samples.js.map +1 -0
- package/dist/mail/stop-route.d.ts +4 -0
- package/dist/mail/stop-route.d.ts.map +1 -0
- package/dist/mail/stop-route.js +41 -0
- package/dist/mail/stop-route.js.map +1 -0
- package/dist/mail/svix.d.ts +8 -0
- package/dist/mail/svix.d.ts.map +1 -0
- package/dist/mail/svix.js +17 -0
- package/dist/mail/svix.js.map +1 -0
- package/dist/mail/templates.d.ts +12 -0
- package/dist/mail/templates.d.ts.map +1 -0
- package/dist/mail/templates.js +18 -0
- package/dist/mail/templates.js.map +1 -0
- package/dist/mail/text.d.ts +4 -0
- package/dist/mail/text.d.ts.map +1 -0
- package/dist/mail/text.js +17 -0
- package/dist/mail/text.js.map +1 -0
- package/dist/mail/theme.d.ts +26 -0
- package/dist/mail/theme.d.ts.map +1 -0
- package/dist/mail/theme.js +21 -0
- package/dist/mail/theme.js.map +1 -0
- package/dist/mail/unsubscribe.d.ts +14 -0
- package/dist/mail/unsubscribe.d.ts.map +1 -0
- package/dist/mail/unsubscribe.js +39 -0
- package/dist/mail/unsubscribe.js.map +1 -0
- package/dist/mail/webhook.d.ts +5 -0
- package/dist/mail/webhook.d.ts.map +1 -0
- package/dist/mail/webhook.js +41 -0
- package/dist/mail/webhook.js.map +1 -0
- package/dist/notify/digest-data.d.ts +31 -0
- package/dist/notify/digest-data.d.ts.map +1 -0
- package/dist/notify/digest-data.js +37 -0
- package/dist/notify/digest-data.js.map +1 -0
- package/dist/notify/digest.d.ts +17 -0
- package/dist/notify/digest.d.ts.map +1 -0
- package/dist/notify/digest.js +76 -0
- package/dist/notify/digest.js.map +1 -0
- package/dist/notify/health.d.ts +6 -0
- package/dist/notify/health.d.ts.map +1 -0
- package/dist/notify/health.js +43 -0
- package/dist/notify/health.js.map +1 -0
- package/dist/notify/team-describe.d.ts +22 -0
- package/dist/notify/team-describe.d.ts.map +1 -0
- package/dist/notify/team-describe.js +67 -0
- package/dist/notify/team-describe.js.map +1 -0
- package/dist/notify/team.d.ts +6 -0
- package/dist/notify/team.d.ts.map +1 -0
- package/dist/notify/team.js +56 -0
- package/dist/notify/team.js.map +1 -0
- package/dist/repo/alerts.d.ts +6 -2
- package/dist/repo/alerts.d.ts.map +1 -1
- package/dist/repo/alerts.js.map +1 -1
- package/dist/repo/auth-prune.d.ts +4 -0
- package/dist/repo/auth-prune.d.ts.map +1 -0
- package/dist/repo/auth-prune.js +8 -0
- package/dist/repo/auth-prune.js.map +1 -0
- package/dist/repo/mail-prefs.d.ts +35 -0
- package/dist/repo/mail-prefs.d.ts.map +1 -0
- package/dist/repo/mail-prefs.js +34 -0
- package/dist/repo/mail-prefs.js.map +1 -0
- package/dist/repo/orgs.d.ts +15 -25
- package/dist/repo/orgs.d.ts.map +1 -1
- package/dist/repo/orgs.js +18 -60
- package/dist/repo/orgs.js.map +1 -1
- package/dist/repo/suppressions.d.ts +8 -0
- package/dist/repo/suppressions.d.ts.map +1 -0
- package/dist/repo/suppressions.js +16 -0
- package/dist/repo/suppressions.js.map +1 -0
- package/dist/repo/users.d.ts +5 -16
- package/dist/repo/users.d.ts.map +1 -1
- package/dist/repo/users.js +2 -60
- package/dist/repo/users.js.map +1 -1
- package/dist/routes/alerts.d.ts.map +1 -1
- package/dist/routes/alerts.js +23 -6
- package/dist/routes/alerts.js.map +1 -1
- package/dist/routes/index.d.ts.map +1 -1
- package/dist/routes/index.js +4 -2
- package/dist/routes/index.js.map +1 -1
- package/dist/routes/me.d.ts +4 -0
- package/dist/routes/me.d.ts.map +1 -0
- package/dist/routes/me.js +37 -0
- package/dist/routes/me.js.map +1 -0
- package/dist/routes/notify.d.ts +4 -0
- package/dist/routes/notify.d.ts.map +1 -0
- package/dist/routes/notify.js +55 -0
- package/dist/routes/notify.js.map +1 -0
- package/dist/routes/orgs.d.ts.map +1 -1
- package/dist/routes/orgs.js +30 -53
- package/dist/routes/orgs.js.map +1 -1
- package/dist/routes/projects.d.ts.map +1 -1
- package/dist/routes/projects.js +4 -3
- package/dist/routes/projects.js.map +1 -1
- package/dist/server.d.ts +2 -0
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +45 -14
- package/dist/server.js.map +1 -1
- package/package.json +19 -6
- package/dist/routes/auth.d.ts +0 -4
- package/dist/routes/auth.d.ts.map +0 -1
- package/dist/routes/auth.js +0 -106
- package/dist/routes/auth.js.map +0 -1
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { facts, text } from "./content.js";
|
|
2
|
+
const clock = (ms) => `${new Date(ms).toISOString().slice(0, 16).replace("T", " ")} UTC`;
|
|
3
|
+
const span = (ms) => (ms >= 86_400_000 ? `${Math.round(ms / 86_400_000)} days` : `${Math.max(1, Math.round(ms / 3_600_000))} hours`);
|
|
4
|
+
/** A project that sends data around the clock stopped. */
|
|
5
|
+
export function projectQuiet(project, org, lastMs, nowMs, url, stop) {
|
|
6
|
+
return {
|
|
7
|
+
kind: "health",
|
|
8
|
+
subject: `${project} stopped sending data to WizeLens`,
|
|
9
|
+
preview: `No data from ${project} for ${span(nowMs - lastMs)}.`,
|
|
10
|
+
heading: `${project} went quiet.`,
|
|
11
|
+
tone: "bad",
|
|
12
|
+
blocks: [
|
|
13
|
+
text(`${project} in ${org} usually sends data every hour. Nothing arrived for ${span(nowMs - lastMs)}.`),
|
|
14
|
+
text("The app may be down, its add-on may have lost its key, or traffic may have stopped."),
|
|
15
|
+
facts([["Last data", clock(lastMs)], ["Quiet for", span(nowMs - lastMs)]]),
|
|
16
|
+
],
|
|
17
|
+
action: { label: `Open ${project}`, url },
|
|
18
|
+
footer: `You get this because you are an owner or admin of ${org}.`,
|
|
19
|
+
unsubscribe: { label: "Stop project health emails", url: stop },
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
/** Data arrives again after a quiet notice. */
|
|
23
|
+
export function projectBack(project, org, quietMs, url, stop) {
|
|
24
|
+
return {
|
|
25
|
+
kind: "health",
|
|
26
|
+
subject: `${project} is sending data to WizeLens again`,
|
|
27
|
+
preview: `Data from ${project} arrives again after ${span(quietMs)}.`,
|
|
28
|
+
heading: `${project} is back.`,
|
|
29
|
+
tone: "good",
|
|
30
|
+
blocks: [text(`Data from ${project} in ${org} arrives again. It was quiet for about ${span(quietMs)}.`)],
|
|
31
|
+
action: { label: `Open ${project}`, url },
|
|
32
|
+
footer: `You get this because you are an owner or admin of ${org}.`,
|
|
33
|
+
unsubscribe: { label: "Stop project health emails", url: stop },
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
//# sourceMappingURL=emails-health.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"emails-health.js","sourceRoot":"","sources":["../../src/mail/emails-health.ts"],"names":[],"mappings":"AAAA,OAAO,EAAqB,KAAK,EAAE,IAAI,EAAE,MAAM,cAAc,CAAC;AAE9D,MAAM,KAAK,GAAG,CAAC,EAAU,EAAE,EAAE,CAAC,GAAG,IAAI,IAAI,CAAC,EAAE,CAAC,CAAC,WAAW,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,GAAG,EAAE,GAAG,CAAC,MAAM,CAAC;AACjG,MAAM,IAAI,GAAG,CAAC,EAAU,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,UAAU,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,EAAE,GAAG,UAAU,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,EAAE,GAAG,SAAS,CAAC,CAAC,QAAQ,CAAC,CAAC;AAE7I,0DAA0D;AAC1D,MAAM,UAAU,YAAY,CAAC,OAAe,EAAE,GAAW,EAAE,MAAc,EAAE,KAAa,EAAE,GAAW,EAAE,IAAY;IACjH,OAAO;QACL,IAAI,EAAE,QAAQ;QACd,OAAO,EAAE,GAAG,OAAO,mCAAmC;QACtD,OAAO,EAAE,gBAAgB,OAAO,QAAQ,IAAI,CAAC,KAAK,GAAG,MAAM,CAAC,GAAG;QAC/D,OAAO,EAAE,GAAG,OAAO,cAAc;QACjC,IAAI,EAAE,KAAK;QACX,MAAM,EAAE;YACN,IAAI,CAAC,GAAG,OAAO,OAAO,GAAG,uDAAuD,IAAI,CAAC,KAAK,GAAG,MAAM,CAAC,GAAG,CAAC;YACxG,IAAI,CAAC,qFAAqF,CAAC;YAC3F,KAAK,CAAC,CAAC,CAAC,WAAW,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,WAAW,EAAE,IAAI,CAAC,KAAK,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC;SAC3E;QACD,MAAM,EAAE,EAAE,KAAK,EAAE,QAAQ,OAAO,EAAE,EAAE,GAAG,EAAE;QACzC,MAAM,EAAE,qDAAqD,GAAG,GAAG;QACnE,WAAW,EAAE,EAAE,KAAK,EAAE,4BAA4B,EAAE,GAAG,EAAE,IAAI,EAAE;KAChE,CAAC;AACJ,CAAC;AAED,+CAA+C;AAC/C,MAAM,UAAU,WAAW,CAAC,OAAe,EAAE,GAAW,EAAE,OAAe,EAAE,GAAW,EAAE,IAAY;IAClG,OAAO;QACL,IAAI,EAAE,QAAQ;QACd,OAAO,EAAE,GAAG,OAAO,oCAAoC;QACvD,OAAO,EAAE,aAAa,OAAO,wBAAwB,IAAI,CAAC,OAAO,CAAC,GAAG;QACrE,OAAO,EAAE,GAAG,OAAO,WAAW;QAC9B,IAAI,EAAE,MAAM;QACZ,MAAM,EAAE,CAAC,IAAI,CAAC,aAAa,OAAO,OAAO,GAAG,0CAA0C,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QACxG,MAAM,EAAE,EAAE,KAAK,EAAE,QAAQ,OAAO,EAAE,EAAE,GAAG,EAAE;QACzC,MAAM,EAAE,qDAAqD,GAAG,GAAG;QACnE,WAAW,EAAE,EAAE,KAAK,EAAE,4BAA4B,EAAE,GAAG,EAAE,IAAI,EAAE;KAChE,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { type EmailContent } from "./content.js";
|
|
2
|
+
export interface SecurityLinks {
|
|
3
|
+
/** The Account page, to review devices and sign-in settings. */
|
|
4
|
+
account: string;
|
|
5
|
+
/** The page that sends a reset link, for when the person cannot sign in. */
|
|
6
|
+
forgot: string;
|
|
7
|
+
}
|
|
8
|
+
export declare const passwordChanged: (l: SecurityLinks, atMs: number) => EmailContent;
|
|
9
|
+
export declare const emailChanged: (l: SecurityLinks, newEmail: string, atMs: number) => EmailContent;
|
|
10
|
+
export declare const twoFactorChanged: (l: SecurityLinks, on: boolean, atMs: number) => EmailContent;
|
|
11
|
+
export declare const passkeyChanged: (l: SecurityLinks, added: boolean, name: string, atMs: number) => EmailContent;
|
|
12
|
+
export declare const backupCodesMade: (l: SecurityLinks, atMs: number) => EmailContent;
|
|
13
|
+
export declare const newSignIn: (l: SecurityLinks, device: string, address: string, atMs: number) => EmailContent;
|
|
14
|
+
//# sourceMappingURL=emails-security.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"emails-security.d.ts","sourceRoot":"","sources":["../../src/mail/emails-security.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,YAAY,EAAqB,MAAM,cAAc,CAAC;AAOpE,MAAM,WAAW,aAAa;IAC5B,gEAAgE;IAChE,OAAO,EAAE,MAAM,CAAC;IAChB,4EAA4E;IAC5E,MAAM,EAAE,MAAM,CAAC;CAChB;AAQD,eAAO,MAAM,eAAe,MAAO,aAAa,QAAQ,MAAM,iBAI1D,CAAC;AAEL,eAAO,MAAM,YAAY,MAAO,aAAa,YAAY,MAAM,QAAQ,MAAM,iBAIzE,CAAC;AAEL,eAAO,MAAM,gBAAgB,MAAO,aAAa,MAAM,OAAO,QAAQ,MAAM,iBAQzE,CAAC;AAEJ,eAAO,MAAM,cAAc,MAAO,aAAa,SAAS,OAAO,QAAQ,MAAM,QAAQ,MAAM,iBAQxF,CAAC;AAEJ,eAAO,MAAM,eAAe,MAAO,aAAa,QAAQ,MAAM,iBAI1D,CAAC;AAEL,eAAO,MAAM,SAAS,MAAO,aAAa,UAAU,MAAM,WAAW,MAAM,QAAQ,MAAM,iBAIrF,CAAC"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { facts, note, text } from "./content.js";
|
|
2
|
+
// Notices about changes to the person's own account. They always go out: they are how a taken account gets noticed.
|
|
3
|
+
const FOOTER = "We send a notice for every security change to your account. These notices cannot be turned off.";
|
|
4
|
+
const NOT_YOU = "If this was not you, reset your password now. It signs out every device.";
|
|
5
|
+
const when = (ms) => `${new Date(ms).toISOString().slice(0, 16).replace("T", " ")} UTC`;
|
|
6
|
+
function notice(subject, heading, lines, tone, rows, action) {
|
|
7
|
+
return { kind: "security", subject, preview: lines[0], heading, tone, blocks: [...lines.map(text), facts(rows), note(NOT_YOU)], action, footer: FOOTER };
|
|
8
|
+
}
|
|
9
|
+
export const passwordChanged = (l, atMs) => notice("Your WizeLens password was changed", "Your password changed.", ["The password of your WizeLens account was changed. Other devices were signed out."], "neutral", [["When", when(atMs)]], {
|
|
10
|
+
label: "Reset your password",
|
|
11
|
+
url: l.forgot,
|
|
12
|
+
});
|
|
13
|
+
export const emailChanged = (l, newEmail, atMs) => notice("Your WizeLens email was changed", "Your email changed.", [`Your WizeLens account now uses ${newEmail}. This address gets no more mail from it.`], "warn", [["New email", newEmail], ["When", when(atMs)]], {
|
|
14
|
+
label: "Get back in",
|
|
15
|
+
url: l.forgot,
|
|
16
|
+
});
|
|
17
|
+
export const twoFactorChanged = (l, on, atMs) => notice(on ? "Two-step sign-in is on for WizeLens" : "Two-step sign-in is off for WizeLens", on ? "Two-step sign-in is on." : "Two-step sign-in is off.", [on ? "Signing in now needs a code from your authenticator app, or a backup code." : "Signing in now needs only your password or a passkey."], on ? "good" : "warn", [["When", when(atMs)]], { label: "Review your account", url: l.account });
|
|
18
|
+
export const passkeyChanged = (l, added, name, atMs) => notice(added ? "A passkey was added to your WizeLens account" : "A passkey was removed from your WizeLens account", added ? "New passkey." : "Passkey removed.", [added ? "A new passkey can now sign in to your account." : "A passkey can no longer sign in to your account."], added ? "warn" : "neutral", [["Passkey", name || "Unnamed"], ["When", when(atMs)]], { label: "Review your passkeys", url: l.account });
|
|
19
|
+
export const backupCodesMade = (l, atMs) => notice("New WizeLens backup codes", "New backup codes.", ["New backup codes were made for two-step sign-in. The old codes no longer work."], "neutral", [["When", when(atMs)]], {
|
|
20
|
+
label: "Review your account",
|
|
21
|
+
url: l.account,
|
|
22
|
+
});
|
|
23
|
+
export const newSignIn = (l, device, address, atMs) => notice("New sign-in to your WizeLens account", "New sign-in.", [`Your account was signed in on ${device}, a device it has not used before.`], "warn", [["Device", device], ["Address", address || "Unknown"], ["When", when(atMs)]], {
|
|
24
|
+
label: "Review your devices",
|
|
25
|
+
url: l.account,
|
|
26
|
+
});
|
|
27
|
+
//# sourceMappingURL=emails-security.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"emails-security.js","sourceRoot":"","sources":["../../src/mail/emails-security.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { type EmailContent } from "./content.js";
|
|
2
|
+
/** Changes in one organization since the last notice, for its owners and admins. */
|
|
3
|
+
export declare function teamChanges(org: string, changes: {
|
|
4
|
+
text: string;
|
|
5
|
+
timeMs: number;
|
|
6
|
+
}[], url: string, stop: string): EmailContent;
|
|
7
|
+
/** A member's own role changed. */
|
|
8
|
+
export declare function roleChanged(org: string, role: string, url: string, stop: string): EmailContent;
|
|
9
|
+
/** An organization and all its data are gone. Sent to every member, always. */
|
|
10
|
+
export declare function orgDeleted(org: string, by: string, url: string): EmailContent;
|
|
11
|
+
//# sourceMappingURL=emails-team.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"emails-team.d.ts","sourceRoot":"","sources":["../../src/mail/emails-team.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,YAAY,EAAc,MAAM,cAAc,CAAC;AAI7D,oFAAoF;AACpF,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,EAAE,EAAE,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,YAAY,CAa7H;AAED,mCAAmC;AACnC,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,YAAY,CAY9F;AAED,+EAA+E;AAC/E,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,YAAY,CAW7E"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { list, text } from "./content.js";
|
|
2
|
+
const clock = (ms) => `${new Date(ms).toISOString().slice(0, 16).replace("T", " ")} UTC`;
|
|
3
|
+
/** Changes in one organization since the last notice, for its owners and admins. */
|
|
4
|
+
export function teamChanges(org, changes, url, stop) {
|
|
5
|
+
const one = changes.length === 1;
|
|
6
|
+
return {
|
|
7
|
+
kind: "team",
|
|
8
|
+
subject: one ? `${org}: ${changes[0].text}` : `${changes.length} changes in ${org} on WizeLens`,
|
|
9
|
+
preview: changes[0].text,
|
|
10
|
+
heading: `Changes in ${org}.`,
|
|
11
|
+
tone: "neutral",
|
|
12
|
+
blocks: [list(one ? "What changed" : `${changes.length} changes`, changes.map((c) => ({ text: c.text, meta: clock(c.timeMs) })))],
|
|
13
|
+
action: { label: "Open the audit log", url },
|
|
14
|
+
footer: `You get this because you are an owner or admin of ${org}.`,
|
|
15
|
+
unsubscribe: { label: "Stop team change emails", url: stop },
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
/** A member's own role changed. */
|
|
19
|
+
export function roleChanged(org, role, url, stop) {
|
|
20
|
+
return {
|
|
21
|
+
kind: "team",
|
|
22
|
+
subject: `Your role in ${org} is now ${role}`,
|
|
23
|
+
preview: `You are now ${role} in ${org}.`,
|
|
24
|
+
heading: `You are now ${role}.`,
|
|
25
|
+
tone: "neutral",
|
|
26
|
+
blocks: [text(`Your role in ${org} on WizeLens changed to ${role}. What you can see and change follows the new role.`)],
|
|
27
|
+
action: { label: `Open ${org}`, url },
|
|
28
|
+
footer: `You get this because you are a member of ${org}.`,
|
|
29
|
+
unsubscribe: { label: "Stop team change emails", url: stop },
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
/** An organization and all its data are gone. Sent to every member, always. */
|
|
33
|
+
export function orgDeleted(org, by, url) {
|
|
34
|
+
return {
|
|
35
|
+
kind: "team",
|
|
36
|
+
subject: `${org} was deleted on WizeLens`,
|
|
37
|
+
preview: `${by} deleted ${org} and all its data.`,
|
|
38
|
+
heading: `${org} was deleted.`,
|
|
39
|
+
tone: "bad",
|
|
40
|
+
blocks: [text(`${by} deleted ${org} on WizeLens. Its projects, traces, keys, and alerts are gone. This cannot be undone.`)],
|
|
41
|
+
action: { label: "Open WizeLens", url },
|
|
42
|
+
footer: `You get this because you were a member of ${org}.`,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
//# sourceMappingURL=emails-team.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"emails-team.js","sourceRoot":"","sources":["../../src/mail/emails-team.ts"],"names":[],"mappings":"AAAA,OAAO,EAAqB,IAAI,EAAE,IAAI,EAAE,MAAM,cAAc,CAAC;AAE7D,MAAM,KAAK,GAAG,CAAC,EAAU,EAAE,EAAE,CAAC,GAAG,IAAI,IAAI,CAAC,EAAE,CAAC,CAAC,WAAW,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,GAAG,EAAE,GAAG,CAAC,MAAM,CAAC;AAEjG,oFAAoF;AACpF,MAAM,UAAU,WAAW,CAAC,GAAW,EAAE,OAA2C,EAAE,GAAW,EAAE,IAAY;IAC7G,MAAM,GAAG,GAAG,OAAO,CAAC,MAAM,KAAK,CAAC,CAAC;IACjC,OAAO;QACL,IAAI,EAAE,MAAM;QACZ,OAAO,EAAE,GAAG,CAAC,CAAC,CAAC,GAAG,GAAG,KAAK,OAAO,CAAC,CAAC,CAAE,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,MAAM,eAAe,GAAG,cAAc;QAChG,OAAO,EAAE,OAAO,CAAC,CAAC,CAAE,CAAC,IAAI;QACzB,OAAO,EAAE,cAAc,GAAG,GAAG;QAC7B,IAAI,EAAE,SAAS;QACf,MAAM,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,cAAc,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,MAAM,UAAU,EAAE,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,IAAI,EAAE,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;QACjI,MAAM,EAAE,EAAE,KAAK,EAAE,oBAAoB,EAAE,GAAG,EAAE;QAC5C,MAAM,EAAE,qDAAqD,GAAG,GAAG;QACnE,WAAW,EAAE,EAAE,KAAK,EAAE,yBAAyB,EAAE,GAAG,EAAE,IAAI,EAAE;KAC7D,CAAC;AACJ,CAAC;AAED,mCAAmC;AACnC,MAAM,UAAU,WAAW,CAAC,GAAW,EAAE,IAAY,EAAE,GAAW,EAAE,IAAY;IAC9E,OAAO;QACL,IAAI,EAAE,MAAM;QACZ,OAAO,EAAE,gBAAgB,GAAG,WAAW,IAAI,EAAE;QAC7C,OAAO,EAAE,eAAe,IAAI,OAAO,GAAG,GAAG;QACzC,OAAO,EAAE,eAAe,IAAI,GAAG;QAC/B,IAAI,EAAE,SAAS;QACf,MAAM,EAAE,CAAC,IAAI,CAAC,gBAAgB,GAAG,2BAA2B,IAAI,qDAAqD,CAAC,CAAC;QACvH,MAAM,EAAE,EAAE,KAAK,EAAE,QAAQ,GAAG,EAAE,EAAE,GAAG,EAAE;QACrC,MAAM,EAAE,4CAA4C,GAAG,GAAG;QAC1D,WAAW,EAAE,EAAE,KAAK,EAAE,yBAAyB,EAAE,GAAG,EAAE,IAAI,EAAE;KAC7D,CAAC;AACJ,CAAC;AAED,+EAA+E;AAC/E,MAAM,UAAU,UAAU,CAAC,GAAW,EAAE,EAAU,EAAE,GAAW;IAC7D,OAAO;QACL,IAAI,EAAE,MAAM;QACZ,OAAO,EAAE,GAAG,GAAG,0BAA0B;QACzC,OAAO,EAAE,GAAG,EAAE,YAAY,GAAG,oBAAoB;QACjD,OAAO,EAAE,GAAG,GAAG,eAAe;QAC9B,IAAI,EAAE,KAAK;QACX,MAAM,EAAE,CAAC,IAAI,CAAC,GAAG,EAAE,YAAY,GAAG,uFAAuF,CAAC,CAAC;QAC3H,MAAM,EAAE,EAAE,KAAK,EAAE,eAAe,EAAE,GAAG,EAAE;QACvC,MAAM,EAAE,6CAA6C,GAAG,GAAG;KAC5D,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { Notice } from "../alerts/notify.js";
|
|
2
|
+
import { type EmailContent } from "./content.js";
|
|
3
|
+
export declare const verifyEmail: (url: string) => EmailContent;
|
|
4
|
+
export declare const resetPassword: (url: string) => EmailContent;
|
|
5
|
+
export declare const changeEmail: (newEmail: string, url: string) => EmailContent;
|
|
6
|
+
export declare const deleteAccount: (url: string) => EmailContent;
|
|
7
|
+
export declare function invite(inviter: string, org: string, role: string, url: string): EmailContent;
|
|
8
|
+
export declare function alert(n: Notice): EmailContent;
|
|
9
|
+
//# sourceMappingURL=emails.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"emails.d.ts","sourceRoot":"","sources":["../../src/mail/emails.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAC;AAClD,OAAO,EAAE,KAAK,YAAY,EAAqB,MAAM,cAAc,CAAC;AAUpE,eAAO,MAAM,WAAW,QAAS,MAAM,iBACsI,CAAC;AAE9K,eAAO,MAAM,aAAa,QAAS,MAAM,iBAIrC,CAAC;AAEL,eAAO,MAAM,WAAW,aAAc,MAAM,OAAO,MAAM,iBAIrD,CAAC;AAEL,eAAO,MAAM,aAAa,QAAS,MAAM,iBAIrC,CAAC;AAEL,wBAAgB,MAAM,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,YAAY,CAW5F;AAMD,wBAAgB,KAAK,CAAC,CAAC,EAAE,MAAM,GAAG,YAAY,CAY7C"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { facts, note, text } from "./content.js";
|
|
2
|
+
// Every email the cloud sends, as content only. The words follow the README's voice: plain, short, concrete.
|
|
3
|
+
const IGNORE = "If you did not ask for this, you can ignore this email. Nothing changes.";
|
|
4
|
+
const ACCOUNT_FOOTER = "You got this email because someone used this address on WizeLens.";
|
|
5
|
+
function account(subject, heading, lines, action) {
|
|
6
|
+
return { kind: "account", subject, preview: lines[0], heading, tone: "neutral", blocks: [...lines.map(text), note(IGNORE)], action, footer: ACCOUNT_FOOTER };
|
|
7
|
+
}
|
|
8
|
+
export const verifyEmail = (url) => account("Confirm your email for WizeLens", "Confirm your email.", ["Confirm that this is your email address. The link works for 1 hour."], { label: "Confirm email", url });
|
|
9
|
+
export const resetPassword = (url) => account("Reset your WizeLens password", "Reset your password.", ["Someone asked to reset the password of this account. The link works for 30 minutes, once.", "A new password signs out every device."], {
|
|
10
|
+
label: "Set a new password",
|
|
11
|
+
url,
|
|
12
|
+
});
|
|
13
|
+
export const changeEmail = (newEmail, url) => account("Confirm your new WizeLens email", "Confirm the new email.", [`Someone asked to change the email of this account to ${newEmail}.`, "Confirm it here first. Then we send a link to the new address."], {
|
|
14
|
+
label: "Confirm the change",
|
|
15
|
+
url,
|
|
16
|
+
});
|
|
17
|
+
export const deleteAccount = (url) => account("Confirm deleting your WizeLens account", "Delete your account?", ["Someone asked to delete this account. This cannot be undone. The link works for 1 day."], {
|
|
18
|
+
label: "Delete my account",
|
|
19
|
+
url,
|
|
20
|
+
});
|
|
21
|
+
export function invite(inviter, org, role, url) {
|
|
22
|
+
return {
|
|
23
|
+
kind: "invite",
|
|
24
|
+
subject: `${inviter} invited you to ${org} on WizeLens`,
|
|
25
|
+
preview: `Join ${org} as ${role}.`,
|
|
26
|
+
heading: `Join ${org}.`,
|
|
27
|
+
tone: "neutral",
|
|
28
|
+
blocks: [text(`${inviter} invited you to join ${org} on WizeLens as ${role}.`), facts([["Team", org], ["Role", role], ["Works for", "7 days"]])],
|
|
29
|
+
action: { label: `Join ${org}`, url },
|
|
30
|
+
footer: `${inviter} sent this invite. If you do not know them, ignore it.`,
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
const VERB = { fired: "Alert", resolved: "Resolved", test: "Test" };
|
|
34
|
+
const TONE = { fired: "bad", resolved: "good", test: "neutral" };
|
|
35
|
+
const number = (v) => (v === null ? "no data" : String(Math.round(v * 100) / 100));
|
|
36
|
+
export function alert(n) {
|
|
37
|
+
const when = `${new Date(n.timeMs).toISOString().slice(0, 16).replace("T", " ")} UTC`;
|
|
38
|
+
return {
|
|
39
|
+
kind: "alert",
|
|
40
|
+
subject: `${VERB[n.state]}: ${n.rule} (${n.project})`,
|
|
41
|
+
preview: n.message,
|
|
42
|
+
heading: `${VERB[n.state]}: ${n.rule}.`,
|
|
43
|
+
tone: TONE[n.state],
|
|
44
|
+
blocks: [text(n.message), facts([["Project", n.project], ["Value", number(n.value)], ["Threshold", number(n.threshold)], ["When", when]])],
|
|
45
|
+
action: { label: "Open the alert", url: n.url },
|
|
46
|
+
footer: `You get this because an alert rule in ${n.project} emails you. Change it on the Alerts page.`,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
//# sourceMappingURL=emails.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"emails.js","sourceRoot":"","sources":["../../src/mail/emails.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { EmailContent } from "./content.js";
|
|
2
|
+
/** The frame every email shares: a light page, one white card, the header, the body, and the footer under the card. */
|
|
3
|
+
export declare function EmailLayout({ content: c }: {
|
|
4
|
+
content: EmailContent;
|
|
5
|
+
}): import("react").JSX.Element;
|
|
6
|
+
//# sourceMappingURL=layout.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"layout.d.ts","sourceRoot":"","sources":["../../src/mail/layout.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAKjD,uHAAuH;AACvH,wBAAgB,WAAW,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,EAAE;IAAE,OAAO,EAAE,YAAY,CAAA;CAAE,+BAwCpE"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Body, Container, Head, Heading, Hr, Html, Link, Preview, Section, Text } from "@react-email/components";
|
|
3
|
+
import { Action, BlockView, Header } from "./blocks.js";
|
|
4
|
+
import { theme } from "./theme.js";
|
|
5
|
+
const footerText = { margin: "0 0 4px", fontSize: "12px", lineHeight: "18px", color: theme.muted };
|
|
6
|
+
/** The frame every email shares: a light page, one white card, the header, the body, and the footer under the card. */
|
|
7
|
+
export function EmailLayout({ content: c }) {
|
|
8
|
+
return (_jsxs(Html, { lang: "en", children: [_jsxs(Head, { children: [_jsx("meta", { name: "color-scheme", content: "light" }), _jsx("meta", { name: "supported-color-schemes", content: "light" })] }), _jsx(Preview, { children: c.preview }), _jsxs(Body, { style: { margin: 0, padding: "32px 12px", background: theme.page, fontFamily: theme.sans }, children: [_jsxs(Container, { style: { maxWidth: `${theme.width}px`, background: theme.card, border: `1px solid ${theme.line}`, borderRadius: theme.radius }, children: [_jsx(Header, { tone: c.tone }), _jsxs(Section, { style: { padding: "8px 32px 8px" }, children: [_jsx(Heading, { as: "h1", style: { margin: "16px 0", fontSize: "26px", lineHeight: "32px", fontWeight: 800, letterSpacing: "-0.02em", color: theme.ink }, children: c.heading }), c.blocks.map((b, i) => (_jsx(BlockView, { block: b }, i))), c.action && _jsx(Action, { action: c.action })] })] }), _jsxs(Container, { style: { maxWidth: `${theme.width}px`, padding: "16px 32px 0" }, children: [_jsx(Text, { style: footerText, children: c.footer }), c.unsubscribe && (_jsx(Text, { style: footerText, children: _jsx(Link, { href: c.unsubscribe.url, style: { color: theme.muted, textDecoration: "underline" }, children: c.unsubscribe.label }) })), _jsx(Hr, { style: { margin: "12px 0", borderColor: theme.line } }), _jsx(Text, { style: footerText, children: _jsx(Link, { href: theme.homeUrl, style: { color: theme.muted }, children: theme.company }) })] })] })] }));
|
|
9
|
+
}
|
|
10
|
+
//# sourceMappingURL=layout.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"layout.js","sourceRoot":"","sources":["../../src/mail/layout.tsx"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { CloudConfig } from "../config.js";
|
|
2
|
+
import type { Queryable } from "../db/db.js";
|
|
3
|
+
/** "account": mail the person asked for. "security": notices about their own account. The rest: mail someone or something else started. */
|
|
4
|
+
export type MailKind = "account" | "security" | "invite" | "alert" | "team" | "health" | "digest";
|
|
5
|
+
export interface Mail {
|
|
6
|
+
kind: MailKind;
|
|
7
|
+
to: string;
|
|
8
|
+
subject: string;
|
|
9
|
+
text: string;
|
|
10
|
+
html: string;
|
|
11
|
+
/** Extra headers, for example List-Unsubscribe. */
|
|
12
|
+
headers?: Record<string, string>;
|
|
13
|
+
}
|
|
14
|
+
export type MailVia = "resend" | "smtp" | "test" | "log";
|
|
15
|
+
export interface Mailer {
|
|
16
|
+
/** True when mail really leaves (Resend, SMTP, or a test sender). False: mail is written to the log only. */
|
|
17
|
+
readonly live: boolean;
|
|
18
|
+
readonly via: MailVia;
|
|
19
|
+
/** Queue one email (or one still rendering). Never throws and never waits, so a slow mail server cannot slow or reveal anything. */
|
|
20
|
+
send(mail: Mail | Promise<Mail>): void;
|
|
21
|
+
close(): Promise<void>;
|
|
22
|
+
}
|
|
23
|
+
/** The cloud's mail, with the suppression list checked before each send. */
|
|
24
|
+
export declare function createMailer(config: CloudConfig, db: Queryable): Promise<Mailer>;
|
|
25
|
+
//# sourceMappingURL=mailer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mailer.d.ts","sourceRoot":"","sources":["../../src/mail/mailer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAChD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAI7C,2IAA2I;AAC3I,MAAM,MAAM,QAAQ,GAAG,SAAS,GAAG,UAAU,GAAG,QAAQ,GAAG,OAAO,GAAG,MAAM,GAAG,QAAQ,GAAG,QAAQ,CAAC;AAKlG,MAAM,WAAW,IAAI;IACnB,IAAI,EAAE,QAAQ,CAAC;IACf,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,mDAAmD;IACnD,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAClC;AAED,MAAM,MAAM,OAAO,GAAG,QAAQ,GAAG,MAAM,GAAG,MAAM,GAAG,KAAK,CAAC;AAEzD,MAAM,WAAW,MAAM;IACrB,6GAA6G;IAC7G,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IACvB,QAAQ,CAAC,GAAG,EAAE,OAAO,CAAC;IACtB,oIAAoI;IACpI,IAAI,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IACvC,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxB;AAiCD,4EAA4E;AAC5E,wBAAsB,YAAY,CAAC,MAAM,EAAE,WAAW,EAAE,EAAE,EAAE,SAAS,GAAG,OAAO,CAAC,MAAM,CAAC,CAgBtF"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { suppressionOf } from "../repo/suppressions.js";
|
|
2
|
+
import { resendSender } from "./resend.js";
|
|
3
|
+
/** Kinds a spam complaint does not stop: the person asked for them, or they protect the account. */
|
|
4
|
+
const ALWAYS = new Set(["account", "security"]);
|
|
5
|
+
async function smtpSender(config) {
|
|
6
|
+
const { createTransport } = await import("nodemailer");
|
|
7
|
+
const transport = createTransport(config.smtpUrl, { from: config.mailFrom });
|
|
8
|
+
return { via: "smtp", send: async (m) => void (await transport.sendMail({ from: config.mailFrom, ...m })), close: async () => transport.close() };
|
|
9
|
+
}
|
|
10
|
+
const logSender = async (m) => console.info(`[wizelens-cloud] mail (no mail service set, not sent) to ${m.to}: ${m.subject}\n${m.text}\n`);
|
|
11
|
+
/** The test sender, then Resend, then SMTP, then the log. */
|
|
12
|
+
async function transportOf(config) {
|
|
13
|
+
if (config.sendMail)
|
|
14
|
+
return { via: "test", send: config.sendMail };
|
|
15
|
+
if (config.resendApiKey)
|
|
16
|
+
return { via: "resend", send: resendSender({ apiKey: config.resendApiKey, from: config.mailFrom, fetch: config.fetch }) };
|
|
17
|
+
if (config.smtpUrl)
|
|
18
|
+
return smtpSender(config);
|
|
19
|
+
return { via: "log", send: logSender };
|
|
20
|
+
}
|
|
21
|
+
/** A bounce skips every email. A complaint skips mail the person did not ask for. */
|
|
22
|
+
async function skipReason(db, mail) {
|
|
23
|
+
const reason = await suppressionOf(db, mail.to);
|
|
24
|
+
if (reason === "bounce")
|
|
25
|
+
return "it bounced before";
|
|
26
|
+
if (reason === "complaint" && !ALWAYS.has(mail.kind))
|
|
27
|
+
return "it marked our mail as spam";
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
/** The cloud's mail, with the suppression list checked before each send. */
|
|
31
|
+
export async function createMailer(config, db) {
|
|
32
|
+
const t = await transportOf(config);
|
|
33
|
+
const deliver = async (pending) => {
|
|
34
|
+
const mail = await pending;
|
|
35
|
+
const skip = await skipReason(db, mail);
|
|
36
|
+
if (skip)
|
|
37
|
+
return console.info(`[wizelens-cloud] mail to ${mail.to} skipped: ${skip}.`);
|
|
38
|
+
await t.send(mail).catch((err) => {
|
|
39
|
+
throw new Error(`mail to ${mail.to} failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
40
|
+
});
|
|
41
|
+
};
|
|
42
|
+
return {
|
|
43
|
+
live: t.via !== "log",
|
|
44
|
+
via: t.via,
|
|
45
|
+
send: (mail) => void deliver(mail).catch((err) => console.error("[wizelens-cloud]", err instanceof Error ? err.message : err)),
|
|
46
|
+
close: async () => t.close?.(),
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
//# sourceMappingURL=mailer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mailer.js","sourceRoot":"","sources":["../../src/mail/mailer.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { SAMPLES } from "./samples.js";
|
|
3
|
+
export { SAMPLES };
|
|
4
|
+
/** Write each sample as .html and .txt, plus an index page that links them. */
|
|
5
|
+
export declare function writePreviews(dir: string): Promise<string[]>;
|
|
6
|
+
//# sourceMappingURL=preview.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"preview.d.ts","sourceRoot":"","sources":["../../src/mail/preview.ts"],"names":[],"mappings":";AAIA,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAGvC,OAAO,EAAE,OAAO,EAAE,CAAC;AAEnB,+EAA+E;AAC/E,wBAAsB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAWlE"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { mkdir, writeFile } from "node:fs/promises";
|
|
3
|
+
import { join, resolve } from "node:path";
|
|
4
|
+
import { pathToFileURL } from "node:url";
|
|
5
|
+
import { SAMPLES } from "./samples.js";
|
|
6
|
+
import { renderMail } from "./templates.js";
|
|
7
|
+
export { SAMPLES };
|
|
8
|
+
/** Write each sample as .html and .txt, plus an index page that links them. */
|
|
9
|
+
export async function writePreviews(dir) {
|
|
10
|
+
await mkdir(dir, { recursive: true });
|
|
11
|
+
const names = Object.keys(SAMPLES);
|
|
12
|
+
for (const name of names) {
|
|
13
|
+
const mail = await renderMail("ann@acme.io", SAMPLES[name]);
|
|
14
|
+
await writeFile(join(dir, `${name}.html`), mail.html);
|
|
15
|
+
await writeFile(join(dir, `${name}.txt`), `Subject: ${mail.subject}\n\n${mail.text}`);
|
|
16
|
+
}
|
|
17
|
+
const links = names.map((n) => `<li><a href="${n}.html">${n}</a> · <a href="${n}.txt">text</a></li>`).join("");
|
|
18
|
+
await writeFile(join(dir, "index.html"), `<!doctype html><meta charset="utf-8"><title>WizeLens emails</title><ul>${links}</ul>`);
|
|
19
|
+
return names;
|
|
20
|
+
}
|
|
21
|
+
if (import.meta.url === pathToFileURL(process.argv[1] ?? "").href) {
|
|
22
|
+
const dir = resolve(process.argv[2] ?? "email-preview");
|
|
23
|
+
const names = await writePreviews(dir);
|
|
24
|
+
console.log(`Wrote ${names.length} emails to ${dir}. Open index.html.`);
|
|
25
|
+
}
|
|
26
|
+
//# sourceMappingURL=preview.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"preview.js","sourceRoot":"","sources":["../../src/mail/preview.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AACpD,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AACvC,OAAO,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAE5C,OAAO,EAAE,OAAO,EAAE,CAAC;AAEnB,+EAA+E;AAC/E,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,GAAW;IAC7C,MAAM,KAAK,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACtC,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IACnC,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,MAAM,IAAI,GAAG,MAAM,UAAU,CAAC,aAAa,EAAE,OAAO,CAAC,IAAI,CAAE,CAAC,CAAC;QAC7D,MAAM,SAAS,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,OAAO,CAAC,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC;QACtD,MAAM,SAAS,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,MAAM,CAAC,EAAE,YAAY,IAAI,CAAC,OAAO,OAAO,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC;IACxF,CAAC;IACD,MAAM,KAAK,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,gBAAgB,CAAC,UAAU,CAAC,mBAAmB,CAAC,qBAAqB,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAC/G,MAAM,SAAS,CAAC,IAAI,CAAC,GAAG,EAAE,YAAY,CAAC,EAAE,0EAA0E,KAAK,OAAO,CAAC,CAAC;IACjI,OAAO,KAAK,CAAC;AACf,CAAC;AAED,IAAI,OAAO,IAAI,CAAC,GAAG,KAAK,aAAa,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;IAClE,MAAM,GAAG,GAAG,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,eAAe,CAAC,CAAC;IACxD,MAAM,KAAK,GAAG,MAAM,aAAa,CAAC,GAAG,CAAC,CAAC;IACvC,OAAO,CAAC,GAAG,CAAC,SAAS,KAAK,CAAC,MAAM,cAAc,GAAG,oBAAoB,CAAC,CAAC;AAC1E,CAAC"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { Mail } from "./mailer.js";
|
|
2
|
+
export interface ResendSettings {
|
|
3
|
+
apiKey: string;
|
|
4
|
+
from: string;
|
|
5
|
+
fetch: typeof fetch;
|
|
6
|
+
/** Wait between tries. Tests pass a fast one. */
|
|
7
|
+
sleep?: (ms: number) => Promise<void>;
|
|
8
|
+
}
|
|
9
|
+
/** Send one email through the Resend API. The same Idempotency-Key on every try, so a retry never sends it twice. */
|
|
10
|
+
export declare function resendSender(s: ResendSettings): (mail: Mail) => Promise<void>;
|
|
11
|
+
//# sourceMappingURL=resend.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resend.d.ts","sourceRoot":"","sources":["../../src/mail/resend.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,aAAa,CAAC;AAKxC,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,OAAO,KAAK,CAAC;IACpB,iDAAiD;IACjD,KAAK,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CACvC;AAaD,qHAAqH;AACrH,wBAAgB,YAAY,CAAC,CAAC,EAAE,cAAc,GAAG,CAAC,IAAI,EAAE,IAAI,KAAK,OAAO,CAAC,IAAI,CAAC,CAkB7E"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
const API = "https://api.resend.com/emails";
|
|
3
|
+
const TRIES = 4;
|
|
4
|
+
const pause = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
5
|
+
/** 429 and 5xx are worth another try, except a used-up daily or monthly quota. */
|
|
6
|
+
const retryable = (status, body) => (status === 429 && !/quota_exceeded/.test(body)) || status >= 500;
|
|
7
|
+
/** Seconds from Retry-After, else 1 s, 2 s, 4 s. At most 30 s. */
|
|
8
|
+
const waitMs = (res, attempt) => Math.min(30_000, Number(res?.headers.get("retry-after")) * 1000 || 1000 * 2 ** attempt);
|
|
9
|
+
const payload = (s, m) => JSON.stringify({ from: s.from, to: [m.to], subject: m.subject, html: m.html, text: m.text, tags: [{ name: "kind", value: m.kind }], ...(m.headers ? { headers: m.headers } : {}) });
|
|
10
|
+
/** Send one email through the Resend API. The same Idempotency-Key on every try, so a retry never sends it twice. */
|
|
11
|
+
export function resendSender(s) {
|
|
12
|
+
const sleep = s.sleep ?? pause;
|
|
13
|
+
return async (mail) => {
|
|
14
|
+
const headers = { Authorization: `Bearer ${s.apiKey}`, "Content-Type": "application/json", "Idempotency-Key": randomUUID() };
|
|
15
|
+
let problem = "";
|
|
16
|
+
for (let attempt = 0; attempt < TRIES; attempt++) {
|
|
17
|
+
const res = await s.fetch(API, { method: "POST", headers, body: payload(s, mail), signal: AbortSignal.timeout(15_000) }).catch((err) => {
|
|
18
|
+
problem = err instanceof Error ? err.message : String(err);
|
|
19
|
+
return null;
|
|
20
|
+
});
|
|
21
|
+
if (res?.ok)
|
|
22
|
+
return;
|
|
23
|
+
const body = res ? await res.text().catch(() => "") : "";
|
|
24
|
+
if (res)
|
|
25
|
+
problem = `Resend answered ${res.status}: ${body.slice(0, 300)}`;
|
|
26
|
+
if (res && !retryable(res.status, body))
|
|
27
|
+
break;
|
|
28
|
+
if (attempt < TRIES - 1)
|
|
29
|
+
await sleep(waitMs(res, attempt));
|
|
30
|
+
}
|
|
31
|
+
throw new Error(problem);
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
//# sourceMappingURL=resend.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resend.js","sourceRoot":"","sources":["../../src/mail/resend.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"samples.d.ts","sourceRoot":"","sources":["../../src/mail/samples.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AA8CjD,eAAO,MAAM,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,YAAY,CAqBhD,CAAC"}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import * as emails from "./emails.js";
|
|
2
|
+
import { digest } from "./emails-digest.js";
|
|
3
|
+
import { projectBack, projectQuiet } from "./emails-health.js";
|
|
4
|
+
import * as security from "./emails-security.js";
|
|
5
|
+
import { orgDeleted, roleChanged, teamChanges } from "./emails-team.js";
|
|
6
|
+
// Sample data for every email, for the preview command and the template tests.
|
|
7
|
+
const APP = "https://app.wizelens.com/_wizelens";
|
|
8
|
+
const LINK = `${APP}/#/verify-email?token=sample-token`;
|
|
9
|
+
const STOP = `${APP}/api/mail/unsubscribe?token=sample`;
|
|
10
|
+
const AT = Date.UTC(2026, 9, 18, 14, 30);
|
|
11
|
+
const LINKS = { account: `${APP}/#/account`, forgot: `${APP}/#/forgot-password` };
|
|
12
|
+
const notice = (state) => ({
|
|
13
|
+
rule: "Checkout errors",
|
|
14
|
+
project: "shop",
|
|
15
|
+
state,
|
|
16
|
+
message: state === "fired" ? "Error rate is 7.4% (above 5)" : "Error rate is 1.2% (no longer above 5)",
|
|
17
|
+
value: state === "fired" ? 7.4 : 1.2,
|
|
18
|
+
threshold: 5,
|
|
19
|
+
timeMs: AT,
|
|
20
|
+
url: `${APP}/#/alerts?project=p1`,
|
|
21
|
+
});
|
|
22
|
+
const h = (value, previous) => ({ value, previous, change: ((value - previous) / previous) * 100, series: [] });
|
|
23
|
+
const WEEK = {
|
|
24
|
+
empty: false,
|
|
25
|
+
requests: h(48_210, 41_900),
|
|
26
|
+
errorRate: h(1.8, 1.1),
|
|
27
|
+
avgMs: h(132, 140),
|
|
28
|
+
sqlQueries: h(310_442, 306_100),
|
|
29
|
+
p95Ms: 412,
|
|
30
|
+
errors: [
|
|
31
|
+
{ type: "TimeoutError", message: "payments-api did not answer in 5000 ms", count: 214 },
|
|
32
|
+
{ type: "TypeError", message: "Cannot read properties of null (reading 'total')", count: 37 },
|
|
33
|
+
],
|
|
34
|
+
slowest: [
|
|
35
|
+
{ endpoint: "POST /checkout", p95Ms: 1840, count: 3120 },
|
|
36
|
+
{ endpoint: "GET /orders/:id", p95Ms: 620, count: 9811 },
|
|
37
|
+
],
|
|
38
|
+
insights: [{ title: "N+1 query: SELECT * FROM order_items WHERE order_id = ?", occurrences: 9811 }],
|
|
39
|
+
alertsFired: 1,
|
|
40
|
+
};
|
|
41
|
+
export const SAMPLES = {
|
|
42
|
+
"verify-email": emails.verifyEmail(LINK),
|
|
43
|
+
"reset-password": emails.resetPassword(LINK),
|
|
44
|
+
"change-email": emails.changeEmail("new@acme.io", LINK),
|
|
45
|
+
"delete-account": emails.deleteAccount(LINK),
|
|
46
|
+
invite: emails.invite("Olive", "Acme", "member", LINK),
|
|
47
|
+
"alert-fired": emails.alert(notice("fired")),
|
|
48
|
+
"alert-resolved": emails.alert(notice("resolved")),
|
|
49
|
+
"security-password": security.passwordChanged(LINKS, AT),
|
|
50
|
+
"security-email": security.emailChanged(LINKS, "olive@new.io", AT),
|
|
51
|
+
"security-2fa-off": security.twoFactorChanged(LINKS, false, AT),
|
|
52
|
+
"security-passkey": security.passkeyChanged(LINKS, true, "MacBook Touch ID", AT),
|
|
53
|
+
"security-backup-codes": security.backupCodesMade(LINKS, AT),
|
|
54
|
+
"security-new-sign-in": security.newSignIn(LINKS, "Chrome on Windows", "203.0.113.7", AT),
|
|
55
|
+
"team-changes": teamChanges("Acme", [{ text: "Vic (vic@acme.io) made an ingest key for shop.", timeMs: AT }, { text: "Vic (vic@acme.io) invited ann@acme.io as member.", timeMs: AT + 60_000 }], `${APP}/#/team`, STOP),
|
|
56
|
+
"team-role": roleChanged("Acme", "admin", `${APP}/#/team`, STOP),
|
|
57
|
+
"team-org-deleted": orgDeleted("Acme", "Olive (olive@acme.io)", `${APP}/`),
|
|
58
|
+
"health-quiet": projectQuiet("shop", "Acme", AT, AT + 3 * 3_600_000, `${APP}/#/`, STOP),
|
|
59
|
+
"health-back": projectBack("shop", "Acme", 5 * 3_600_000, `${APP}/#/`, STOP),
|
|
60
|
+
"digest-weekly": digest("shop", "Acme", "weekly", WEEK, { project: `${APP}/#/`, stop: STOP }),
|
|
61
|
+
"digest-daily": digest("shop", "Acme", "daily", { ...WEEK, alertsFired: 0, errorRate: h(0.9, 1.4), errors: [] }, { project: `${APP}/#/`, stop: STOP }),
|
|
62
|
+
};
|
|
63
|
+
//# sourceMappingURL=samples.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"samples.js","sourceRoot":"","sources":["../../src/mail/samples.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { Ctx } from "../app.js";
|
|
2
|
+
/** GET shows a button (mail scanners open links, so a GET changes nothing). POST turns the mail off; it is also the RFC 8058 one-click call. */
|
|
3
|
+
export declare function stopRoute(ctx: Ctx): Promise<void>;
|
|
4
|
+
//# sourceMappingURL=stop-route.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stop-route.d.ts","sourceRoot":"","sources":["../../src/mail/stop-route.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,WAAW,CAAC;AA0BrC,gJAAgJ;AAChJ,wBAAsB,SAAS,CAAC,GAAG,EAAE,GAAG,GAAG,OAAO,CAAC,IAAI,CAAC,CAavD"}
|