@nxgt/janus-mail 0.8.0 → 0.9.1
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 +66 -13
- package/dist/generated/mail.d.ts +8 -0
- package/dist/generated/mail.d.ts.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +77 -23
- package/dist/index.js.map +8 -6
- package/dist/janus-mail.d.ts.map +1 -1
- package/dist/link-types.d.ts +51 -0
- package/dist/link-types.d.ts.map +1 -0
- package/dist/new-sign-in-mail.d.ts +14 -0
- package/dist/new-sign-in-mail.d.ts.map +1 -0
- package/dist/no-location.d.ts +23 -0
- package/dist/no-location.d.ts.map +1 -0
- package/dist/options.d.ts.map +1 -1
- package/dist/template-types.d.ts +17 -2
- package/dist/template-types.d.ts.map +1 -1
- package/dist/templates.d.ts +1 -1
- package/dist/templates.d.ts.map +1 -1
- package/dist/types.d.ts +20 -46
- package/dist/types.d.ts.map +1 -1
- package/docs/README.md +3 -3
- package/docs/guide/building.md +1 -1
- package/docs/guide/locales.md +11 -4
- package/docs/guide/sending.md +52 -1
- package/docs/guide/templates.md +24 -12
- package/docs/roadmap.md +20 -23
- package/docs/troubleshooting.md +45 -9
- package/mails/en/new-sign-in.html +60 -0
- package/mails/en/new-sign-in.txt +25 -0
- package/mails/fr/new-sign-in.html +58 -0
- package/mails/fr/new-sign-in.txt +25 -0
- package/mails/mail-manifest.json +27 -0
- package/package.json +6 -6
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The links of the e-mails, in their own file for its length. Re-exported
|
|
3
|
+
* by `./types`.
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* The links of the e-mails, each an absolute `http(s)` URL of your
|
|
7
|
+
* application. Each is called at send time and must answer a string
|
|
8
|
+
* synchronously: an `async` function or a `URL` object is a compile error,
|
|
9
|
+
* and from JavaScript, or through a cast, the send throws a `TypeError`
|
|
10
|
+
* naming the call. A
|
|
11
|
+
* `mailto:` URL is accepted too — `secureAccount: () => 'mailto:security@acme.example'`
|
|
12
|
+
* sends the user to your support desk; any other scheme is refused by the
|
|
13
|
+
* renderer with a `MailRefused`. The functions are copied when `janusMail()`
|
|
14
|
+
* is called: replacing one afterwards changes nothing.
|
|
15
|
+
*/
|
|
16
|
+
export interface JanusMailLinks {
|
|
17
|
+
/** The page that confirms an e-mail, given the one-time token: `(token) => \`https://app.example/verify?token=${token}\`` */
|
|
18
|
+
readonly verifyEmail: (token: string) => string;
|
|
19
|
+
/** The page that sets a new password, given the one-time token. */
|
|
20
|
+
readonly resetPassword: (token: string) => string;
|
|
21
|
+
/**
|
|
22
|
+
* Where a user who did not make a change secures their account — their
|
|
23
|
+
* security settings. `passwordChanged`, `emailChanged`, `twoFactorEnabled`,
|
|
24
|
+
* `twoFactorDisabled` and `newSignIn` link to it, and so does `stepUp`, for
|
|
25
|
+
* a user who asked for no code.
|
|
26
|
+
*/
|
|
27
|
+
readonly secureAccount: () => string;
|
|
28
|
+
/**
|
|
29
|
+
* Where a new user starts — your application's home, or its sign-in page
|
|
30
|
+
* for an account someone else created. `welcome` links to it, from its
|
|
31
|
+
* "Get started" button.
|
|
32
|
+
*/
|
|
33
|
+
readonly getStarted: () => string;
|
|
34
|
+
/**
|
|
35
|
+
* Where a user regenerates their recovery codes — `recoveryCodeUsed`
|
|
36
|
+
* links to it, from its "Secure my account" button. **Optional**: without
|
|
37
|
+
* it, that e-mail links to `secureAccount`, the security settings where
|
|
38
|
+
* the codes usually are.
|
|
39
|
+
*/
|
|
40
|
+
readonly recoveryCodes?: () => string;
|
|
41
|
+
/**
|
|
42
|
+
* The page a sign-in link opens, given its one-time token —
|
|
43
|
+
* `magicLink` links to it. **Optional**, for an application that sends no
|
|
44
|
+
* sign-in link; without it, `magicLink` throws a `TypeError` before
|
|
45
|
+
* anything is rendered. That page spends nothing: its button posts the
|
|
46
|
+
* token to the route that calls `auth.magicLink.confirm`, so a mail
|
|
47
|
+
* scanner that opens the link does not sign in for the user.
|
|
48
|
+
*/
|
|
49
|
+
readonly magicLink?: (token: string) => string;
|
|
50
|
+
}
|
|
51
|
+
//# sourceMappingURL=link-types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"link-types.d.ts","sourceRoot":"","sources":["../src/link-types.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH;;;;;;;;;;GAUG;AACH,MAAM,WAAW,cAAc;IAC9B,6HAA6H;IAC7H,QAAQ,CAAC,WAAW,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAC;IAChD,mEAAmE;IACnE,QAAQ,CAAC,aAAa,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAC;IAClD;;;;;OAKG;IACH,QAAQ,CAAC,aAAa,EAAE,MAAM,MAAM,CAAC;IACrC;;;;OAIG;IACH,QAAQ,CAAC,UAAU,EAAE,MAAM,MAAM,CAAC;IAClC;;;;;OAKG;IACH,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,MAAM,CAAC;IACtC;;;;;;;OAOG;IACH,QAAQ,CAAC,SAAS,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAC;CAC/C"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `newSignIn`: the notice of a sign-in from a device the user had not signed
|
|
3
|
+
* in from — which device, when, and where when the sender knows — linking to
|
|
4
|
+
* where the account is secured.
|
|
5
|
+
*
|
|
6
|
+
* Apart from the other notices because of its optional `location`: Janus
|
|
7
|
+
* sees no IP, so the sender may know none, and the e-mail then says so in
|
|
8
|
+
* the send's locale — "Location Unknown", "Lieu Inconnu" (`./no-location`).
|
|
9
|
+
*/
|
|
10
|
+
import type { ResolvedOptions } from './options';
|
|
11
|
+
import type { JanusMail } from './types';
|
|
12
|
+
/** The new sign-in notice, over the resolved options. */
|
|
13
|
+
export declare function newSignInMail(options: ResolvedOptions): Pick<JanusMail<string>, 'newSignIn'>;
|
|
14
|
+
//# sourceMappingURL=new-sign-in-mail.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"new-sign-in-mail.d.ts","sourceRoot":"","sources":["../src/new-sign-in-mail.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAGH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC;AAEjD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAiBzC,yDAAyD;AACzD,wBAAgB,aAAa,CAC5B,OAAO,EAAE,eAAe,GACtB,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,EAAE,WAAW,CAAC,CAiBtC"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What `newSignIn` shows where the sender knew no location — "Unknown",
|
|
3
|
+
* "Inconnu" — in the recipient's locale: the value after the row's label,
|
|
4
|
+
* so "Location Unknown", "Lieu Inconnu".
|
|
5
|
+
*
|
|
6
|
+
* A map written here, not a message in `mail/locales/*.json`: those
|
|
7
|
+
* catalogues are read by the Maizzle build alone, and hold only the
|
|
8
|
+
* presets' own keys, which the build checks against `@nxgt/mail-presets`'
|
|
9
|
+
* `en`. This string is a send's — whether a location was given is known
|
|
10
|
+
* only then — so it is chosen at send time, as the codes left are
|
|
11
|
+
* (`./codes-left`). Keyed by `JanusMailLocale`, so a locale added to the
|
|
12
|
+
* build without its text here is a compile error.
|
|
13
|
+
*/
|
|
14
|
+
import type { JanusMailLocale } from './generated/locales';
|
|
15
|
+
/** The text of a missing location, by built locale. */
|
|
16
|
+
export declare const NO_LOCATION: Readonly<Record<JanusMailLocale, string>>;
|
|
17
|
+
/**
|
|
18
|
+
* The text of a missing location in `locale` — the one `localeFor` picked —
|
|
19
|
+
* else in its language (`fr` for `fr-CA`), else in `en`: a locale beyond
|
|
20
|
+
* the built ones is sent only through templates of the consumer's own.
|
|
21
|
+
*/
|
|
22
|
+
export declare function noLocationText(locale: string): string;
|
|
23
|
+
//# sourceMappingURL=no-location.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"no-location.d.ts","sourceRoot":"","sources":["../src/no-location.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAE3D,uDAAuD;AACvD,eAAO,MAAM,WAAW,EAAE,QAAQ,CAAC,MAAM,CAAC,eAAe,EAAE,MAAM,CAAC,CAI/D,CAAC;AAMJ;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAKrD"}
|
package/dist/options.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"options.d.ts","sourceRoot":"","sources":["../src/options.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,YAAY,CAAC;AAKlD,OAAO,KAAK,EACX,cAAc,EACd,qBAAqB,EACrB,kBAAkB,EAClB,MAAM,SAAS,CAAC;AAEjB,+DAA+D;AAC/D,eAAO,MAAM,cAAc,EAAE,SAAS,qBAAqB,
|
|
1
|
+
{"version":3,"file":"options.d.ts","sourceRoot":"","sources":["../src/options.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,YAAY,CAAC;AAKlD,OAAO,KAAK,EACX,cAAc,EACd,qBAAqB,EACrB,kBAAkB,EAClB,MAAM,SAAS,CAAC;AAEjB,+DAA+D;AAC/D,eAAO,MAAM,cAAc,EAAE,SAAS,qBAAqB,EAa1D,CAAC;AAEF,yFAAyF;AACzF,MAAM,WAAW,eAAe;IAC/B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IACvB,QAAQ,CAAC,OAAO,EAAE,OAAO,GAAG,SAAS,CAAC;IACtC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,KAAK,EAAE,cAAc,CAAC;IAC/B,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;IACpC,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,SAAS,EAAE,kBAAkB,CAAC,MAAM,CAAC,CAAC;IAC/C,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAC;CACtB;AAiJD;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,OAAO,GAAG,eAAe,CAoBhE"}
|
package/dist/template-types.d.ts
CHANGED
|
@@ -84,6 +84,21 @@ export interface JanusMailVariables {
|
|
|
84
84
|
readonly recoveryCodesLeft: string;
|
|
85
85
|
readonly link: string;
|
|
86
86
|
};
|
|
87
|
+
/**
|
|
88
|
+
* `device`, `time` and `location` are the sender's text, already in the
|
|
89
|
+
* recipient's locale. Without a `location` from the sender, it is
|
|
90
|
+
* "Unknown" in the send's locale — "Inconnu" in `fr` — the
|
|
91
|
+
* language's text for a regional `en` or `fr` tag, `en`'s for any other.
|
|
92
|
+
* `link` is where the account is secured.
|
|
93
|
+
*/
|
|
94
|
+
readonly newSignIn: {
|
|
95
|
+
readonly brand: string;
|
|
96
|
+
readonly name: string;
|
|
97
|
+
readonly device: string;
|
|
98
|
+
readonly location: string;
|
|
99
|
+
readonly time: string;
|
|
100
|
+
readonly link: string;
|
|
101
|
+
};
|
|
87
102
|
/** `name` is in the subject too: "Welcome, Ada". */
|
|
88
103
|
readonly welcome: {
|
|
89
104
|
readonly brand: string;
|
|
@@ -91,7 +106,7 @@ export interface JanusMailVariables {
|
|
|
91
106
|
readonly link: string;
|
|
92
107
|
};
|
|
93
108
|
}
|
|
94
|
-
/** The name of one of the
|
|
109
|
+
/** The name of one of the twelve templates: `verifyEmail`, `resetPassword`, … */
|
|
95
110
|
export type JanusMailTemplateName = keyof JanusMailVariables;
|
|
96
111
|
/**
|
|
97
112
|
* One e-mail: its variables and the locale picked for the recipient, in; its
|
|
@@ -102,7 +117,7 @@ export type JanusMailTemplateName = keyof JanusMailVariables;
|
|
|
102
117
|
export type JanusMailTemplate<V, L extends string = JanusMailLocale> = (variables: V & {
|
|
103
118
|
readonly locale: L;
|
|
104
119
|
}) => Rendered | PromiseLike<Rendered>;
|
|
105
|
-
/** The
|
|
120
|
+
/** The twelve templates, each for the locales `L`. */
|
|
106
121
|
export type JanusMailTemplates<L extends string = JanusMailLocale> = {
|
|
107
122
|
readonly [K in JanusMailTemplateName]: JanusMailTemplate<JanusMailVariables[K], L>;
|
|
108
123
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"template-types.d.ts","sourceRoot":"","sources":["../src/template-types.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"template-types.d.ts","sourceRoot":"","sources":["../src/template-types.ts"],"names":[],"mappings":"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"}
|
package/dist/templates.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { JanusMailTemplates } from './types';
|
|
2
2
|
/**
|
|
3
|
-
* The
|
|
3
|
+
* The twelve default templates, in the locales they are built in. The
|
|
4
4
|
* renderer behind them is created on the first render, not here.
|
|
5
5
|
*
|
|
6
6
|
* ```ts
|
package/dist/templates.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"templates.d.ts","sourceRoot":"","sources":["../src/templates.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"templates.d.ts","sourceRoot":"","sources":["../src/templates.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,SAAS,CAAC;AAgClD;;;;;;;;;;;GAWG;AACH,wBAAgB,cAAc,IAAI,kBAAkB,CA6DnD"}
|
package/dist/types.d.ts
CHANGED
|
@@ -6,8 +6,10 @@
|
|
|
6
6
|
import type { Clock, IssuedCode, IssuedToken, StepUpByEmail } from '@nxgt/janus';
|
|
7
7
|
import type { Address, Mailer, SentMail, WantedLocales } from '@nxgt/mail';
|
|
8
8
|
import type { JanusMailLocale } from './generated/locales';
|
|
9
|
+
import type { JanusMailLinks } from './link-types';
|
|
9
10
|
import type { JanusMailTemplates } from './template-types';
|
|
10
11
|
export type { JanusMailLocale } from './generated/locales';
|
|
12
|
+
export type { JanusMailLinks } from './link-types';
|
|
11
13
|
export type { JanusMailTemplate, JanusMailTemplateName, JanusMailTemplates, JanusMailVariables, } from './template-types';
|
|
12
14
|
/**
|
|
13
15
|
* Who an e-mail is for, besides the address: the name it greets, and the
|
|
@@ -19,52 +21,6 @@ export interface Recipient {
|
|
|
19
21
|
readonly name: string;
|
|
20
22
|
readonly locale?: WantedLocales;
|
|
21
23
|
}
|
|
22
|
-
/**
|
|
23
|
-
* The links of the e-mails, each an absolute `http(s)` URL of your
|
|
24
|
-
* application. Each is called at send time and must answer a string
|
|
25
|
-
* synchronously: an `async` function or a `URL` object is a compile error,
|
|
26
|
-
* and from JavaScript, or through a cast, the send throws a `TypeError`
|
|
27
|
-
* naming the call. A
|
|
28
|
-
* `mailto:` URL is accepted too — `secureAccount: () => 'mailto:security@acme.example'`
|
|
29
|
-
* sends the user to your support desk; any other scheme is refused by the
|
|
30
|
-
* renderer with a `MailRefused`. The functions are copied when `janusMail()`
|
|
31
|
-
* is called: replacing one afterwards changes nothing.
|
|
32
|
-
*/
|
|
33
|
-
export interface JanusMailLinks {
|
|
34
|
-
/** The page that confirms an e-mail, given the one-time token: `(token) => \`https://app.example/verify?token=${token}\`` */
|
|
35
|
-
readonly verifyEmail: (token: string) => string;
|
|
36
|
-
/** The page that sets a new password, given the one-time token. */
|
|
37
|
-
readonly resetPassword: (token: string) => string;
|
|
38
|
-
/**
|
|
39
|
-
* Where a user who did not make a change secures their account — their
|
|
40
|
-
* security settings. `passwordChanged`, `emailChanged`, `twoFactorEnabled`
|
|
41
|
-
* and `twoFactorDisabled` link to it, and so does `stepUp`, for a user who
|
|
42
|
-
* asked for no code.
|
|
43
|
-
*/
|
|
44
|
-
readonly secureAccount: () => string;
|
|
45
|
-
/**
|
|
46
|
-
* Where a new user starts — your application's home, or its sign-in page
|
|
47
|
-
* for an account someone else created. `welcome` links to it, from its
|
|
48
|
-
* "Get started" button.
|
|
49
|
-
*/
|
|
50
|
-
readonly getStarted: () => string;
|
|
51
|
-
/**
|
|
52
|
-
* Where a user regenerates their recovery codes — `recoveryCodeUsed`
|
|
53
|
-
* links to it, from its "Secure my account" button. **Optional**: without
|
|
54
|
-
* it, that e-mail links to `secureAccount`, the security settings where
|
|
55
|
-
* the codes usually are.
|
|
56
|
-
*/
|
|
57
|
-
readonly recoveryCodes?: () => string;
|
|
58
|
-
/**
|
|
59
|
-
* The page a sign-in link opens, given its one-time token —
|
|
60
|
-
* `magicLink` links to it. **Optional**, for an application that sends no
|
|
61
|
-
* sign-in link; without it, `magicLink` throws a `TypeError` before
|
|
62
|
-
* anything is rendered. That page spends nothing: its button posts the
|
|
63
|
-
* token to the route that calls `auth.magicLink.confirm`, so a mail
|
|
64
|
-
* scanner that opens the link does not sign in for the user.
|
|
65
|
-
*/
|
|
66
|
-
readonly magicLink?: (token: string) => string;
|
|
67
|
-
}
|
|
68
24
|
/** The options every locale set shares. */
|
|
69
25
|
interface JanusMailBaseOptions<L extends string> {
|
|
70
26
|
/** The transport — `@nxgt/mail-smtp`, `@nxgt/mail-resend`, or `createMemoryMailer()` in tests. */
|
|
@@ -200,6 +156,24 @@ export interface JanusMail<L extends string = JanusMailLocale> {
|
|
|
200
156
|
readonly when: string;
|
|
201
157
|
readonly recoveryCodesLeft: number | string;
|
|
202
158
|
}): Promise<SentMail>;
|
|
159
|
+
/**
|
|
160
|
+
* Tells `to.email` their account was signed in from a device they had
|
|
161
|
+
* not signed in from, `time`, on `device` — and `location`, when you know
|
|
162
|
+
* it — with a link to secure the account: once a sign-in answered
|
|
163
|
+
* `newDevice: true`, or on the `user.newDeviceSignedIn` event.
|
|
164
|
+
*
|
|
165
|
+
* Every value is your text, in the recipient's locale: `device` described
|
|
166
|
+
* from the request (`'Firefox on macOS'`), `time` formatted in their time
|
|
167
|
+
* zone. **Janus sees no IP**: `location` is yours to work out, and an
|
|
168
|
+
* e-mail without one says "Unknown" in its place — "Inconnu" in French.
|
|
169
|
+
*/
|
|
170
|
+
newSignIn(to: Recipient & {
|
|
171
|
+
readonly email: string;
|
|
172
|
+
}, signIn: {
|
|
173
|
+
readonly device: string;
|
|
174
|
+
readonly time: string;
|
|
175
|
+
readonly location?: string;
|
|
176
|
+
}): Promise<SentMail>;
|
|
203
177
|
/** The templates in use: the defaults, with `templates` over them. */
|
|
204
178
|
readonly templates: JanusMailTemplates<L>;
|
|
205
179
|
/** The locales sent in. */
|
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,KAAK,EACX,KAAK,EACL,UAAU,EACV,WAAW,EACX,aAAa,EACb,MAAM,aAAa,CAAC;AACrB,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAC3E,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAC3D,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AAE3D,YAAY,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAC3D,YAAY,EACX,iBAAiB,EACjB,qBAAqB,EACrB,kBAAkB,EAClB,kBAAkB,GAClB,MAAM,kBAAkB,CAAC;AAE1B;;;;;GAKG;AACH,MAAM,WAAW,SAAS;IACzB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,MAAM,CAAC,EAAE,aAAa,CAAC;CAChC;AAED
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
|
package/docs/README.md
CHANGED
|
@@ -5,7 +5,7 @@ the detail.
|
|
|
5
5
|
|
|
6
6
|
| Page | Read it when |
|
|
7
7
|
| --- | --- |
|
|
8
|
-
| [Sending](guide/sending.md) | You are wiring `janusMail()` into your routes: the options, `clock` among them; each method, the step-up's code, the welcome on `user.created`, the recovery code notice on `user.recoveryCodeUsed
|
|
8
|
+
| [Sending](guide/sending.md) | You are wiring `janusMail()` into your routes: the options, `clock` among them; each method, the step-up's code, the welcome on `user.created`, the recovery code notice on `user.recoveryCodeUsed`, the change notices on `user.passwordChanged` and `user.emailChanged` and the new sign-in notice among them; the expiry an e-mail states and how to set it, where each e-mail goes, what a failure looks like, retrying and tracing the mailer, and a test |
|
|
9
9
|
| [Templates](guide/templates.md) | You want an e-mail of your own: what a template takes and answers, a partial override, `janusTemplates()`, and what the defaults say |
|
|
10
10
|
| [Locales](guide/locales.md) | You send in more than one language: how the locale is picked, `fallbackLocale`, fewer locales, and adding one |
|
|
11
11
|
| [Building](guide/building.md) | You work on this package: the Maizzle project in `mail/`, `mails/`, the generated files, and why none of it reaches a consumer's build |
|
|
@@ -23,13 +23,13 @@ They mean the same here. These pages add:
|
|
|
23
23
|
|
|
24
24
|
| Word | Means | Not |
|
|
25
25
|
| --- | --- | --- |
|
|
26
|
-
| **e-mail** | One of the
|
|
26
|
+
| **e-mail** | One of the twelve this package sends: `verify-email`, `reset-password`, `sign-in-code`, `magic-link`, `confirm-action`, `password-changed`, `email-changed`, `two-factor-enabled`, `two-factor-disabled`, `recovery-code-used`, `new-sign-in`, `welcome` — by the name of its built file | "mail", "message" — a *message* is what a mailer sends, addressed |
|
|
27
27
|
| **template** | The function that renders one e-mail: `(variables & { locale }) => Rendered`. Named in camelCase, after the method that sends it: `verifyEmail`, `signInCode` | "renderer" — the renderer is `@nxgt/mail`'s, and reads the build |
|
|
28
28
|
| **default template** | A template of `janusTemplates()`: the prebuilt e-mail, filled by the renderer | "preset" — a preset is `@nxgt/mail-presets`' source, which the build compiles |
|
|
29
29
|
| **override** | A template of yours, passed in `templates`, used instead of the default | "custom template" |
|
|
30
30
|
| **issued** | What a flow answered: an `IssuedToken` from `verifyEmail.send`, `resetPassword.request` or `magicLink.request`, an `IssuedCode` from `signInCode.request`, a `StepUpByEmail` from `stepUp.request` once narrowed to `via: 'email'` | "result", "payload" |
|
|
31
31
|
| **recipient** | Who an e-mail is for besides the address: a `Recipient`, `{ name, locale? }` | "user" — a user is a record of `@nxgt/janus`; "to" is the address |
|
|
32
|
-
| **notice** | An e-mail that tells, and asks for nothing: `passwordChanged`, `emailChanged`, `twoFactorEnabled`, `twoFactorDisabled`, `recoveryCodeUsed`. It links to `links.secureAccount()` — `recoveryCodeUsed` to `links.recoveryCodes()` when given | "alert", "notification" |
|
|
32
|
+
| **notice** | An e-mail that tells, and asks for nothing: `passwordChanged`, `emailChanged`, `twoFactorEnabled`, `twoFactorDisabled`, `recoveryCodeUsed`, `newSignIn`. It links to `links.secureAccount()` — `recoveryCodeUsed` to `links.recoveryCodes()` when given | "alert", "notification" |
|
|
33
33
|
| **welcome** | The e-mail sent once a user was created, on `@nxgt/janus`'s `user.created`: `welcome`. It links to `links.getStarted()` — not a notice, since it warns of nothing | "onboarding", "greeting" |
|
|
34
34
|
| **codes left** | How many recovery codes the user still holds, as the recovery code notice says it: `recoveryCodesLeft`, a sentence written from the count with the preset's plural in the recipient's locale — "You have 1 recovery code left." — or passed as text | "remaining codes" — the count itself is `@nxgt/janus`'s `recoveryCodesLeft` |
|
|
35
35
|
| **expiry** | How long a link or a code lasts, as the e-mail says it: `expiresIn`, derived from the flow's `expiresAt` in the recipient's locale — "1 hour", "1 heure" — or passed per send | "TTL", "lifetime" — the `tokens` TTL is `@nxgt/janus`'s setting, which sets `expiresAt` |
|
package/docs/guide/building.md
CHANGED
|
@@ -55,7 +55,7 @@ packages/janus-mail/
|
|
|
55
55
|
`src/generated/mail.ts` (`rendererTypes`), from the build.
|
|
56
56
|
3. `scripts/build-mail.ts` reads the manifest and **fails unless its
|
|
57
57
|
format is one every `@nxgt/mail` the peer admits reads** — see below —
|
|
58
|
-
and **unless exactly the
|
|
58
|
+
and **unless exactly the twelve e-mails were built**: a thirteenth from a new
|
|
59
59
|
`@nxgt/mail-presets`, or one missing, stops the build.
|
|
60
60
|
4. It writes `src/generated/locales.ts` from the manifest's locales, and
|
|
61
61
|
`src/generated/codes-left.ts` — below — each only when its content
|
package/docs/guide/locales.md
CHANGED
|
@@ -89,6 +89,8 @@ const templates: JanusMailTemplates<Locale> = {
|
|
|
89
89
|
twoFactorDisabled: ({ name, link, locale }) => render('two-factor-disabled', locale, { name, link }),
|
|
90
90
|
recoveryCodeUsed: ({ name, when, recoveryCodesLeft, link, locale }) =>
|
|
91
91
|
render('recovery-code-used', locale, { name, when, recoveryCodesLeft, link }),
|
|
92
|
+
newSignIn: ({ name, device, location, time, link, locale }) =>
|
|
93
|
+
render('new-sign-in', locale, { name, device, location, time, link }),
|
|
92
94
|
welcome: ({ name, link, locale }) => render('welcome', locale, { name, link }),
|
|
93
95
|
};
|
|
94
96
|
|
|
@@ -102,8 +104,13 @@ mail.locales; // readonly ('en' | 'fr' | 'de')[]
|
|
|
102
104
|
plural of the codes left in `en` and `fr` only, so a German recipient's
|
|
103
105
|
`mail.recoveryCodeUsed` takes the sentence as text — "Sie haben noch 3
|
|
104
106
|
Wiederherstellungscodes." — and a count is a `TypeError` there.
|
|
107
|
+
`location`, left out of a `newSignIn`, is "Unknown" for `de`:
|
|
108
|
+
the text of a missing location is held in `en` and `fr` only, so pass
|
|
109
|
+
`location` yourself — "Unbekannt", so the row reads `Ort Unbekannt` —
|
|
110
|
+
where it matters.
|
|
105
111
|
`recoveryCodeUsed` is the ninth template, since 0.5.0, `magicLink` the
|
|
106
|
-
tenth, since 0.7.0,
|
|
112
|
+
tenth, since 0.7.0, `stepUp` the eleventh, since 0.8.0, and `newSignIn` the
|
|
113
|
+
twelfth, since 0.9.0: a `templates`
|
|
107
114
|
written for an earlier version no longer
|
|
108
115
|
compiles with a locale beyond `en` and `fr` — add the one it lacks. A
|
|
109
116
|
renderer over a Maizzle build of your own, with
|
|
@@ -112,13 +119,13 @@ With only some templates, the call does not compile — the error lands on
|
|
|
112
119
|
`templates`:
|
|
113
120
|
|
|
114
121
|
```text
|
|
115
|
-
TS2740: Type '{ verifyEmail: () => Rendered; }' is missing the following properties from type 'JanusMailTemplates<"en" | "fr" | "de">': welcome, resetPassword, signInCode, magicLink, and
|
|
122
|
+
TS2740: Type '{ verifyEmail: () => Rendered; }' is missing the following properties from type 'JanusMailTemplates<"en" | "fr" | "de">': welcome, resetPassword, signInCode, magicLink, and 7 more.
|
|
116
123
|
```
|
|
117
124
|
|
|
118
125
|
and in JavaScript it throws when `janusMail()` is called:
|
|
119
126
|
|
|
120
127
|
```text
|
|
121
|
-
TypeError: janusMail: the default templates are built in en and fr only — with another locale in locales, pass every template in templates; resetPassword, signInCode, magicLink, stepUp, passwordChanged, emailChanged, twoFactorEnabled, twoFactorDisabled, recoveryCodeUsed, welcome missing
|
|
128
|
+
TypeError: janusMail: the default templates are built in en and fr only — with another locale in locales, pass every template in templates; resetPassword, signInCode, magicLink, stepUp, passwordChanged, emailChanged, twoFactorEnabled, twoFactorDisabled, recoveryCodeUsed, newSignIn, welcome missing
|
|
122
129
|
```
|
|
123
130
|
|
|
124
131
|
A default template is typed for `'en' | 'fr'`, so reusing one in a wider set
|
|
@@ -131,4 +138,4 @@ Built-in locales for more languages are on the [roadmap](../roadmap.md).
|
|
|
131
138
|
|
|
132
139
|
- `@nxgt/mail`'s [locales guide](https://github.com/softistx/nxgt-mail/blob/develop/packages/mail/docs/guide/locales.md)
|
|
133
140
|
— `pickLocale` and `parseAcceptLanguage` in full.
|
|
134
|
-
- [Templates](templates.md) — writing the
|
|
141
|
+
- [Templates](templates.md) — writing the twelve for a new locale.
|
package/docs/guide/sending.md
CHANGED
|
@@ -46,7 +46,7 @@ time one is sent, and kept.
|
|
|
46
46
|
| `brand` | `string` | required | The name the e-mails show, in the header, the body and the footer. Text: escaped in the HTML |
|
|
47
47
|
| `links.verifyEmail` | `(token) => string` | required | The page that confirms an address, given the one-time token |
|
|
48
48
|
| `links.resetPassword` | `(token) => string` | required | The page that sets a new password, given the one-time token |
|
|
49
|
-
| `links.secureAccount` | `() => string` | required | Where a user who made no change secures their account: the notices link to it
|
|
49
|
+
| `links.secureAccount` | `() => string` | required | Where a user who made no change secures their account: the notices link to it — the new sign-in's included — and so does the step-up's code, for a user who asked for none |
|
|
50
50
|
| `links.getStarted` | `() => string` | required | Where a new user starts — your home page, or your sign-in page for an account someone else created: the welcome's **Get started** button links to it |
|
|
51
51
|
| `links.magicLink` | `(token) => string` | none: `magicLink` is a `TypeError` without it | The page a sign-in link opens, given the one-time token. That page spends nothing: its button posts the token to the route that calls `auth.magicLink.confirm` |
|
|
52
52
|
| `links.recoveryCodes` | `() => string` | `links.secureAccount` | Where a user regenerates their recovery codes: the recovery code notice links to it, from its **Secure my account** button |
|
|
@@ -349,6 +349,56 @@ when opening the session then fails.
|
|
|
349
349
|
codes; without it, `links.secureAccount()`. `recoveryCodes` is optional,
|
|
350
350
|
so a `links` written for 0.4 still compiles.
|
|
351
351
|
|
|
352
|
+
### `newSignIn(to, { device, time, location? })`
|
|
353
|
+
|
|
354
|
+
```ts
|
|
355
|
+
// From the sign-in's answer, with @nxgt/janus given devices: { keys } and the device cookie:
|
|
356
|
+
const signedIn = await auth.signIn({ email, password }, { device: cookie ?? null });
|
|
357
|
+
if (signedIn.newDevice) {
|
|
358
|
+
const { name, email, locale } = signedIn.user;
|
|
359
|
+
await mail.newSignIn(
|
|
360
|
+
{ name, email, locale },
|
|
361
|
+
{
|
|
362
|
+
device: describeDevice(request.headers.get('user-agent')), // yours: 'Firefox on macOS'
|
|
363
|
+
time: new Intl.DateTimeFormat(locale ?? 'en', {
|
|
364
|
+
dateStyle: 'long',
|
|
365
|
+
timeStyle: 'short',
|
|
366
|
+
timeZone: 'Europe/Paris', // the user's, when you keep it
|
|
367
|
+
}).format(signedIn.session.createdAt),
|
|
368
|
+
location: 'Lyon, France', // optional: from a geo-IP of yours
|
|
369
|
+
},
|
|
370
|
+
);
|
|
371
|
+
}
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
A notice for the account at `to.email`: it was signed in from a device the
|
|
375
|
+
user had not signed in from — which device, where and when — with a
|
|
376
|
+
**Secure my account** button to `links.secureAccount()`. Send it when a
|
|
377
|
+
`@nxgt/janus` 0.17 sign-in answers `newDevice: true`, or on the
|
|
378
|
+
`user.newDeviceSignedIn` event it sends then — the event names the user by
|
|
379
|
+
id alone, so read the name, the locale and the address from the user, and
|
|
380
|
+
it carries no request to describe the device from.
|
|
381
|
+
|
|
382
|
+
- **Every value is your text**, already in the recipient's locale: `device`
|
|
383
|
+
as you describe the request — "Firefox on macOS", "Firefox sur macOS" —
|
|
384
|
+
and `time` formatted in their time zone: "29 septembre 2026 à 09:12".
|
|
385
|
+
The session's `createdAt`, or the event's `occurredAt`, is when it
|
|
386
|
+
happened.
|
|
387
|
+
- **`location` is optional, and yours.** `@nxgt/janus` sees no IP: a
|
|
388
|
+
location comes from a geo-IP of your own, if you have one. Left out, the
|
|
389
|
+
e-mail says so in the locale it is sent in — `Location Unknown`, `Lieu
|
|
390
|
+
Inconnu` — since every line of the summary is always there. A locale
|
|
391
|
+
beyond `en` and `fr`, sent through templates of your own, gets its
|
|
392
|
+
language's text when it is `en` or `fr` (`fr-CA` gets "Inconnu"),
|
|
393
|
+
and "Unknown" otherwise: pass `location` yourself there. `null` is a
|
|
394
|
+
compile error, and in JavaScript `janusMail.newSignIn: location must be a
|
|
395
|
+
string`: spread it in only when you have one,
|
|
396
|
+
`...(city === null ? {} : { location: city })`.
|
|
397
|
+
- **The text part** gives each line of the summary on one line, its label
|
|
398
|
+
then its value — `Device Firefox on macOS`, `Location Unknown`,
|
|
399
|
+
`Time September 29, 2026 at 9:12 AM` — one row per line, with no blank line
|
|
400
|
+
between rows.
|
|
401
|
+
|
|
352
402
|
## The expiry
|
|
353
403
|
|
|
354
404
|
`verifyEmail`, `resetPassword`, `signInCode`, `magicLink` and `stepUp` say
|
|
@@ -406,6 +456,7 @@ a `TypeError`, and nothing reaches the mailer: revive the date with
|
|
|
406
456
|
| `emailChanged` | `to.formerEmail` | `to.newEmail` |
|
|
407
457
|
| `twoFactorEnabled`, `twoFactorDisabled` | `to.email` | — |
|
|
408
458
|
| `recoveryCodeUsed` | `to.email` | — |
|
|
459
|
+
| `newSignIn` | `to.email` | — |
|
|
409
460
|
| `welcome` | `to.email` | — |
|
|
410
461
|
|
|
411
462
|
`issued.email` is the address the user's record holds, as they registered
|
package/docs/guide/templates.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# Templates
|
|
2
2
|
|
|
3
3
|
This page is for changing what an e-mail says or looks like: what a template
|
|
4
|
-
is given and answers, replacing some of the
|
|
4
|
+
is given and answers, replacing some of the twelve, using the defaults on their
|
|
5
5
|
own, and what the defaults say.
|
|
6
6
|
|
|
7
7
|
```ts
|
|
@@ -22,7 +22,7 @@ const mail = janusMail({
|
|
|
22
22
|
});
|
|
23
23
|
```
|
|
24
24
|
|
|
25
|
-
The sign-in code e-mail is now yours; the
|
|
25
|
+
The sign-in code e-mail is now yours; the eleven others stay the defaults.
|
|
26
26
|
|
|
27
27
|
## What a template is
|
|
28
28
|
|
|
@@ -69,14 +69,18 @@ e-mail.
|
|
|
69
69
|
| `twoFactorEnabled` | `brand`, `name`, `link` | `mail.twoFactorEnabled(to)` |
|
|
70
70
|
| `twoFactorDisabled` | `brand`, `name`, `link` | `mail.twoFactorDisabled(to)` |
|
|
71
71
|
| `recoveryCodeUsed` | `brand`, `name`, `when`, `recoveryCodesLeft`, `link` | `mail.recoveryCodeUsed(to, { when, recoveryCodesLeft })` |
|
|
72
|
+
| `newSignIn` | `brand`, `name`, `device`, `location`, `time`, `link` | `mail.newSignIn(to, { device, time, location? })` |
|
|
72
73
|
| `welcome` | `brand`, `name`, `link` | `mail.welcome(to)` |
|
|
73
74
|
|
|
74
75
|
Every one also gets `locale`, one of `locales`. Every value is a string:
|
|
75
76
|
`brand` from the options, `name` from the recipient, `link` from `links` —
|
|
76
|
-
`links.secureAccount()` for the
|
|
77
|
+
`links.secureAccount()` for the notices other than `recoveryCodeUsed` and
|
|
78
|
+
for `stepUp`, `links.recoveryCodes()` (else
|
|
77
79
|
`links.secureAccount()`) for `recoveryCodeUsed`, `links.getStarted()` for
|
|
78
|
-
`welcome` — `code`, `newEmail` and `
|
|
79
|
-
call,
|
|
80
|
+
`welcome` — `code`, `newEmail`, `when`, `device` and `time` from the
|
|
81
|
+
call, `location` from the call or, without one, "Unknown" in the
|
|
82
|
+
send's locale — "Inconnu" in `fr`; for another locale, its
|
|
83
|
+
language's text when it is `en` or `fr` (`fr-CA`), else `en`'s — and `expiresIn` derived from the flow's `expiresAt` in the locale, or
|
|
80
84
|
the send's own `expiresIn` ([Sending](sending.md#the-expiry)).
|
|
81
85
|
|
|
82
86
|
`recoveryCodesLeft` is a **sentence**, not the count: "You have 9 recovery
|
|
@@ -126,11 +130,12 @@ methods live on its prototype and are refused — see
|
|
|
126
130
|
[troubleshooting](../troubleshooting.md#janusmail-templatesname-is-not-an-own-enumerable-property--pass-a-plain-object-as--name-variables--rendered-).
|
|
127
131
|
|
|
128
132
|
`templates` is `Partial` while `locales` stays within `en` and `fr`: give
|
|
129
|
-
any of the
|
|
130
|
-
another locale, it takes **all
|
|
133
|
+
any of the twelve, and the defaults render the rest. Once `locales` holds
|
|
134
|
+
another locale, it takes **all twelve** — `stepUp` included, since 0.8, and
|
|
135
|
+
`newSignIn`, since 0.9 — see
|
|
131
136
|
[Adding a locale](locales.md#adding-a-locale).
|
|
132
137
|
|
|
133
|
-
`mail.templates` answers the
|
|
138
|
+
`mail.templates` answers the twelve in use — yours, and the defaults for the
|
|
134
139
|
rest — frozen:
|
|
135
140
|
|
|
136
141
|
```ts
|
|
@@ -152,7 +157,7 @@ const { subject, html, text } = await defaults.resetPassword({
|
|
|
152
157
|
});
|
|
153
158
|
```
|
|
154
159
|
|
|
155
|
-
It answers the
|
|
160
|
+
It answers the twelve defaults, typed for `'en' | 'fr'`, without a mailer:
|
|
156
161
|
to preview an e-mail, to wrap one — add a line to the default text, say — or
|
|
157
162
|
to send one through something other than `janusMail()`. A default reused for
|
|
158
163
|
a wider set of locales is a compile error: it could not render the one it
|
|
@@ -178,7 +183,7 @@ them renders: that is when the package's `mails/` folder is read, once.
|
|
|
178
183
|
## What the defaults say
|
|
179
184
|
|
|
180
185
|
The defaults are [`@nxgt/mail-presets`](https://www.npmjs.com/package/@nxgt/mail-presets)
|
|
181
|
-
1.1.
|
|
186
|
+
1.1.1, built with a neutral grey theme and no logo:
|
|
182
187
|
|
|
183
188
|
| Template | Built from | Subject (`en`) | Subject (`fr`) |
|
|
184
189
|
| --- | --- | --- | --- |
|
|
@@ -192,18 +197,25 @@ The defaults are [`@nxgt/mail-presets`](https://www.npmjs.com/package/@nxgt/mail
|
|
|
192
197
|
| `twoFactorEnabled` | `two-factor-enabled` | Two-factor authentication was turned on | L'authentification à deux facteurs a été activée |
|
|
193
198
|
| `twoFactorDisabled` | `two-factor-disabled` | Two-factor authentication was turned off | L'authentification à deux facteurs a été désactivée |
|
|
194
199
|
| `recoveryCodeUsed` | `recovery-code-used` | A recovery code was used on your account | Un code de récupération a été utilisé sur votre compte |
|
|
200
|
+
| `newSignIn` | `new-sign-in` | New sign-in to your account | Nouvelle connexion à votre compte |
|
|
195
201
|
| `welcome` | `welcome` | Welcome, `Ada` | Bienvenue, `Ada` |
|
|
196
202
|
|
|
197
203
|
Each has a header and a footer with the brand, a heading, a greeting by name
|
|
198
204
|
(but `signInCode` and `magicLink`), a button with its link and the link again in text for a
|
|
199
|
-
client that shows no button, and a text part. The
|
|
205
|
+
client that shows no button, and a text part. The notices other than `recoveryCodeUsed` add a warning:
|
|
200
206
|
*if this was not you, secure your account now*. `stepUp` shows its code as
|
|
201
207
|
the sign-in code does, names no action — *someone, we hope you, asked to do
|
|
202
208
|
something sensitive on your account* — and warns not to share the code if
|
|
203
209
|
it was not the user, its button saying **Secure my account**. `recoveryCodeUsed` says it
|
|
204
210
|
too, after a warning banner — *a recovery code was used on your account at
|
|
205
211
|
`when`* — the codes left, and a line on generating new ones; its button says
|
|
206
|
-
**Secure my account**. `
|
|
212
|
+
**Secure my account**. `newSignIn` says the account *was signed in from a
|
|
213
|
+
device we had not seen*, then a summary of three lines — **Device**,
|
|
214
|
+
**Location** and **Time**, *Appareil*, *Lieu* and *Heure* in French — then
|
|
215
|
+
*if this was you, there is nothing to do; if this was not you, secure your
|
|
216
|
+
account now*, its button saying **Secure my account**; in the text part each
|
|
217
|
+
line of the summary is one line, its label then its value — `Device Firefox
|
|
218
|
+
on macOS` — with no blank line between rows. `welcome` has the brand in
|
|
207
219
|
its heading — "Welcome to Acme" — and the recipient's name in its subject,
|
|
208
220
|
filled at send time like the body; its button says **Get started**.
|
|
209
221
|
|
package/docs/roadmap.md
CHANGED
|
@@ -9,9 +9,8 @@ Nothing between releases.
|
|
|
9
9
|
|
|
10
10
|
## Next
|
|
11
11
|
|
|
12
|
-
- **The
|
|
13
|
-
`@nxgt/
|
|
14
|
-
sign-in from a new device, an invitation to a user type.
|
|
12
|
+
- **The invitation** — `invitation` from `@nxgt/mail-presets`, once
|
|
13
|
+
`@nxgt/janus` has a flow that sends it: an invitation to a user type.
|
|
15
14
|
- **The other presets of `@nxgt/mail-presets` 0.4.0** — `account-deleted`,
|
|
16
15
|
after a user is deleted, once `@nxgt/janus` has a deletion a link can
|
|
17
16
|
undo; and `invitation-accepted`, once it has invitations.
|
|
@@ -47,6 +46,24 @@ Nothing between releases.
|
|
|
47
46
|
|
|
48
47
|
Newest first; from the first release on, the package's CHANGELOG holds every one.
|
|
49
48
|
|
|
49
|
+
- **A readable summary and a location in words, v0.9.1.** Built with
|
|
50
|
+
`@nxgt/mail-config` 1.0.2 and `@nxgt/mail-presets` 1.1.1, the text part of
|
|
51
|
+
`newSignIn` gives each row of its summary on one line — `Device Firefox on
|
|
52
|
+
macOS` — with no blank line between rows, where it split each into a label
|
|
53
|
+
line and a value line. A new sign-in sent without `location` says
|
|
54
|
+
"Unknown" — `Location Unknown`, `Lieu Inconnu` in French — in the
|
|
55
|
+
recipient's locale, where it showed `—`.
|
|
56
|
+
|
|
57
|
+
- **The new sign-in notice, v0.9.0.** `newSignIn(to, { device, time,
|
|
58
|
+
location? })`, built from `@nxgt/mail-presets` 1.1.0's `new-sign-in` in
|
|
59
|
+
English and French — "New sign-in to your account", which device, where
|
|
60
|
+
and when, and a **Secure my account** button to `links.secureAccount()`:
|
|
61
|
+
send it when an `@nxgt/janus` 0.17 sign-in answers `newDevice: true`, or
|
|
62
|
+
on `user.newDeviceSignedIn`. Every value is your text in the recipient's
|
|
63
|
+
locale; `@nxgt/janus` sees no IP, so `location` is yours, and `—` without
|
|
64
|
+
one. Twelve e-mails now, and twelve templates to pass for a locale beyond
|
|
65
|
+
`en` and `fr`.
|
|
66
|
+
|
|
50
67
|
- **The step-up's code, v0.8.0.** `stepUp(issued, to, options?)`, built
|
|
51
68
|
from `@nxgt/mail-presets` 1.1.0's `confirm-action` in English and French:
|
|
52
69
|
send what `@nxgt/janus`'s `auth.stepUp.request(user)` answered, once
|
|
@@ -121,23 +138,3 @@ Newest first; from the first release on, the package's CHANGELOG holds every one
|
|
|
121
138
|
linking to `links.secureAccount()`: send them on `@nxgt/janus` 0.9's
|
|
122
139
|
`user.secondFactorEnabled` and `user.secondFactorDisabled` events. Seven
|
|
123
140
|
e-mails now, and seven templates to pass for a locale beyond `en` and `fr`.
|
|
124
|
-
- **Dark mode, v0.3.0.** The e-mails follow the reader's dark mode in
|
|
125
|
-
every client that supports it, Gmail excepted: a dark page, a dark card and
|
|
126
|
-
light text. Built from `@nxgt/mail-ui` 0.4.0, `@nxgt/mail-presets` 0.4.0
|
|
127
|
-
and `@nxgt/mail-i18n` 0.5.0; the text parts, subjects and variables are
|
|
128
|
-
unchanged, the manifest is still format 1, and the `@nxgt/mail` peer stays
|
|
129
|
-
`>=0.1.0 <1`.
|
|
130
|
-
|
|
131
|
-
- **The expiry in the e-mail, v0.2.0.** The verification, reset and sign-in
|
|
132
|
-
code e-mails say how long the link or code lasts — "1 hour", "1 heure" —
|
|
133
|
-
derived from the flow's `expiresAt` in the recipient's locale, measured
|
|
134
|
-
against `janusMail({ clock })` (the clock given to `janus()`), or given per
|
|
135
|
-
send as `{ expiresIn }`. Two breaks: a template called directly takes
|
|
136
|
-
`expiresIn`, and `signInCode` reads `issued.expiresAt`. Built from
|
|
137
|
-
`@nxgt/mail-presets` 0.2.0, whose text parts keep each paragraph on one
|
|
138
|
-
line.
|
|
139
|
-
|
|
140
|
-
- **The first release, v0.1.0.** The five e-mails of `@nxgt/janus`'s flows,
|
|
141
|
-
in English and French, over any `@nxgt/mail` transport: `janusMail()` and
|
|
142
|
-
`janusTemplates()`. Built with Maizzle when the package is built, shipped
|
|
143
|
-
in `mails/`, and only filled in at send time, every value escaped.
|