@12-apps/notifications 4.7.1 → 4.9.0
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/ADOPTING.md +145 -0
- package/README.md +65 -0
- package/dist/catalog-BOVZ2b-b.d.ts +132 -0
- package/dist/{chunk-XE7HZVMH.js → chunk-BGI4BQ66.js} +1 -1
- package/dist/chunk-BGI4BQ66.js.map +1 -0
- package/dist/chunk-EKUSNUBT.js +222 -0
- package/dist/chunk-EKUSNUBT.js.map +1 -0
- package/dist/chunk-FTFLU6KH.js +114 -0
- package/dist/chunk-FTFLU6KH.js.map +1 -0
- package/dist/{chunk-HHMRCMQU.js → chunk-GGAZOLNN.js} +2 -2
- package/dist/{chunk-CUZW62JS.js → chunk-KU4FVOFA.js} +2 -2
- package/dist/{chunk-WHBMPHQE.js → chunk-KZG7QGFA.js} +28 -6
- package/dist/chunk-KZG7QGFA.js.map +1 -0
- package/dist/{chunk-MMLV4EZT.js → chunk-M5XTROYM.js} +2 -2
- package/dist/chunk-ZFIYBNZ7.js +502 -0
- package/dist/chunk-ZFIYBNZ7.js.map +1 -0
- package/dist/{create-api-notifications-B3u6Kx3x.d.ts → create-api-notifications-B-VMezVB.d.ts} +43 -4
- package/dist/{create-web-notifications-BHCzaU2y.d.ts → create-web-notifications-B-xQ6R0B.d.ts} +2 -2
- package/dist/email/index.d.ts +26 -0
- package/dist/email/index.js +20 -0
- package/dist/email/index.js.map +1 -0
- package/dist/email/locales.d.ts +43 -0
- package/dist/email/locales.js +29 -0
- package/dist/email/locales.js.map +1 -0
- package/dist/email/previews/hono.d.ts +46 -0
- package/dist/email/previews/hono.js +33 -0
- package/dist/email/previews/hono.js.map +1 -0
- package/dist/email/previews/index.d.ts +67 -0
- package/dist/email/previews/index.js +12 -0
- package/dist/email/previews/index.js.map +1 -0
- package/dist/email/previews/react/index.d.ts +43 -0
- package/dist/email/previews/react/index.js +58 -0
- package/dist/email/previews/react/index.js.map +1 -0
- package/dist/{generators-B9xt3sRh.d.ts → generators-BsuXe8Mp.d.ts} +1 -1
- package/dist/hono/index.d.ts +6 -5
- package/dist/hono/index.js +4 -3
- package/dist/hono/index.js.map +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.js +5 -5
- package/dist/{jobs-BDZ7aGHV.d.ts → jobs-D64B_bg7.d.ts} +1 -1
- package/dist/manifest/index.d.ts +39 -1
- package/dist/manifest/index.js +9 -0
- package/dist/manifest/index.js.map +1 -1
- package/dist/manifest/server.d.ts +32 -7
- package/dist/manifest/server.js +15 -4
- package/dist/manifest/server.js.map +1 -1
- package/dist/manifest/web.d.ts +18 -4
- package/dist/manifest/web.js +9 -1
- package/dist/manifest/web.js.map +1 -1
- package/dist/{preferences-screen-IOW6Y2H2.js → preferences-screen-V3ZBWP7A.js} +2 -2
- package/dist/preview-screen-DYJRAnAY.d.ts +83 -0
- package/dist/react/index.d.ts +4 -4
- package/dist/react/index.js +10 -10
- package/dist/server/index.d.ts +8 -7
- package/dist/server/index.js +5 -4
- package/dist/template-Dlt5S54z.d.ts +196 -0
- package/dist/{types-CXLAG3UU.d.ts → types-BFmAY0P5.d.ts} +39 -2
- package/dist/web-push/index.d.ts +2 -2
- package/dist/{web-push-Cs14Wp9u.d.ts → web-push-D-ysQd5h.d.ts} +1 -1
- package/dist/{wire-6dzyfDE7.d.ts → wire-_coV3tJk.d.ts} +1 -1
- package/package.json +23 -3
- package/src/email/chrome.en-US.ts +8 -0
- package/src/email/chrome.pt-BR.ts +21 -0
- package/src/email/index.ts +43 -0
- package/src/email/locales.ts +29 -0
- package/src/email/previews/catalog.ts +208 -0
- package/src/email/previews/hono.ts +69 -0
- package/src/email/previews/index.ts +26 -0
- package/src/email/previews/react/copy.en-US.ts +24 -0
- package/src/email/previews/react/copy.pt-BR.ts +24 -0
- package/src/email/previews/react/copy.ts +42 -0
- package/src/email/previews/react/index.tsx +14 -0
- package/src/email/previews/react/loadable.tsx +83 -0
- package/src/email/previews/react/message-list.tsx +133 -0
- package/src/email/previews/react/message-view.tsx +167 -0
- package/src/email/previews/react/preview-screen.tsx +342 -0
- package/src/email/previews/react/transport.ts +60 -0
- package/src/email/previews/routes.ts +111 -0
- package/src/email/template.ts +365 -0
- package/src/email/text.ts +59 -0
- package/src/email/theme.ts +77 -0
- package/src/index.ts +1 -0
- package/src/manifest/index.ts +37 -0
- package/src/manifest/server.ts +23 -0
- package/src/manifest/web.ts +13 -0
- package/src/server/db.ts +12 -1
- package/src/server/dispatch.ts +4 -0
- package/src/server/router.ts +17 -1
- package/src/server/transports/email.ts +62 -0
- package/src/types.ts +39 -1
- package/dist/chunk-WHBMPHQE.js.map +0 -1
- package/dist/chunk-XE7HZVMH.js.map +0 -1
- /package/dist/{chunk-HHMRCMQU.js.map → chunk-GGAZOLNN.js.map} +0 -0
- /package/dist/{chunk-CUZW62JS.js.map → chunk-KU4FVOFA.js.map} +0 -0
- /package/dist/{chunk-MMLV4EZT.js.map → chunk-M5XTROYM.js.map} +0 -0
- /package/dist/{preferences-screen-IOW6Y2H2.js.map → preferences-screen-V3ZBWP7A.js.map} +0 -0
package/dist/{create-web-notifications-BHCzaU2y.d.ts → create-web-notifications-B-xQ6R0B.d.ts}
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { JSX, ComponentType } from 'react';
|
|
2
|
-
import { L as ListNotificationsResult, a as ChannelRow, I as InboxNotification, N as NotificationMessages } from './wire-
|
|
3
|
-
import {
|
|
2
|
+
import { L as ListNotificationsResult, a as ChannelRow, I as InboxNotification, N as NotificationMessages } from './wire-_coV3tJk.js';
|
|
3
|
+
import { c as NotificationChannel } from './types-BFmAY0P5.js';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* How the notification screens reach their data (12-15) — the report-builder
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { E as EmailDocument } from '../template-Dlt5S54z.js';
|
|
2
|
+
export { a as EMAIL_CONTENT_WIDTH, b as EMAIL_FONT_STACK, c as EmailAction, d as EmailChromeCopy, e as EmailFact, f as EmailTheme, N as NEUTRAL_EMAIL_THEME, R as RenderedEmail, r as renderEmail, g as renderEmailHtml, s as safeHref } from '../template-Dlt5S54z.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* The plain-text half.
|
|
6
|
+
*
|
|
7
|
+
* Not a courtesy. Every major spam filter scores a `text/html` part with no
|
|
8
|
+
* `text/plain` twin, and a watch, a terminal client and a screen reader in
|
|
9
|
+
* plain-text mode show this and nothing else.
|
|
10
|
+
*
|
|
11
|
+
* It renders from the SAME `EmailDocument` the HTML half does, which is the
|
|
12
|
+
* property that matters: a line added to one is in the other, so the two cannot
|
|
13
|
+
* drift. Its own module only because the HTML renderer is already a file's
|
|
14
|
+
* worth of client workarounds — the seam is the output format, not the content.
|
|
15
|
+
*/
|
|
16
|
+
/**
|
|
17
|
+
* Render the document as the plain-text half.
|
|
18
|
+
*
|
|
19
|
+
* Not a courtesy. Every major spam filter scores a `text/html` part with no
|
|
20
|
+
* `text/plain` twin, and a watch, a terminal client and a screen reader in
|
|
21
|
+
* plain-text mode show this and nothing else. It is rendered from the SAME
|
|
22
|
+
* object, so it cannot say less than the HTML does.
|
|
23
|
+
*/
|
|
24
|
+
declare function renderEmailText(document: EmailDocument): string;
|
|
25
|
+
|
|
26
|
+
export { EmailDocument, renderEmailText };
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import {
|
|
2
|
+
EMAIL_CONTENT_WIDTH,
|
|
3
|
+
EMAIL_FONT_STACK,
|
|
4
|
+
NEUTRAL_EMAIL_THEME,
|
|
5
|
+
renderEmail,
|
|
6
|
+
renderEmailHtml,
|
|
7
|
+
renderEmailText,
|
|
8
|
+
safeHref
|
|
9
|
+
} from "../chunk-EKUSNUBT.js";
|
|
10
|
+
import "../chunk-7QVYU63E.js";
|
|
11
|
+
export {
|
|
12
|
+
EMAIL_CONTENT_WIDTH,
|
|
13
|
+
EMAIL_FONT_STACK,
|
|
14
|
+
NEUTRAL_EMAIL_THEME,
|
|
15
|
+
renderEmail,
|
|
16
|
+
renderEmailHtml,
|
|
17
|
+
renderEmailText,
|
|
18
|
+
safeHref
|
|
19
|
+
};
|
|
20
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { d as EmailChromeCopy } from '../template-Dlt5S54z.js';
|
|
2
|
+
|
|
3
|
+
/** US English — the twin that makes {@link PT_BR_EMAIL_CHROME} a choice. */
|
|
4
|
+
declare const EN_US_EMAIL_CHROME: EmailChromeCopy;
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Brazilian Portuguese, as a NAMED pack.
|
|
8
|
+
*
|
|
9
|
+
* A host passes this by name — `chrome: PT_BR_EMAIL_CHROME`, or
|
|
10
|
+
* `localeCopy(EMAIL_CHROME)` for a host whose readers do not share one
|
|
11
|
+
* language. It is never a default: a pack a host CHOOSES is a decision in a
|
|
12
|
+
* diff, and a pack a package applies silently is another product's voice in
|
|
13
|
+
* somebody's inbox.
|
|
14
|
+
*
|
|
15
|
+
* The tagline names the brand and stops. It is deliberately not a description
|
|
16
|
+
* of what the product does — that sentence is the host's, it belongs to a
|
|
17
|
+
* vocabulary this package cannot know, and a package that guessed one would be
|
|
18
|
+
* putting words in every adopter's footer.
|
|
19
|
+
*/
|
|
20
|
+
declare const PT_BR_EMAIL_CHROME: EmailChromeCopy;
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* The layout's own words, in every language this package ships.
|
|
24
|
+
*
|
|
25
|
+
* Its own subpath (`@12-apps/notifications/email/locales`) for the reason every
|
|
26
|
+
* copy-shipping package here uses one: keeping the packs off the layout entry
|
|
27
|
+
* is what lets that entry stay the framework-free mechanism a browser can
|
|
28
|
+
* import, and it is what makes "which language" a decision a host takes by
|
|
29
|
+
* name rather than one this package takes by default.
|
|
30
|
+
*
|
|
31
|
+
* Shaped as `Record<tag, pack>` rather than importing `@12-apps/i18n`'s
|
|
32
|
+
* `LocalePack`: a package must stay liftable into a repo that has never heard
|
|
33
|
+
* of that one, so the type is a local structural mirror. The two agree by tag,
|
|
34
|
+
* and `scripts/locale-coverage-gate.mjs` is what checks that they do.
|
|
35
|
+
*
|
|
36
|
+
* A host with ONE audience passes a pack by name. A host whose readers do not
|
|
37
|
+
* share a language passes `localeCopy(EMAIL_CHROME)` and each message is
|
|
38
|
+
* written in its own recipient's language — which is the whole reason
|
|
39
|
+
* `EmailDocument.chrome` is resolved per message rather than at a mount.
|
|
40
|
+
*/
|
|
41
|
+
declare const EMAIL_CHROME: Readonly<Record<string, EmailChromeCopy>>;
|
|
42
|
+
|
|
43
|
+
export { EMAIL_CHROME, EN_US_EMAIL_CHROME, PT_BR_EMAIL_CHROME };
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import {
|
|
2
|
+
__name
|
|
3
|
+
} from "../chunk-7QVYU63E.js";
|
|
4
|
+
|
|
5
|
+
// src/email/chrome.en-US.ts
|
|
6
|
+
var EN_US_EMAIL_CHROME = {
|
|
7
|
+
fallbackHint: "If the button above does not work, copy and paste this address into your browser:",
|
|
8
|
+
automated: "This is an automated message. Please do not reply to this email.",
|
|
9
|
+
tagline: /* @__PURE__ */ __name((brand) => `Sent by ${brand}.`, "tagline")
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
// src/email/chrome.pt-BR.ts
|
|
13
|
+
var PT_BR_EMAIL_CHROME = {
|
|
14
|
+
fallbackHint: "Se o bot\xE3o acima n\xE3o funcionar, copie e cole este endere\xE7o no seu navegador:",
|
|
15
|
+
automated: "Esta \xE9 uma mensagem autom\xE1tica. N\xE3o responda a este e-mail.",
|
|
16
|
+
tagline: /* @__PURE__ */ __name((brand) => `Enviado por ${brand}.`, "tagline")
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
// src/email/locales.ts
|
|
20
|
+
var EMAIL_CHROME = {
|
|
21
|
+
"pt-BR": PT_BR_EMAIL_CHROME,
|
|
22
|
+
"en-US": EN_US_EMAIL_CHROME
|
|
23
|
+
};
|
|
24
|
+
export {
|
|
25
|
+
EMAIL_CHROME,
|
|
26
|
+
EN_US_EMAIL_CHROME,
|
|
27
|
+
PT_BR_EMAIL_CHROME
|
|
28
|
+
};
|
|
29
|
+
//# sourceMappingURL=locales.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/email/chrome.en-US.ts","../../src/email/chrome.pt-BR.ts","../../src/email/locales.ts"],"sourcesContent":["import type { EmailChromeCopy } from './template';\n\n/** US English — the twin that makes {@link PT_BR_EMAIL_CHROME} a choice. */\nexport const EN_US_EMAIL_CHROME: EmailChromeCopy = {\n fallbackHint: 'If the button above does not work, copy and paste this address into your browser:',\n automated: 'This is an automated message. Please do not reply to this email.',\n tagline: (brand) => `Sent by ${brand}.`,\n};\n","import type { EmailChromeCopy } from './template';\n\n/**\n * Brazilian Portuguese, as a NAMED pack.\n *\n * A host passes this by name — `chrome: PT_BR_EMAIL_CHROME`, or\n * `localeCopy(EMAIL_CHROME)` for a host whose readers do not share one\n * language. It is never a default: a pack a host CHOOSES is a decision in a\n * diff, and a pack a package applies silently is another product's voice in\n * somebody's inbox.\n *\n * The tagline names the brand and stops. It is deliberately not a description\n * of what the product does — that sentence is the host's, it belongs to a\n * vocabulary this package cannot know, and a package that guessed one would be\n * putting words in every adopter's footer.\n */\nexport const PT_BR_EMAIL_CHROME: EmailChromeCopy = {\n fallbackHint: 'Se o botão acima não funcionar, copie e cole este endereço no seu navegador:',\n automated: 'Esta é uma mensagem automática. Não responda a este e-mail.',\n tagline: (brand) => `Enviado por ${brand}.`,\n};\n","import { EN_US_EMAIL_CHROME } from './chrome.en-US';\nimport { PT_BR_EMAIL_CHROME } from './chrome.pt-BR';\nimport type { EmailChromeCopy } from './template';\n\n/**\n * The layout's own words, in every language this package ships.\n *\n * Its own subpath (`@12-apps/notifications/email/locales`) for the reason every\n * copy-shipping package here uses one: keeping the packs off the layout entry\n * is what lets that entry stay the framework-free mechanism a browser can\n * import, and it is what makes \"which language\" a decision a host takes by\n * name rather than one this package takes by default.\n *\n * Shaped as `Record<tag, pack>` rather than importing `@12-apps/i18n`'s\n * `LocalePack`: a package must stay liftable into a repo that has never heard\n * of that one, so the type is a local structural mirror. The two agree by tag,\n * and `scripts/locale-coverage-gate.mjs` is what checks that they do.\n *\n * A host with ONE audience passes a pack by name. A host whose readers do not\n * share a language passes `localeCopy(EMAIL_CHROME)` and each message is\n * written in its own recipient's language — which is the whole reason\n * `EmailDocument.chrome` is resolved per message rather than at a mount.\n */\nexport const EMAIL_CHROME: Readonly<Record<string, EmailChromeCopy>> = {\n 'pt-BR': PT_BR_EMAIL_CHROME,\n 'en-US': EN_US_EMAIL_CHROME,\n};\n\nexport { EN_US_EMAIL_CHROME, PT_BR_EMAIL_CHROME };\n"],"mappings":";;;;;AAGO,IAAM,qBAAsC;AAAA,EACjD,cAAc;AAAA,EACd,WAAW;AAAA,EACX,SAAS,wBAAC,UAAU,WAAW,KAAK,KAA3B;AACX;;;ACSO,IAAM,qBAAsC;AAAA,EACjD,cAAc;AAAA,EACd,WAAW;AAAA,EACX,SAAS,wBAAC,UAAU,eAAe,KAAK,KAA/B;AACX;;;ACGO,IAAM,eAA0D;AAAA,EACrE,SAAS;AAAA,EACT,SAAS;AACX;","names":[]}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { Context, Hono } from 'hono';
|
|
2
|
+
import { E as EmailPreviewsConfig } from '../../catalog-BOVZ2b-b.js';
|
|
3
|
+
import '../../template-Dlt5S54z.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* `@12-apps/notifications/email/previews/hono` — the catalogue as a router.
|
|
7
|
+
*
|
|
8
|
+
* The framework-neutral descriptors in `./server` are the contract; this is the
|
|
9
|
+
* adapter for the framework we happen to use, behind its own subpath with
|
|
10
|
+
* `hono` as an OPTIONAL peer — a host on Express, or one that only wants the
|
|
11
|
+
* layout, never resolves it.
|
|
12
|
+
*
|
|
13
|
+
* A host writes:
|
|
14
|
+
*
|
|
15
|
+
* const previews = emailPreviewsRouter({ sources, locales, defaultLocale });
|
|
16
|
+
* app.use('/api/platform/email-previews/*', requirePlatformOperator);
|
|
17
|
+
* app.route('/api/platform/email-previews', previews.router);
|
|
18
|
+
*
|
|
19
|
+
* ## The `use` line above is not decoration
|
|
20
|
+
*
|
|
21
|
+
* This surface publishes a host's whole transactional-mail inventory and the
|
|
22
|
+
* exact wording of its verification and reset mails. The routes declare no
|
|
23
|
+
* session of their own because a package cannot know who a host lets look — so
|
|
24
|
+
* the gate is the host's, it is REQUIRED, and it is written here rather than
|
|
25
|
+
* left to be inferred, because the failure is silent: an ungated mount answers
|
|
26
|
+
* every stranger and nothing about it looks wrong.
|
|
27
|
+
*/
|
|
28
|
+
interface EmailPreviewsHonoConfig extends EmailPreviewsConfig {
|
|
29
|
+
/**
|
|
30
|
+
* Optional last-resort refusal, for a host that would rather state the gate
|
|
31
|
+
* once here than mount middleware around the router.
|
|
32
|
+
*
|
|
33
|
+
* Returning `false` answers 403 before any handler runs. Absent means the
|
|
34
|
+
* host has gated the mount itself — which is the ordinary case, and why this
|
|
35
|
+
* is not required: a package that demanded its own guard would be a second
|
|
36
|
+
* authorization system beside the one the host already runs.
|
|
37
|
+
*/
|
|
38
|
+
readonly allow?: (c: Context) => Promise<boolean> | boolean;
|
|
39
|
+
}
|
|
40
|
+
/** The mounted router, plus the descriptors it was built from. */
|
|
41
|
+
interface EmailPreviewsRouter {
|
|
42
|
+
router: Hono;
|
|
43
|
+
}
|
|
44
|
+
declare function emailPreviewsRouter(config: EmailPreviewsHonoConfig): EmailPreviewsRouter;
|
|
45
|
+
|
|
46
|
+
export { type EmailPreviewsHonoConfig, type EmailPreviewsRouter, emailPreviewsRouter };
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import {
|
|
2
|
+
emailPreviewRoutes
|
|
3
|
+
} from "../../chunk-FTFLU6KH.js";
|
|
4
|
+
import {
|
|
5
|
+
__name
|
|
6
|
+
} from "../../chunk-7QVYU63E.js";
|
|
7
|
+
|
|
8
|
+
// src/email/previews/hono.ts
|
|
9
|
+
import { Hono } from "hono";
|
|
10
|
+
function emailPreviewsRouter(config) {
|
|
11
|
+
const router = new Hono();
|
|
12
|
+
for (const route of emailPreviewRoutes(config)) {
|
|
13
|
+
router.get(route.path, async (c) => {
|
|
14
|
+
if (config.allow && !await config.allow(c)) {
|
|
15
|
+
return c.json({ error: "Forbidden." }, 403);
|
|
16
|
+
}
|
|
17
|
+
const response = await route.handle({
|
|
18
|
+
params: c.req.param(),
|
|
19
|
+
// `Object.fromEntries` over the parsed query rather than the raw string:
|
|
20
|
+
// a repeated `?locale=` is a caller error, and taking the last value is
|
|
21
|
+
// the same answer every framework here gives.
|
|
22
|
+
query: c.req.query()
|
|
23
|
+
});
|
|
24
|
+
return c.json(response.body, response.status);
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
return { router };
|
|
28
|
+
}
|
|
29
|
+
__name(emailPreviewsRouter, "emailPreviewsRouter");
|
|
30
|
+
export {
|
|
31
|
+
emailPreviewsRouter
|
|
32
|
+
};
|
|
33
|
+
//# sourceMappingURL=hono.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/email/previews/hono.ts"],"sourcesContent":["import { Hono } from 'hono';\nimport type { Context } from 'hono';\n\nimport { emailPreviewRoutes } from './routes';\nimport type { EmailPreviewsConfig } from './catalog';\n\n/**\n * `@12-apps/notifications/email/previews/hono` — the catalogue as a router.\n *\n * The framework-neutral descriptors in `./server` are the contract; this is the\n * adapter for the framework we happen to use, behind its own subpath with\n * `hono` as an OPTIONAL peer — a host on Express, or one that only wants the\n * layout, never resolves it.\n *\n * A host writes:\n *\n * const previews = emailPreviewsRouter({ sources, locales, defaultLocale });\n * app.use('/api/platform/email-previews/*', requirePlatformOperator);\n * app.route('/api/platform/email-previews', previews.router);\n *\n * ## The `use` line above is not decoration\n *\n * This surface publishes a host's whole transactional-mail inventory and the\n * exact wording of its verification and reset mails. The routes declare no\n * session of their own because a package cannot know who a host lets look — so\n * the gate is the host's, it is REQUIRED, and it is written here rather than\n * left to be inferred, because the failure is silent: an ungated mount answers\n * every stranger and nothing about it looks wrong.\n */\n\nexport interface EmailPreviewsHonoConfig extends EmailPreviewsConfig {\n /**\n * Optional last-resort refusal, for a host that would rather state the gate\n * once here than mount middleware around the router.\n *\n * Returning `false` answers 403 before any handler runs. Absent means the\n * host has gated the mount itself — which is the ordinary case, and why this\n * is not required: a package that demanded its own guard would be a second\n * authorization system beside the one the host already runs.\n */\n readonly allow?: (c: Context) => Promise<boolean> | boolean;\n}\n\n/** The mounted router, plus the descriptors it was built from. */\nexport interface EmailPreviewsRouter {\n router: Hono;\n}\n\nexport function emailPreviewsRouter(config: EmailPreviewsHonoConfig): EmailPreviewsRouter {\n const router = new Hono();\n\n for (const route of emailPreviewRoutes(config)) {\n router.get(route.path, async (c) => {\n if (config.allow && !(await config.allow(c))) {\n return c.json({ error: 'Forbidden.' }, 403);\n }\n const response = await route.handle({\n params: c.req.param() as Record<string, string | undefined>,\n // `Object.fromEntries` over the parsed query rather than the raw string:\n // a repeated `?locale=` is a caller error, and taking the last value is\n // the same answer every framework here gives.\n query: c.req.query() as Record<string, string | undefined>,\n });\n return c.json(response.body as Record<string, unknown>, response.status as 200 | 400 | 404);\n });\n }\n\n return { router };\n}\n"],"mappings":";;;;;;;;AAAA,SAAS,YAAY;AAgDd,SAAS,oBAAoB,QAAsD;AACxF,QAAM,SAAS,IAAI,KAAK;AAExB,aAAW,SAAS,mBAAmB,MAAM,GAAG;AAC9C,WAAO,IAAI,MAAM,MAAM,OAAO,MAAM;AAClC,UAAI,OAAO,SAAS,CAAE,MAAM,OAAO,MAAM,CAAC,GAAI;AAC5C,eAAO,EAAE,KAAK,EAAE,OAAO,aAAa,GAAG,GAAG;AAAA,MAC5C;AACA,YAAM,WAAW,MAAM,MAAM,OAAO;AAAA,QAClC,QAAQ,EAAE,IAAI,MAAM;AAAA;AAAA;AAAA;AAAA,QAIpB,OAAO,EAAE,IAAI,MAAM;AAAA,MACrB,CAAC;AACD,aAAO,EAAE,KAAK,SAAS,MAAiC,SAAS,MAAyB;AAAA,IAC5F,CAAC;AAAA,EACH;AAEA,SAAO,EAAE,OAAO;AAClB;AApBgB;","names":[]}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { E as EmailPreviewsConfig } from '../../catalog-BOVZ2b-b.js';
|
|
2
|
+
export { A as ApiEmailPreviews, D as DuplicateEmailPreviewIdError, a as EmailPreviewCoverage, b as EmailPreviewDetail, c as EmailPreviewIndex, d as EmailPreviewMessage, e as EmailPreviewRow, f as EmailPreviewSource, g as createEmailPreviews } from '../../catalog-BOVZ2b-b.js';
|
|
3
|
+
import '../../template-Dlt5S54z.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* The two endpoints over the catalogue, as descriptors.
|
|
7
|
+
*
|
|
8
|
+
* `GET /` answers the list — one row per message, a subject each, no rendered
|
|
9
|
+
* bytes — and `GET /:id` answers one whole document. Two calls rather than one
|
|
10
|
+
* because a rendered mail is 6-10 KB and a catalogue of twenty would be twenty
|
|
11
|
+
* times that on a screen where the operator reads one.
|
|
12
|
+
*
|
|
13
|
+
* ## Nothing here can send anything
|
|
14
|
+
*
|
|
15
|
+
* The surface holds no driver, no transport and no address. `render` is pure by
|
|
16
|
+
* the contract `EmailPreviewMessage` states, so the one mistake a preview
|
|
17
|
+
* surface must be incapable of — putting a sample in somebody's inbox — is not
|
|
18
|
+
* reachable from this code at all rather than merely not done.
|
|
19
|
+
*
|
|
20
|
+
* ## The gate is the HOST's — but the POSTURE is declared here
|
|
21
|
+
*
|
|
22
|
+
* Every route carries `kind: 'authenticated'`, the wiring contract's own word
|
|
23
|
+
* for "behind the host's session resolution and its RBAC". A package cannot
|
|
24
|
+
* know WHICH gate a host uses, and this one does not try: it names no
|
|
25
|
+
* permission id, because the ids belong to whichever host mounts it.
|
|
26
|
+
*
|
|
27
|
+
* What it must not do is stay silent. `public` is the contract's word for
|
|
28
|
+
* anonymous-by-design, and the contract's DEFAULT is `authenticated` — so a
|
|
29
|
+
* descriptor that declared nothing would still read as authenticated to a
|
|
30
|
+
* host's gates, while saying nothing to the person adopting it. This surface
|
|
31
|
+
* publishes a host's whole transactional-mail inventory and the exact wording
|
|
32
|
+
* and link shape of its verification and password-reset mails, which is the
|
|
33
|
+
* reference somebody writing a convincing phishing mail would want. That is
|
|
34
|
+
* worth one word in the descriptor rather than a sentence in a document.
|
|
35
|
+
*
|
|
36
|
+
* `ADOPTING.md` states the obligation in prose as well, because a host still
|
|
37
|
+
* has to supply the gate this word only asks for.
|
|
38
|
+
*
|
|
39
|
+
* An unknown LOCALE is a 400 rather than a silent fall back to the default.
|
|
40
|
+
* This is a diagnostic surface: quietly answering the default language to
|
|
41
|
+
* `?locale=es-AR` looks exactly like a product with no Spanish, which is the
|
|
42
|
+
* question the operator was asking.
|
|
43
|
+
*/
|
|
44
|
+
/** Twin of the wiring contract's request; no import, so this package stays liftable. */
|
|
45
|
+
interface EmailPreviewRequest {
|
|
46
|
+
/** The path segment after the mount, when the caller asked for one message. */
|
|
47
|
+
params?: Readonly<Record<string, string | undefined>>;
|
|
48
|
+
query?: Readonly<Record<string, string | undefined>>;
|
|
49
|
+
}
|
|
50
|
+
interface EmailPreviewResponse {
|
|
51
|
+
status: number;
|
|
52
|
+
body: unknown;
|
|
53
|
+
}
|
|
54
|
+
interface EmailPreviewRoute {
|
|
55
|
+
method: 'GET';
|
|
56
|
+
path: string;
|
|
57
|
+
/**
|
|
58
|
+
* The wiring contract's `WireRouteKind`, restated. Always `authenticated`
|
|
59
|
+
* here: the host supplies the gate, and this is the descriptor asking for
|
|
60
|
+
* one — see the docblock above for why silence was not an option.
|
|
61
|
+
*/
|
|
62
|
+
kind: 'authenticated';
|
|
63
|
+
handle(request: EmailPreviewRequest): Promise<EmailPreviewResponse>;
|
|
64
|
+
}
|
|
65
|
+
declare function emailPreviewRoutes(config: EmailPreviewsConfig): EmailPreviewRoute[];
|
|
66
|
+
|
|
67
|
+
export { type EmailPreviewRequest, type EmailPreviewResponse, type EmailPreviewRoute, EmailPreviewsConfig, emailPreviewRoutes };
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import {
|
|
2
|
+
DuplicateEmailPreviewIdError,
|
|
3
|
+
createEmailPreviews,
|
|
4
|
+
emailPreviewRoutes
|
|
5
|
+
} from "../../chunk-FTFLU6KH.js";
|
|
6
|
+
import "../../chunk-7QVYU63E.js";
|
|
7
|
+
export {
|
|
8
|
+
DuplicateEmailPreviewIdError,
|
|
9
|
+
createEmailPreviews,
|
|
10
|
+
emailPreviewRoutes
|
|
11
|
+
};
|
|
12
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { E as EmailPreviewScreenCopy } from '../../../preview-screen-DYJRAnAY.js';
|
|
2
|
+
export { a as EmailPreviewScreenConfig, c as createEmailPreviewScreen } from '../../../preview-screen-DYJRAnAY.js';
|
|
3
|
+
import 'react';
|
|
4
|
+
|
|
5
|
+
/** US English — the twin that makes {@link PT_BR_EMAIL_PREVIEW_COPY} a choice. */
|
|
6
|
+
declare const EN_US_EMAIL_PREVIEW_COPY: EmailPreviewScreenCopy;
|
|
7
|
+
|
|
8
|
+
/** Brazilian Portuguese, as a NAMED pack — never a default. */
|
|
9
|
+
declare const PT_BR_EMAIL_PREVIEW_COPY: EmailPreviewScreenCopy;
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* One rendered message, in the three ways it is worth looking at.
|
|
13
|
+
*
|
|
14
|
+
* - **HTML** — what most people will see, in a sandboxed frame at either of the
|
|
15
|
+
* two widths that matter. A phone is not a nice-to-have: more than half of
|
|
16
|
+
* transactional mail is opened on one, and the 600px card is exactly the
|
|
17
|
+
* thing that either survives that or does not.
|
|
18
|
+
* - **Text** — the plain-text twin. Worth its own tab because it is what a spam
|
|
19
|
+
* filter scores, what a watch shows, and what a screen reader in plain-text
|
|
20
|
+
* mode reads — and because it is the half nobody ever looks at, which is how
|
|
21
|
+
* it drifts out of step with the HTML.
|
|
22
|
+
* - **Source** — the markup itself, for the moment somebody is debugging why a
|
|
23
|
+
* client rendered it oddly.
|
|
24
|
+
*
|
|
25
|
+
* ## Why an iframe, and why sandboxed
|
|
26
|
+
*
|
|
27
|
+
* The mail is a whole document with its own `<body>` background, and rendering
|
|
28
|
+
* that inside the console's DOM would both break the mail (the console's CSS
|
|
29
|
+
* reaches it) and break the console (the mail's body styles reach the page). A
|
|
30
|
+
* frame is the only honest preview.
|
|
31
|
+
*
|
|
32
|
+
* `sandbox=""` — no scripts, no forms, no top-level navigation. These documents
|
|
33
|
+
* come from the host's own renderer and carry no script, so this is less a
|
|
34
|
+
* containment measure than a statement that the preview is INERT: a click on a
|
|
35
|
+
* CTA inside a previewed mail must never navigate the operator anywhere, least
|
|
36
|
+
* of all to a sample verification link.
|
|
37
|
+
*/
|
|
38
|
+
/** The two widths the HTML view renders at. */
|
|
39
|
+
type PreviewWidth = 'desktop' | 'mobile';
|
|
40
|
+
/** Which of the three views is showing. */
|
|
41
|
+
type PreviewTab = 'html' | 'text' | 'source';
|
|
42
|
+
|
|
43
|
+
export { EN_US_EMAIL_PREVIEW_COPY, EmailPreviewScreenCopy, PT_BR_EMAIL_PREVIEW_COPY, type PreviewTab, type PreviewWidth };
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createEmailPreviewScreen
|
|
3
|
+
} from "../../../chunk-ZFIYBNZ7.js";
|
|
4
|
+
import {
|
|
5
|
+
__name
|
|
6
|
+
} from "../../../chunk-7QVYU63E.js";
|
|
7
|
+
|
|
8
|
+
// src/email/previews/react/copy.en-US.ts
|
|
9
|
+
var EN_US_EMAIL_PREVIEW_COPY = {
|
|
10
|
+
title: "Email previews",
|
|
11
|
+
description: "Every email this system sends, grouped by the package that owns it. Nothing is sent from here.",
|
|
12
|
+
searchLabel: "Filter",
|
|
13
|
+
searchPlaceholder: "subject, event or package",
|
|
14
|
+
noMatches: "No message matches the filter.",
|
|
15
|
+
pickOne: "Pick a message from the list to see its preview.",
|
|
16
|
+
tabHtml: "HTML",
|
|
17
|
+
tabText: "Text",
|
|
18
|
+
tabSource: "Source",
|
|
19
|
+
widthDesktop: "Desktop",
|
|
20
|
+
widthMobile: "Mobile",
|
|
21
|
+
subjectLabel: "Subject",
|
|
22
|
+
frameTitle: "Email preview",
|
|
23
|
+
coverageTitle: "Incomplete coverage",
|
|
24
|
+
missingSamples: /* @__PURE__ */ __name((keys) => `No sample data, so no preview: ${keys}.`, "missingSamples"),
|
|
25
|
+
orphanSamples: /* @__PURE__ */ __name((keys) => `A sample exists but nothing produces this message any more: ${keys}.`, "orphanSamples"),
|
|
26
|
+
loading: "Loading...",
|
|
27
|
+
loadError: "The previews could not be loaded.",
|
|
28
|
+
retry: "Try again"
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
// src/email/previews/react/copy.pt-BR.ts
|
|
32
|
+
var PT_BR_EMAIL_PREVIEW_COPY = {
|
|
33
|
+
title: "Pr\xE9via de e-mails",
|
|
34
|
+
description: "Todos os e-mails que este sistema envia, agrupados por pacote. Nada \xE9 enviado a partir daqui.",
|
|
35
|
+
searchLabel: "Filtrar",
|
|
36
|
+
searchPlaceholder: "assunto, evento ou pacote",
|
|
37
|
+
noMatches: "Nenhuma mensagem corresponde ao filtro.",
|
|
38
|
+
pickOne: "Escolha uma mensagem na lista para ver a pr\xE9via.",
|
|
39
|
+
tabHtml: "HTML",
|
|
40
|
+
tabText: "Texto",
|
|
41
|
+
tabSource: "C\xF3digo",
|
|
42
|
+
widthDesktop: "Computador",
|
|
43
|
+
widthMobile: "Celular",
|
|
44
|
+
subjectLabel: "Assunto",
|
|
45
|
+
frameTitle: "Pr\xE9via do e-mail",
|
|
46
|
+
coverageTitle: "Cobertura incompleta",
|
|
47
|
+
missingSamples: /* @__PURE__ */ __name((keys) => `Sem dados de exemplo, ent\xE3o n\xE3o h\xE1 pr\xE9via: ${keys}.`, "missingSamples"),
|
|
48
|
+
orphanSamples: /* @__PURE__ */ __name((keys) => `H\xE1 exemplo, mas nada mais produz esta mensagem: ${keys}.`, "orphanSamples"),
|
|
49
|
+
loading: "Carregando...",
|
|
50
|
+
loadError: "N\xE3o foi poss\xEDvel carregar as pr\xE9vias.",
|
|
51
|
+
retry: "Tentar novamente"
|
|
52
|
+
};
|
|
53
|
+
export {
|
|
54
|
+
EN_US_EMAIL_PREVIEW_COPY,
|
|
55
|
+
PT_BR_EMAIL_PREVIEW_COPY,
|
|
56
|
+
createEmailPreviewScreen
|
|
57
|
+
};
|
|
58
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../src/email/previews/react/copy.en-US.ts","../../../../src/email/previews/react/copy.pt-BR.ts"],"sourcesContent":["import type { EmailPreviewScreenCopy } from './copy';\n\n/** US English — the twin that makes {@link PT_BR_EMAIL_PREVIEW_COPY} a choice. */\nexport const EN_US_EMAIL_PREVIEW_COPY: EmailPreviewScreenCopy = {\n title: 'Email previews',\n description: 'Every email this system sends, grouped by the package that owns it. Nothing is sent from here.',\n searchLabel: 'Filter',\n searchPlaceholder: 'subject, event or package',\n noMatches: 'No message matches the filter.',\n pickOne: 'Pick a message from the list to see its preview.',\n tabHtml: 'HTML',\n tabText: 'Text',\n tabSource: 'Source',\n widthDesktop: 'Desktop',\n widthMobile: 'Mobile',\n subjectLabel: 'Subject',\n frameTitle: 'Email preview',\n coverageTitle: 'Incomplete coverage',\n missingSamples: (keys) => `No sample data, so no preview: ${keys}.`,\n orphanSamples: (keys) => `A sample exists but nothing produces this message any more: ${keys}.`,\n loading: 'Loading...',\n loadError: 'The previews could not be loaded.',\n retry: 'Try again',\n};\n","import type { EmailPreviewScreenCopy } from './copy';\n\n/** Brazilian Portuguese, as a NAMED pack — never a default. */\nexport const PT_BR_EMAIL_PREVIEW_COPY: EmailPreviewScreenCopy = {\n title: 'Prévia de e-mails',\n description: 'Todos os e-mails que este sistema envia, agrupados por pacote. Nada é enviado a partir daqui.',\n searchLabel: 'Filtrar',\n searchPlaceholder: 'assunto, evento ou pacote',\n noMatches: 'Nenhuma mensagem corresponde ao filtro.',\n pickOne: 'Escolha uma mensagem na lista para ver a prévia.',\n tabHtml: 'HTML',\n tabText: 'Texto',\n tabSource: 'Código',\n widthDesktop: 'Computador',\n widthMobile: 'Celular',\n subjectLabel: 'Assunto',\n frameTitle: 'Prévia do e-mail',\n coverageTitle: 'Cobertura incompleta',\n missingSamples: (keys) => `Sem dados de exemplo, então não há prévia: ${keys}.`,\n orphanSamples: (keys) => `Há exemplo, mas nada mais produz esta mensagem: ${keys}.`,\n loading: 'Carregando...',\n loadError: 'Não foi possível carregar as prévias.',\n retry: 'Tentar novamente',\n};\n"],"mappings":";;;;;;;;AAGO,IAAM,2BAAmD;AAAA,EAC9D,OAAO;AAAA,EACP,aAAa;AAAA,EACb,aAAa;AAAA,EACb,mBAAmB;AAAA,EACnB,WAAW;AAAA,EACX,SAAS;AAAA,EACT,SAAS;AAAA,EACT,SAAS;AAAA,EACT,WAAW;AAAA,EACX,cAAc;AAAA,EACd,aAAa;AAAA,EACb,cAAc;AAAA,EACd,YAAY;AAAA,EACZ,eAAe;AAAA,EACf,gBAAgB,wBAAC,SAAS,kCAAkC,IAAI,KAAhD;AAAA,EAChB,eAAe,wBAAC,SAAS,+DAA+D,IAAI,KAA7E;AAAA,EACf,SAAS;AAAA,EACT,WAAW;AAAA,EACX,OAAO;AACT;;;ACpBO,IAAM,2BAAmD;AAAA,EAC9D,OAAO;AAAA,EACP,aAAa;AAAA,EACb,aAAa;AAAA,EACb,mBAAmB;AAAA,EACnB,WAAW;AAAA,EACX,SAAS;AAAA,EACT,SAAS;AAAA,EACT,SAAS;AAAA,EACT,WAAW;AAAA,EACX,cAAc;AAAA,EACd,aAAa;AAAA,EACb,cAAc;AAAA,EACd,YAAY;AAAA,EACZ,eAAe;AAAA,EACf,gBAAgB,wBAAC,SAAS,0DAA8C,IAAI,KAA5D;AAAA,EAChB,eAAe,wBAAC,SAAS,sDAAmD,IAAI,KAAjE;AAAA,EACf,SAAS;AAAA,EACT,WAAW;AAAA,EACX,OAAO;AACT;","names":[]}
|
package/dist/hono/index.d.ts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { Hono, Context } from 'hono';
|
|
2
|
-
import { A as ApiNotifications, N as NotificationsServerConfig, a as NotificationsActor } from '../create-api-notifications-
|
|
3
|
-
import '../generators-
|
|
4
|
-
import '../types-
|
|
5
|
-
import '../wire-
|
|
6
|
-
import '../web-push-
|
|
2
|
+
import { A as ApiNotifications, N as NotificationsServerConfig, a as NotificationsActor } from '../create-api-notifications-B-VMezVB.js';
|
|
3
|
+
import '../generators-BsuXe8Mp.js';
|
|
4
|
+
import '../types-BFmAY0P5.js';
|
|
5
|
+
import '../wire-_coV3tJk.js';
|
|
6
|
+
import '../web-push-D-ysQd5h.js';
|
|
7
|
+
import '../template-Dlt5S54z.js';
|
|
7
8
|
|
|
8
9
|
/**
|
|
9
10
|
* `@12-apps/notifications/hono` — the account notification endpoints as a
|
package/dist/hono/index.js
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import {
|
|
2
2
|
createApiNotifications
|
|
3
|
-
} from "../chunk-
|
|
4
|
-
import "../chunk-
|
|
5
|
-
import "../chunk-XE7HZVMH.js";
|
|
3
|
+
} from "../chunk-KZG7QGFA.js";
|
|
4
|
+
import "../chunk-GGAZOLNN.js";
|
|
6
5
|
import {
|
|
7
6
|
messagesOf
|
|
8
7
|
} from "../chunk-M2TVBVH2.js";
|
|
8
|
+
import "../chunk-BGI4BQ66.js";
|
|
9
|
+
import "../chunk-EKUSNUBT.js";
|
|
9
10
|
import {
|
|
10
11
|
__name
|
|
11
12
|
} from "../chunk-7QVYU63E.js";
|
package/dist/hono/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/hono/index.ts"],"sourcesContent":["import { Hono } from 'hono';\nimport type { Context } from 'hono';\n\nimport { messagesOf } from '../messages';\n\nimport {\n createApiNotifications,\n type ApiNotifications,\n type NotificationsServerConfig,\n} from '../server/create-api-notifications';\nimport type { NotificationsActor } from '../server/context';\n\n/**\n * `@12-apps/notifications/hono` — the account notification endpoints as a\n * mountable router.\n *\n * The framework-neutral descriptors in `/server` are the contract; this is the\n * adapter for the framework we happen to use, behind its own subpath with\n * `hono` as an OPTIONAL peer (the report-builder precedent — a host on Express,\n * or one that only wants the React surface, never resolves Hono).\n *\n * A host writes:\n *\n * const notifications = notificationsRouter({ …config, resolveActor });\n * app.route('/api/account', notifications.router);\n *\n * and keeps what is genuinely its own: who the caller is. Everything after\n * that — parsing, status codes, the envelope, the pt-BR copy — is the\n * package's.\n */\n\n/**\n * Resolve the caller. Returning `null` means unauthenticated, which answers 401\n * before any handler runs.\n *\n * Note the 401 is self-guarded HERE rather than assumed from middleware: these\n * paths sit under an API prefix that a host's page middleware typically does not\n * match, and an unauthenticated inbox read that fell through would answer\n * somebody else's rows or none at all — both worse than a 401.\n */\nexport type ResolveNotificationsActor = (\n c: Context,\n) => Promise<NotificationsActor | null> | NotificationsActor | null;\n\nexport interface NotificationsHonoConfig extends NotificationsServerConfig {\n resolveActor: ResolveNotificationsActor;\n}\n\nexport interface NotificationsHono extends ApiNotifications {\n router: Hono;\n}\n\n/**\n * Reads the JSON body, tolerating an absent or malformed one — and only when\n * the caller SAID it was JSON.\n *\n * The content-type check is a CSRF speed bump, not a defence (see ADOPTING rule\n * 13, which names the actual one). `text/plain`, `multipart/form-data` and\n * `application/x-www-form-urlencoded` are the three types a cross-site `fetch`\n * or a plain `<form>` can send with NO preflight, so parsing a body regardless\n * of its type is what lets such a request reach these handlers at all. Refusing\n * them means a cross-site write has to earn a preflight first, which the browser\n * will then refuse on its own. The price is nil: every client of this surface,\n * the packaged one included, sends `application/json`.\n */\nfunction saysJson(c: Context): boolean {\n const type = c.req.header('content-type');\n if (!type) return false;\n const mime = (type.split(';')[0] ?? '').trim().toLowerCase();\n return mime === 'application/json' || mime.endsWith('+json');\n}\n\nasync function readBody(c: Context): Promise<unknown> {\n if (c.req.method === 'GET') return undefined;\n if (!saysJson(c)) return undefined;\n try {\n return await c.req.json();\n } catch {\n // A malformed body is the caller's error; the handler's own validation\n // reports it far better than a parse failure would.\n return undefined;\n }\n}\n\nexport function notificationsRouter(config: NotificationsHonoConfig): NotificationsHono {\n const api = createApiNotifications(config);\n const messages = messagesOf(config);\n const router = new Hono();\n\n // Mounted IN DESCRIPTOR ORDER, which any adapter must preserve. Hono resolves\n // by registration order, so a host route shaped `/notifications/:id` under the\n // same prefix must be registered AFTER this router or it captures\n // `/notifications/unread-count`.\n for (const route of api.routes) {\n const handler = async (c: Context): Promise<Response> => {\n const actor = await config.resolveActor(c);\n if (!actor) return c.json({ error: messages.unauthenticated }, 401);\n\n const response = await route.handle({\n actor,\n params: c.req.param() as Record<string, string | undefined>,\n query: c.req.query() as Record<string, string | undefined>,\n body: await readBody(c),\n headers: { 'user-agent': c.req.header('user-agent') },\n });\n\n // A handler that chose NO body means exactly that (204).\n if (response.body === undefined) return c.body(null, response.status as 204);\n // The status travels with the body the handler chose; the adapter never\n // reinterprets either.\n return c.json(response.body as Record<string, unknown>, response.status as 200);\n };\n\n if (route.method === 'GET') router.get(route.path, handler);\n else if (route.method === 'POST') router.post(route.path, handler);\n else if (route.method === 'PUT') router.put(route.path, handler);\n else router.delete(route.path, handler);\n }\n\n return { ...api, router };\n}\n"],"mappings":"
|
|
1
|
+
{"version":3,"sources":["../../src/hono/index.ts"],"sourcesContent":["import { Hono } from 'hono';\nimport type { Context } from 'hono';\n\nimport { messagesOf } from '../messages';\n\nimport {\n createApiNotifications,\n type ApiNotifications,\n type NotificationsServerConfig,\n} from '../server/create-api-notifications';\nimport type { NotificationsActor } from '../server/context';\n\n/**\n * `@12-apps/notifications/hono` — the account notification endpoints as a\n * mountable router.\n *\n * The framework-neutral descriptors in `/server` are the contract; this is the\n * adapter for the framework we happen to use, behind its own subpath with\n * `hono` as an OPTIONAL peer (the report-builder precedent — a host on Express,\n * or one that only wants the React surface, never resolves Hono).\n *\n * A host writes:\n *\n * const notifications = notificationsRouter({ …config, resolveActor });\n * app.route('/api/account', notifications.router);\n *\n * and keeps what is genuinely its own: who the caller is. Everything after\n * that — parsing, status codes, the envelope, the pt-BR copy — is the\n * package's.\n */\n\n/**\n * Resolve the caller. Returning `null` means unauthenticated, which answers 401\n * before any handler runs.\n *\n * Note the 401 is self-guarded HERE rather than assumed from middleware: these\n * paths sit under an API prefix that a host's page middleware typically does not\n * match, and an unauthenticated inbox read that fell through would answer\n * somebody else's rows or none at all — both worse than a 401.\n */\nexport type ResolveNotificationsActor = (\n c: Context,\n) => Promise<NotificationsActor | null> | NotificationsActor | null;\n\nexport interface NotificationsHonoConfig extends NotificationsServerConfig {\n resolveActor: ResolveNotificationsActor;\n}\n\nexport interface NotificationsHono extends ApiNotifications {\n router: Hono;\n}\n\n/**\n * Reads the JSON body, tolerating an absent or malformed one — and only when\n * the caller SAID it was JSON.\n *\n * The content-type check is a CSRF speed bump, not a defence (see ADOPTING rule\n * 13, which names the actual one). `text/plain`, `multipart/form-data` and\n * `application/x-www-form-urlencoded` are the three types a cross-site `fetch`\n * or a plain `<form>` can send with NO preflight, so parsing a body regardless\n * of its type is what lets such a request reach these handlers at all. Refusing\n * them means a cross-site write has to earn a preflight first, which the browser\n * will then refuse on its own. The price is nil: every client of this surface,\n * the packaged one included, sends `application/json`.\n */\nfunction saysJson(c: Context): boolean {\n const type = c.req.header('content-type');\n if (!type) return false;\n const mime = (type.split(';')[0] ?? '').trim().toLowerCase();\n return mime === 'application/json' || mime.endsWith('+json');\n}\n\nasync function readBody(c: Context): Promise<unknown> {\n if (c.req.method === 'GET') return undefined;\n if (!saysJson(c)) return undefined;\n try {\n return await c.req.json();\n } catch {\n // A malformed body is the caller's error; the handler's own validation\n // reports it far better than a parse failure would.\n return undefined;\n }\n}\n\nexport function notificationsRouter(config: NotificationsHonoConfig): NotificationsHono {\n const api = createApiNotifications(config);\n const messages = messagesOf(config);\n const router = new Hono();\n\n // Mounted IN DESCRIPTOR ORDER, which any adapter must preserve. Hono resolves\n // by registration order, so a host route shaped `/notifications/:id` under the\n // same prefix must be registered AFTER this router or it captures\n // `/notifications/unread-count`.\n for (const route of api.routes) {\n const handler = async (c: Context): Promise<Response> => {\n const actor = await config.resolveActor(c);\n if (!actor) return c.json({ error: messages.unauthenticated }, 401);\n\n const response = await route.handle({\n actor,\n params: c.req.param() as Record<string, string | undefined>,\n query: c.req.query() as Record<string, string | undefined>,\n body: await readBody(c),\n headers: { 'user-agent': c.req.header('user-agent') },\n });\n\n // A handler that chose NO body means exactly that (204).\n if (response.body === undefined) return c.body(null, response.status as 204);\n // The status travels with the body the handler chose; the adapter never\n // reinterprets either.\n return c.json(response.body as Record<string, unknown>, response.status as 200);\n };\n\n if (route.method === 'GET') router.get(route.path, handler);\n else if (route.method === 'POST') router.post(route.path, handler);\n else if (route.method === 'PUT') router.put(route.path, handler);\n else router.delete(route.path, handler);\n }\n\n return { ...api, router };\n}\n"],"mappings":";;;;;;;;;;;;;;AAAA,SAAS,YAAY;AAiErB,SAAS,SAAS,GAAqB;AACrC,QAAM,OAAO,EAAE,IAAI,OAAO,cAAc;AACxC,MAAI,CAAC,KAAM,QAAO;AAClB,QAAM,QAAQ,KAAK,MAAM,GAAG,EAAE,CAAC,KAAK,IAAI,KAAK,EAAE,YAAY;AAC3D,SAAO,SAAS,sBAAsB,KAAK,SAAS,OAAO;AAC7D;AALS;AAOT,eAAe,SAAS,GAA8B;AACpD,MAAI,EAAE,IAAI,WAAW,MAAO,QAAO;AACnC,MAAI,CAAC,SAAS,CAAC,EAAG,QAAO;AACzB,MAAI;AACF,WAAO,MAAM,EAAE,IAAI,KAAK;AAAA,EAC1B,QAAQ;AAGN,WAAO;AAAA,EACT;AACF;AAVe;AAYR,SAAS,oBAAoB,QAAoD;AACtF,QAAM,MAAM,uBAAuB,MAAM;AACzC,QAAM,WAAW,WAAW,MAAM;AAClC,QAAM,SAAS,IAAI,KAAK;AAMxB,aAAW,SAAS,IAAI,QAAQ;AAC9B,UAAM,UAAU,8BAAO,MAAkC;AACvD,YAAM,QAAQ,MAAM,OAAO,aAAa,CAAC;AACzC,UAAI,CAAC,MAAO,QAAO,EAAE,KAAK,EAAE,OAAO,SAAS,gBAAgB,GAAG,GAAG;AAElE,YAAM,WAAW,MAAM,MAAM,OAAO;AAAA,QAClC;AAAA,QACA,QAAQ,EAAE,IAAI,MAAM;AAAA,QACpB,OAAO,EAAE,IAAI,MAAM;AAAA,QACnB,MAAM,MAAM,SAAS,CAAC;AAAA,QACtB,SAAS,EAAE,cAAc,EAAE,IAAI,OAAO,YAAY,EAAE;AAAA,MACtD,CAAC;AAGD,UAAI,SAAS,SAAS,OAAW,QAAO,EAAE,KAAK,MAAM,SAAS,MAAa;AAG3E,aAAO,EAAE,KAAK,SAAS,MAAiC,SAAS,MAAa;AAAA,IAChF,GAjBgB;AAmBhB,QAAI,MAAM,WAAW,MAAO,QAAO,IAAI,MAAM,MAAM,OAAO;AAAA,aACjD,MAAM,WAAW,OAAQ,QAAO,KAAK,MAAM,MAAM,OAAO;AAAA,aACxD,MAAM,WAAW,MAAO,QAAO,IAAI,MAAM,MAAM,OAAO;AAAA,QAC1D,QAAO,OAAO,MAAM,MAAM,OAAO;AAAA,EACxC;AAEA,SAAO,EAAE,GAAG,KAAK,OAAO;AAC1B;AApCgB;","names":[]}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
export { D as DeliveryStatus,
|
|
2
|
-
export { N as NotificationGeneratorRegistry, c as createGeneratorRegistry } from './generators-
|
|
3
|
-
export { C as ChannelMatrix, a as ChannelRow, D as DEFAULT_CHANNEL_ROW, I as InboxNotification, L as ListNotificationsResult, N as NotificationMessages, b as NotificationRow, c as NotificationWireMessages, d as defaultChannelMatrix, e as enabledChannelsOf, i as inboxWire, m as mergeChoices, f as mergeStoredRow, g as messagesOf } from './wire-
|
|
1
|
+
export { D as DeliveryStatus, a as NOTIFICATION_CHANNELS, b as NotificationCategory, c as NotificationChannel, d as NotificationContent, e as NotificationEvent, f as NotificationGenerateContext, N as NotificationGenerator, g as NotificationLogger, h as NotificationRecipient, i as NotificationTaxonomy, j as NotificationTransport, T as TransportRecipient, t as taxonomyOf } from './types-BFmAY0P5.js';
|
|
2
|
+
export { N as NotificationGeneratorRegistry, c as createGeneratorRegistry } from './generators-BsuXe8Mp.js';
|
|
3
|
+
export { C as ChannelMatrix, a as ChannelRow, D as DEFAULT_CHANNEL_ROW, I as InboxNotification, L as ListNotificationsResult, N as NotificationMessages, b as NotificationRow, c as NotificationWireMessages, d as defaultChannelMatrix, e as enabledChannelsOf, i as inboxWire, m as mergeChoices, f as mergeStoredRow, g as messagesOf } from './wire-_coV3tJk.js';
|
|
4
4
|
|
|
5
5
|
/** Thrown by `notify` when no generator is registered for the event type. */
|
|
6
6
|
declare class UnknownNotificationTypeError extends Error {
|
package/dist/index.js
CHANGED
|
@@ -9,14 +9,14 @@ import {
|
|
|
9
9
|
mergeChoices,
|
|
10
10
|
mergeStoredRow,
|
|
11
11
|
normalizePhoneE164
|
|
12
|
-
} from "./chunk-
|
|
13
|
-
import {
|
|
14
|
-
NOTIFICATION_CHANNELS,
|
|
15
|
-
taxonomyOf
|
|
16
|
-
} from "./chunk-XE7HZVMH.js";
|
|
12
|
+
} from "./chunk-GGAZOLNN.js";
|
|
17
13
|
import {
|
|
18
14
|
messagesOf
|
|
19
15
|
} from "./chunk-M2TVBVH2.js";
|
|
16
|
+
import {
|
|
17
|
+
NOTIFICATION_CHANNELS,
|
|
18
|
+
taxonomyOf
|
|
19
|
+
} from "./chunk-BGI4BQ66.js";
|
|
20
20
|
import "./chunk-7QVYU63E.js";
|
|
21
21
|
export {
|
|
22
22
|
DEFAULT_CHANNEL_ROW,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { WireJobBlueprint } from '@12-apps/wiring';
|
|
2
|
-
import { A as ApiNotifications } from './create-api-notifications-
|
|
2
|
+
import { A as ApiNotifications } from './create-api-notifications-B-VMezVB.js';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* The two background jobs getting a message out actually needs.
|
package/dist/manifest/index.d.ts
CHANGED
|
@@ -63,5 +63,43 @@ declare const notificationsManifest: {
|
|
|
63
63
|
readonly server: readonly ["http", "jobs"];
|
|
64
64
|
readonly web: readonly ["surface"];
|
|
65
65
|
};
|
|
66
|
+
/**
|
|
67
|
+
* The e-mail PREVIEW console — a second manifest, from the same package.
|
|
68
|
+
*
|
|
69
|
+
* ## Why a second manifest and not two more keys on the first
|
|
70
|
+
*
|
|
71
|
+
* `notificationsManifest` has already spent both slots this surface needs:
|
|
72
|
+
* `http` is the account inbox at `/api/account`, `surface` is the bell and the
|
|
73
|
+
* preference matrix. A capability is singular by the contract's shape, so a
|
|
74
|
+
* package with two genuinely different surfaces declares two manifests — which
|
|
75
|
+
* is exactly what `@12-apps/auth` does with `@12-apps/auth-platform`, and for
|
|
76
|
+
* the same reason it gives: those two switches turn a sign-in method off for
|
|
77
|
+
* EVERYBODY, so they do not belong behind the same gate as "reset my password".
|
|
78
|
+
*
|
|
79
|
+
* The split here is the same one. The inbox ships to every signed-in user; this
|
|
80
|
+
* console publishes the product's whole transactional-mail inventory and the
|
|
81
|
+
* exact wording and link shape of its verification and reset mails, which is
|
|
82
|
+
* the reference someone writing a convincing phishing mail would want. Two
|
|
83
|
+
* manifests keep that expressible: a host mounts the inbox and DECLINES the
|
|
84
|
+
* console, in writing, rather than silently getting both behind one gate.
|
|
85
|
+
*
|
|
86
|
+
* ## What it deliberately does not declare
|
|
87
|
+
*
|
|
88
|
+
* No `db` — a catalogue is DERIVED from what a host already sends, so an
|
|
89
|
+
* adopter mounts it without touching its schema. No `notifications`, because
|
|
90
|
+
* this surface authors no alert. And no `email`: that capability is a DELIVERY
|
|
91
|
+
* port, and this manifest renders and never sends. The delivery port is the
|
|
92
|
+
* OTHER half of this package, and it stays where it is.
|
|
93
|
+
*/
|
|
94
|
+
declare const notificationEmailPreviewsManifest: {
|
|
95
|
+
readonly name: "@12-apps/notifications-email-previews";
|
|
96
|
+
readonly contract: 1;
|
|
97
|
+
/** A refusal to render a preview files under its own namespace, not nowhere. */
|
|
98
|
+
readonly observability: {
|
|
99
|
+
readonly namespace: "email-previews";
|
|
100
|
+
};
|
|
101
|
+
readonly server: readonly ["http"];
|
|
102
|
+
readonly web: readonly ["surface"];
|
|
103
|
+
};
|
|
66
104
|
|
|
67
|
-
export { notificationsManifest };
|
|
105
|
+
export { notificationEmailPreviewsManifest, notificationsManifest };
|
package/dist/manifest/index.js
CHANGED
|
@@ -13,7 +13,16 @@ var notificationsManifest = {
|
|
|
13
13
|
server: ["http", "jobs"],
|
|
14
14
|
web: ["surface"]
|
|
15
15
|
};
|
|
16
|
+
var notificationEmailPreviewsManifest = {
|
|
17
|
+
name: "@12-apps/notifications-email-previews",
|
|
18
|
+
contract: 1,
|
|
19
|
+
/** A refusal to render a preview files under its own namespace, not nowhere. */
|
|
20
|
+
observability: { namespace: "email-previews" },
|
|
21
|
+
server: ["http"],
|
|
22
|
+
web: ["surface"]
|
|
23
|
+
};
|
|
16
24
|
export {
|
|
25
|
+
notificationEmailPreviewsManifest,
|
|
17
26
|
notificationsManifest
|
|
18
27
|
};
|
|
19
28
|
//# sourceMappingURL=index.js.map
|