hazo_notify 5.0.1 → 5.2.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 +5 -5
- package/dist/components/notification_bell/index.d.ts +11 -1
- package/dist/components/notification_bell/index.d.ts.map +1 -1
- package/dist/components/notification_bell/index.js +33 -1
- package/dist/components/notification_bell/index.js.map +1 -1
- package/dist/lib/adapters/email/adapter.d.ts +2 -0
- package/dist/lib/adapters/email/adapter.d.ts.map +1 -1
- package/dist/lib/adapters/email/adapter.js +53 -22
- package/dist/lib/adapters/email/adapter.js.map +1 -1
- package/dist/lib/adapters/webhook/adapter.d.ts +10 -0
- package/dist/lib/adapters/webhook/adapter.d.ts.map +1 -0
- package/dist/lib/adapters/webhook/adapter.js +90 -0
- package/dist/lib/adapters/webhook/adapter.js.map +1 -0
- package/dist/lib/adapters/webhook/index.d.ts +8 -0
- package/dist/lib/adapters/webhook/index.d.ts.map +1 -0
- package/dist/lib/adapters/webhook/index.js +3 -0
- package/dist/lib/adapters/webhook/index.js.map +1 -0
- package/dist/lib/adapters/webhook/types.d.ts +45 -0
- package/dist/lib/adapters/webhook/types.d.ts.map +1 -0
- package/dist/lib/adapters/webhook/types.js +12 -0
- package/dist/lib/adapters/webhook/types.js.map +1 -0
- package/dist/lib/api/inbox.d.ts +32 -0
- package/dist/lib/api/inbox.d.ts.map +1 -1
- package/dist/lib/api/inbox.js +68 -0
- package/dist/lib/api/inbox.js.map +1 -1
- package/dist/lib/dispatcher/index.d.ts +2 -2
- package/dist/lib/dispatcher/index.d.ts.map +1 -1
- package/dist/lib/dispatcher/index.js +28 -8
- package/dist/lib/dispatcher/index.js.map +1 -1
- package/dist/lib/inbox/broadcaster.d.ts +44 -0
- package/dist/lib/inbox/broadcaster.d.ts.map +1 -0
- package/dist/lib/inbox/broadcaster.js +62 -0
- package/dist/lib/inbox/broadcaster.js.map +1 -0
- package/dist/lib/inbox/index.d.ts +2 -0
- package/dist/lib/inbox/index.d.ts.map +1 -1
- package/dist/lib/inbox/index.js +1 -0
- package/dist/lib/inbox/index.js.map +1 -1
- package/dist/lib/inbox/storage.d.ts.map +1 -1
- package/dist/lib/inbox/storage.js +7 -2
- package/dist/lib/inbox/storage.js.map +1 -1
- package/dist/lib/inbox/types.d.ts +7 -0
- package/dist/lib/inbox/types.d.ts.map +1 -1
- package/dist/lib/jobs/submit.d.ts.map +1 -1
- package/dist/lib/jobs/submit.js +1 -0
- package/dist/lib/jobs/submit.js.map +1 -1
- package/dist/lib/jobs/types.d.ts +1 -0
- package/dist/lib/jobs/types.d.ts.map +1 -1
- package/dist/lib/lifecycle/default_templates.d.ts +9 -0
- package/dist/lib/lifecycle/default_templates.d.ts.map +1 -0
- package/dist/lib/lifecycle/default_templates.js +160 -0
- package/dist/lib/lifecycle/default_templates.js.map +1 -0
- package/dist/lib/lifecycle/dispatch.d.ts +37 -0
- package/dist/lib/lifecycle/dispatch.d.ts.map +1 -0
- package/dist/lib/lifecycle/dispatch.js +32 -0
- package/dist/lib/lifecycle/dispatch.js.map +1 -0
- package/dist/lib/lifecycle/events.d.ts +34 -0
- package/dist/lib/lifecycle/events.d.ts.map +1 -0
- package/dist/lib/lifecycle/events.js +118 -0
- package/dist/lib/lifecycle/events.js.map +1 -0
- package/dist/lib/lifecycle/handler.d.ts +29 -0
- package/dist/lib/lifecycle/handler.d.ts.map +1 -0
- package/dist/lib/lifecycle/handler.js +145 -0
- package/dist/lib/lifecycle/handler.js.map +1 -0
- package/dist/lib/lifecycle/index.d.ts +23 -0
- package/dist/lib/lifecycle/index.d.ts.map +1 -0
- package/dist/lib/lifecycle/index.js +21 -0
- package/dist/lib/lifecycle/index.js.map +1 -0
- package/dist/lib/lifecycle/register.d.ts +29 -0
- package/dist/lib/lifecycle/register.d.ts.map +1 -0
- package/dist/lib/lifecycle/register.js +29 -0
- package/dist/lib/lifecycle/register.js.map +1 -0
- package/dist/lib/lifecycle/resolver.d.ts +22 -0
- package/dist/lib/lifecycle/resolver.d.ts.map +1 -0
- package/dist/lib/lifecycle/resolver.js +105 -0
- package/dist/lib/lifecycle/resolver.js.map +1 -0
- package/dist/lib/lifecycle/scheduler.d.ts +27 -0
- package/dist/lib/lifecycle/scheduler.d.ts.map +1 -0
- package/dist/lib/lifecycle/scheduler.js +108 -0
- package/dist/lib/lifecycle/scheduler.js.map +1 -0
- package/dist/lib/lifecycle/status.d.ts +39 -0
- package/dist/lib/lifecycle/status.d.ts.map +1 -0
- package/dist/lib/lifecycle/status.js +67 -0
- package/dist/lib/lifecycle/status.js.map +1 -0
- package/dist/lib/lifecycle/types.d.ts +165 -0
- package/dist/lib/lifecycle/types.d.ts.map +1 -0
- package/dist/lib/lifecycle/types.js +11 -0
- package/dist/lib/lifecycle/types.js.map +1 -0
- package/dist/lib/preferences/index.d.ts +3 -0
- package/dist/lib/preferences/index.d.ts.map +1 -0
- package/dist/lib/preferences/index.js +3 -0
- package/dist/lib/preferences/index.js.map +1 -0
- package/dist/lib/preferences/storage.d.ts +33 -0
- package/dist/lib/preferences/storage.d.ts.map +1 -0
- package/dist/lib/preferences/storage.js +125 -0
- package/dist/lib/preferences/storage.js.map +1 -0
- package/dist/lib/preferences/types.d.ts +59 -0
- package/dist/lib/preferences/types.d.ts.map +1 -0
- package/dist/lib/preferences/types.js +20 -0
- package/dist/lib/preferences/types.js.map +1 -0
- package/dist/lib/template_manager/db/template_repository.d.ts +4 -1
- package/dist/lib/template_manager/db/template_repository.d.ts.map +1 -1
- package/dist/lib/template_manager/db/template_repository.js +17 -1
- package/dist/lib/template_manager/db/template_repository.js.map +1 -1
- package/dist/lib/template_manager/types.d.ts +1 -0
- package/dist/lib/template_manager/types.d.ts.map +1 -1
- package/migrations/008_lifecycle_status.pg.sql +27 -0
- package/migrations/008_lifecycle_status.sqlite.sql +24 -0
- package/migrations/009_templates_locale.pg.sql +6 -0
- package/migrations/009_templates_locale.sqlite.sql +4 -0
- package/migrations/010_preferences.pg.sql +26 -0
- package/migrations/010_preferences.sqlite.sql +32 -0
- package/package.json +15 -3
package/README.md
CHANGED
|
@@ -2,15 +2,15 @@
|
|
|
2
2
|
|
|
3
3
|
A reusable component library for sending email notifications with scope-aware template management. Implements Zeptomail API integration with support for text and HTML emails, Handlebars-based email templates with multi-tenant resolution, multiple attachments, and comprehensive security features.
|
|
4
4
|
|
|
5
|
-
**Version**:
|
|
5
|
+
**Version**: 5.2.1
|
|
6
6
|
|
|
7
|
-
> **⚠ Readers on
|
|
7
|
+
> **⚠ Readers on older versions:** Many examples below still show the v3.x API surface (`send_email`, `hazo_notify/emailer`, `channels: { in_app, email, banner }`, direct `template_html` / `template_text` access). **v4.0.0 introduced a channel-pluggable architecture** — outbound sends go through `dispatch()` → inbox row + per-channel delivery row → `EmailChannel`/`TelegramChannel` adapter, with a multi-channel worker fleet. For the current API see:
|
|
8
8
|
>
|
|
9
|
-
> - [`TECHDOC.md`](./TECHDOC.md) — v4 architecture and API reference (channel registry, dispatch flow, worker fleet, adapters, schema)
|
|
9
|
+
> - [`TECHDOC.md`](./TECHDOC.md) — v4+ architecture and API reference (channel registry, dispatch flow, worker fleet, adapters, schema)
|
|
10
10
|
> - [`SETUP_CHECKLIST.md`](./SETUP_CHECKLIST.md) §"v4.0.0 Migration" — migrations 005/006/007, dispatch rewrite, boot changes
|
|
11
|
-
> - [`CHANGE_LOG.md`](./CHANGE_LOG.md) — full breaking-change manifest
|
|
11
|
+
> - [`CHANGE_LOG.md`](./CHANGE_LOG.md) — full breaking-change manifest and release history
|
|
12
12
|
>
|
|
13
|
-
> A full
|
|
13
|
+
> A full v5-aware rewrite of this README is planned. The examples that follow remain accurate for v3.x consumers; v5 consumers should read the docs above.
|
|
14
14
|
|
|
15
15
|
## Features
|
|
16
16
|
|
|
@@ -51,6 +51,16 @@ export interface NotificationBellProps {
|
|
|
51
51
|
components: NotificationBellComponents;
|
|
52
52
|
onItemClick?: (item: InboxItem) => void;
|
|
53
53
|
bellIcon?: ReactNode;
|
|
54
|
+
/**
|
|
55
|
+
* Enable real-time push via SSE. When enabled the bell opens an EventSource
|
|
56
|
+
* to `eventSourceUrl` and refreshes the unread count on `notification.created`
|
|
57
|
+
* events. Falls back to polling if the connection drops.
|
|
58
|
+
*/
|
|
59
|
+
realtime?: {
|
|
60
|
+
enabled: boolean;
|
|
61
|
+
/** URL of the SSE stream endpoint. Default: `${apiBasePath}/stream` */
|
|
62
|
+
eventSourceUrl?: string;
|
|
63
|
+
};
|
|
54
64
|
}
|
|
55
|
-
export declare function NotificationBell({ apiBasePath, pollIntervalMs, components: C, onItemClick, bellIcon, }: NotificationBellProps): import("react/jsx-runtime").JSX.Element;
|
|
65
|
+
export declare function NotificationBell({ apiBasePath, pollIntervalMs, components: C, onItemClick, bellIcon, realtime, }: NotificationBellProps): import("react/jsx-runtime").JSX.Element;
|
|
56
66
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/notification_bell/index.tsx"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAoC,KAAK,aAAa,EAAE,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAI7F,MAAM,WAAW,SAAS;IACxB,EAAE,EAAE,MAAM,CAAC;IACX,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,eAAe,EAAE,MAAM,CAAC;IACxB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,0BAA0B;IACzC,MAAM,EAAE,aAAa,CAAC;QAAE,QAAQ,CAAC,EAAE,SAAS,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;QAAC,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAAA;KAAE,CAAC,CAAC;IAC5F,YAAY,EAAE,aAAa,CAAC;QAAE,QAAQ,CAAC,EAAE,SAAS,CAAC;QAAC,IAAI,CAAC,EAAE,OAAO,CAAC;QAAC,YAAY,CAAC,EAAE,CAAC,CAAC,EAAE,OAAO,KAAK,IAAI,CAAA;KAAE,CAAC,CAAC;IAC3G,mBAAmB,EAAE,aAAa,CAAC;QAAE,QAAQ,CAAC,EAAE,SAAS,CAAC;QAAC,OAAO,CAAC,EAAE,OAAO,CAAA;KAAE,CAAC,CAAC;IAChF,mBAAmB,EAAE,aAAa,CAAC;QAAE,QAAQ,CAAC,EAAE,SAAS,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACjG,gBAAgB,EAAE,aAAa,CAAC;QAAE,QAAQ,CAAC,EAAE,SAAS,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,IAAI,CAAA;KAAE,CAAC,CAAC;IAChF,KAAK,EAAE,aAAa,CAAC;QAAE,QAAQ,CAAC,EAAE,SAAS,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CAClE;AAED,MAAM,WAAW,qBAAqB;IACpC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,UAAU,EAAE,0BAA0B,CAAC;IACvC,WAAW,CAAC,EAAE,CAAC,IAAI,EAAE,SAAS,KAAK,IAAI,CAAC;IACxC,QAAQ,CAAC,EAAE,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/notification_bell/index.tsx"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAoC,KAAK,aAAa,EAAE,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAI7F,MAAM,WAAW,SAAS;IACxB,EAAE,EAAE,MAAM,CAAC;IACX,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,eAAe,EAAE,MAAM,CAAC;IACxB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,0BAA0B;IACzC,MAAM,EAAE,aAAa,CAAC;QAAE,QAAQ,CAAC,EAAE,SAAS,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;QAAC,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAAA;KAAE,CAAC,CAAC;IAC5F,YAAY,EAAE,aAAa,CAAC;QAAE,QAAQ,CAAC,EAAE,SAAS,CAAC;QAAC,IAAI,CAAC,EAAE,OAAO,CAAC;QAAC,YAAY,CAAC,EAAE,CAAC,CAAC,EAAE,OAAO,KAAK,IAAI,CAAA;KAAE,CAAC,CAAC;IAC3G,mBAAmB,EAAE,aAAa,CAAC;QAAE,QAAQ,CAAC,EAAE,SAAS,CAAC;QAAC,OAAO,CAAC,EAAE,OAAO,CAAA;KAAE,CAAC,CAAC;IAChF,mBAAmB,EAAE,aAAa,CAAC;QAAE,QAAQ,CAAC,EAAE,SAAS,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACjG,gBAAgB,EAAE,aAAa,CAAC;QAAE,QAAQ,CAAC,EAAE,SAAS,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,IAAI,CAAA;KAAE,CAAC,CAAC;IAChF,KAAK,EAAE,aAAa,CAAC;QAAE,QAAQ,CAAC,EAAE,SAAS,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CAClE;AAED,MAAM,WAAW,qBAAqB;IACpC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,UAAU,EAAE,0BAA0B,CAAC;IACvC,WAAW,CAAC,EAAE,CAAC,IAAI,EAAE,SAAS,KAAK,IAAI,CAAC;IACxC,QAAQ,CAAC,EAAE,SAAS,CAAC;IACrB;;;;OAIG;IACH,QAAQ,CAAC,EAAE;QACT,OAAO,EAAE,OAAO,CAAC;QACjB,uEAAuE;QACvE,cAAc,CAAC,EAAE,MAAM,CAAC;KACzB,CAAC;CACH;AAED,wBAAgB,gBAAgB,CAAC,EAC/B,WAAkC,EAClC,cAAgC,EAChC,UAAU,EAAE,CAAC,EACb,WAAW,EACX,QAAQ,EACR,QAAQ,GACT,EAAE,qBAAqB,2CAoHvB"}
|
|
@@ -9,7 +9,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
9
9
|
*/
|
|
10
10
|
import { useEffect, useState, useCallback } from "react";
|
|
11
11
|
const DEFAULT_POLL_MS = 30000;
|
|
12
|
-
export function NotificationBell({ apiBasePath = "/api/notifications", pollIntervalMs = DEFAULT_POLL_MS, components: C, onItemClick, bellIcon, }) {
|
|
12
|
+
export function NotificationBell({ apiBasePath = "/api/notifications", pollIntervalMs = DEFAULT_POLL_MS, components: C, onItemClick, bellIcon, realtime, }) {
|
|
13
13
|
const [unread, setUnread] = useState(0);
|
|
14
14
|
const [open, setOpen] = useState(false);
|
|
15
15
|
const [items, setItems] = useState([]);
|
|
@@ -56,6 +56,38 @@ export function NotificationBell({ apiBasePath = "/api/notifications", pollInter
|
|
|
56
56
|
}, [fetchCount, pollIntervalMs]);
|
|
57
57
|
useEffect(() => { if (open)
|
|
58
58
|
fetchList(); }, [open, fetchList]);
|
|
59
|
+
// Real-time SSE subscription
|
|
60
|
+
useEffect(() => {
|
|
61
|
+
if (!realtime?.enabled)
|
|
62
|
+
return;
|
|
63
|
+
if (typeof EventSource === "undefined")
|
|
64
|
+
return;
|
|
65
|
+
const url = realtime.eventSourceUrl ?? `${apiBasePath}/stream`;
|
|
66
|
+
let es = null;
|
|
67
|
+
let reconnect_timer = null;
|
|
68
|
+
function connect() {
|
|
69
|
+
es = new EventSource(url, { withCredentials: true });
|
|
70
|
+
es.addEventListener('notification.created', () => {
|
|
71
|
+
// Refresh unread count immediately on new notification
|
|
72
|
+
fetchCount();
|
|
73
|
+
// If dropdown is open, refresh the list too
|
|
74
|
+
if (open)
|
|
75
|
+
fetchList();
|
|
76
|
+
});
|
|
77
|
+
es.onerror = () => {
|
|
78
|
+
es?.close();
|
|
79
|
+
es = null;
|
|
80
|
+
// Back-off reconnect: try again in 5 s
|
|
81
|
+
reconnect_timer = setTimeout(connect, 5000);
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
connect();
|
|
85
|
+
return () => {
|
|
86
|
+
es?.close();
|
|
87
|
+
if (reconnect_timer)
|
|
88
|
+
clearTimeout(reconnect_timer);
|
|
89
|
+
};
|
|
90
|
+
}, [realtime?.enabled, realtime?.eventSourceUrl, apiBasePath, open, fetchCount, fetchList]);
|
|
59
91
|
const handleItem = async (item) => {
|
|
60
92
|
await fetch(`${apiBasePath}/${item.id}/read`, { method: "POST", credentials: "include" });
|
|
61
93
|
setUnread((n) => Math.max(0, n - 1));
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/components/notification_bell/index.tsx"],"names":[],"mappings":";AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,WAAW,EAAsC,MAAM,OAAO,CAAC;AAE7F,MAAM,eAAe,GAAG,KAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/components/notification_bell/index.tsx"],"names":[],"mappings":"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"}
|
|
@@ -11,6 +11,8 @@ import type { ChannelAdapter, ChannelCapabilities, ChannelSendContext, ChannelSe
|
|
|
11
11
|
import type { EmailerConfig, EmailSendResponse, EmailAttachment } from "./types.js";
|
|
12
12
|
/** Payload shape for the email channel. */
|
|
13
13
|
export interface EmailPayload {
|
|
14
|
+
/** Explicit recipient email. When provided, validated on dispatch. At send time, recipient is resolved via resolveRecipient. */
|
|
15
|
+
to?: string;
|
|
14
16
|
subject: string;
|
|
15
17
|
body_text?: string;
|
|
16
18
|
body_html?: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"adapter.d.ts","sourceRoot":"","sources":["../../../../src/lib/adapters/email/adapter.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EACV,cAAc,EACd,mBAAmB,EACnB,kBAAkB,EAClB,iBAAiB,EACjB,uBAAuB,EACxB,MAAM,yBAAyB,CAAC;AACjC,OAAO,KAAK,EAAE,aAAa,EAAE,iBAAiB,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"adapter.d.ts","sourceRoot":"","sources":["../../../../src/lib/adapters/email/adapter.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EACV,cAAc,EACd,mBAAmB,EACnB,kBAAkB,EAClB,iBAAiB,EACjB,uBAAuB,EACxB,MAAM,yBAAyB,CAAC;AACjC,OAAO,KAAK,EAAE,aAAa,EAAE,iBAAiB,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAOpF,2CAA2C;AAC3C,MAAM,WAAW,YAAY;IAC3B,gIAAgI;IAChI,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,aAAa,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACxC,WAAW,CAAC,EAAE,eAAe,EAAE,CAAC;IAChC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;IACvB,GAAG,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;CACzB;AAED;;;;;;;;GAQG;AACH,wBAAgB,kBAAkB,CAAC,CAAC,EAAE,iBAAiB,GAAG,OAAO,CAOhE;AAED,4DAA4D;AAC5D,qBAAa,YAAa,YAAW,cAAc,CAAC,YAAY,CAAC;IAgBnD,OAAO,CAAC,QAAQ,CAAC,IAAI;IAfjC,QAAQ,CAAC,YAAY,EAAE,mBAAmB,CAaxC;gBAE2B,IAAI,GAAE;QAAE,MAAM,CAAC,EAAE,aAAa,CAAA;KAAO;IAElE,QAAQ,CAAC,CAAC,EAAE,YAAY,GAAG,uBAAuB;IAc5C,IAAI,CAAC,CAAC,EAAE,YAAY,EAAE,GAAG,EAAE,kBAAkB,GAAG,OAAO,CAAC,iBAAiB,CAAC;CAyDjF;AAID,OAAO,QAAQ,yBAAyB,CAAC;IACvC,UAAU,iBAAiB;QACzB,KAAK,EAAE,YAAY,CAAC;KACrB;CACF"}
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
import { load_emailer_config } from "./config.js";
|
|
11
11
|
import { get_email_provider } from "./providers/index.js";
|
|
12
12
|
import { wrapEnvelope } from "./envelope.js";
|
|
13
|
+
import { validate_email_address } from "./utils/validation.js";
|
|
13
14
|
/**
|
|
14
15
|
* Returns true if the send failure is retryable (transient), false if permanent.
|
|
15
16
|
*
|
|
@@ -51,6 +52,9 @@ export class EmailChannel {
|
|
|
51
52
|
}
|
|
52
53
|
validate(p) {
|
|
53
54
|
const errors = [];
|
|
55
|
+
if (p.to !== undefined && !validate_email_address(p.to)) {
|
|
56
|
+
errors.push("to: invalid email address");
|
|
57
|
+
}
|
|
54
58
|
if (!p.subject || p.subject.length === 0) {
|
|
55
59
|
errors.push("subject required");
|
|
56
60
|
}
|
|
@@ -60,28 +64,55 @@ export class EmailChannel {
|
|
|
60
64
|
return errors.length > 0 ? { ok: false, errors } : { ok: true };
|
|
61
65
|
}
|
|
62
66
|
async send(p, ctx) {
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
67
|
+
try {
|
|
68
|
+
// Wrap config loading so that missing config / missing API key → permanent failure
|
|
69
|
+
// (retryable: false) rather than an unhandled exception that the flush worker
|
|
70
|
+
// conservatively treats as retryable. An infinite retry loop on a bad config
|
|
71
|
+
// is far more harmful than an immediate permanent failure.
|
|
72
|
+
let config;
|
|
73
|
+
try {
|
|
74
|
+
config = this.opts.config ?? load_emailer_config();
|
|
75
|
+
}
|
|
76
|
+
catch (e) {
|
|
77
|
+
return {
|
|
78
|
+
ok: false,
|
|
79
|
+
error: `email_config_error: ${e instanceof Error ? e.message : String(e)}`,
|
|
80
|
+
retryable: false,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
const provider = get_email_provider(config);
|
|
84
|
+
const body_text = ctx.rendered_bodies.text ?? p.body_text ?? "";
|
|
85
|
+
const body_html = ctx.rendered_bodies.html ?? p.body_html;
|
|
86
|
+
// ScopeBranding and OrgBranding are structurally identical — cast is safe.
|
|
87
|
+
const { text, html } = wrapEnvelope({
|
|
88
|
+
body_text,
|
|
89
|
+
body_html,
|
|
90
|
+
branding: ctx.branding,
|
|
91
|
+
});
|
|
92
|
+
const r = await provider.send_email({
|
|
93
|
+
to: ctx.recipient,
|
|
94
|
+
subject: p.subject,
|
|
95
|
+
content: { text, html },
|
|
96
|
+
attachments: p.attachments,
|
|
97
|
+
reply_to: p.reply_to,
|
|
98
|
+
cc: p.cc,
|
|
99
|
+
bcc: p.bcc,
|
|
100
|
+
}, config, ctx.logger);
|
|
101
|
+
return r.success
|
|
102
|
+
? { ok: true, message_id: r.message_id }
|
|
103
|
+
: { ok: false, error: r.error ?? "send_failed", retryable: classifyEmailError(r) };
|
|
104
|
+
}
|
|
105
|
+
catch (e) {
|
|
106
|
+
// Any unhandled exception (network error thrown from provider, provider bug,
|
|
107
|
+
// etc.) must be a permanent failure — never retryable. Without this outer
|
|
108
|
+
// catch the flush worker conservatively marks the delivery as retryable,
|
|
109
|
+
// causing infinite retry loops that never reach a final state.
|
|
110
|
+
return {
|
|
111
|
+
ok: false,
|
|
112
|
+
error: `send_error: ${e instanceof Error ? e.message : String(e)}`,
|
|
113
|
+
retryable: false,
|
|
114
|
+
};
|
|
115
|
+
}
|
|
85
116
|
}
|
|
86
117
|
}
|
|
87
118
|
//# sourceMappingURL=adapter.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"adapter.js","sourceRoot":"","sources":["../../../../src/lib/adapters/email/adapter.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAUH,OAAO,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAClD,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAC1D,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;
|
|
1
|
+
{"version":3,"file":"adapter.js","sourceRoot":"","sources":["../../../../src/lib/adapters/email/adapter.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { ChannelAdapter, ChannelCapabilities, ChannelSendContext, ChannelSendResult, ChannelValidationResult } from '../../channels/types.js';
|
|
2
|
+
import type { WebhookPayload, WebhookChannelOptions } from './types.js';
|
|
3
|
+
export declare class WebhookChannel implements ChannelAdapter<WebhookPayload> {
|
|
4
|
+
private readonly opts;
|
|
5
|
+
readonly capabilities: ChannelCapabilities;
|
|
6
|
+
constructor(opts: WebhookChannelOptions);
|
|
7
|
+
validate(_payload: WebhookPayload): ChannelValidationResult;
|
|
8
|
+
send(payload: WebhookPayload, ctx: ChannelSendContext): Promise<ChannelSendResult>;
|
|
9
|
+
}
|
|
10
|
+
//# sourceMappingURL=adapter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"adapter.d.ts","sourceRoot":"","sources":["../../../../src/lib/adapters/webhook/adapter.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EACV,cAAc,EACd,mBAAmB,EACnB,kBAAkB,EAClB,iBAAiB,EACjB,uBAAuB,EACxB,MAAM,yBAAyB,CAAC;AACjC,OAAO,KAAK,EAAE,cAAc,EAAmB,qBAAqB,EAAE,MAAM,YAAY,CAAC;AAEzF,qBAAa,cAAe,YAAW,cAAc,CAAC,cAAc,CAAC;IAGvD,OAAO,CAAC,QAAQ,CAAC,IAAI;IAFjC,QAAQ,CAAC,YAAY,EAAE,mBAAmB,CAAC;gBAEd,IAAI,EAAE,qBAAqB;IAgBxD,QAAQ,CAAC,QAAQ,EAAE,cAAc,GAAG,uBAAuB;IAIrD,IAAI,CAAC,OAAO,EAAE,cAAc,EAAE,GAAG,EAAE,kBAAkB,GAAG,OAAO,CAAC,iBAAiB,CAAC;CAoEzF"}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
// src/lib/adapters/webhook/adapter.ts
|
|
2
|
+
/**
|
|
3
|
+
* Webhook channel adapter.
|
|
4
|
+
*
|
|
5
|
+
* POSTs a signed JSON envelope to a consumer-supplied URL per scope_id.
|
|
6
|
+
* Retries on 5xx and 429 (Retry-After header honoured).
|
|
7
|
+
*
|
|
8
|
+
* @packageDocumentation
|
|
9
|
+
*/
|
|
10
|
+
import { createHmac } from 'node:crypto';
|
|
11
|
+
export class WebhookChannel {
|
|
12
|
+
constructor(opts) {
|
|
13
|
+
this.opts = opts;
|
|
14
|
+
this.capabilities = {
|
|
15
|
+
channel_id: opts.channel_id ?? 'webhook',
|
|
16
|
+
display_name: 'Webhook',
|
|
17
|
+
template_body_keys: [],
|
|
18
|
+
max_text_length: null,
|
|
19
|
+
splits_long_messages: false,
|
|
20
|
+
supports_explicit_recipient: false,
|
|
21
|
+
retry: {
|
|
22
|
+
max_attempts: 3,
|
|
23
|
+
backoff_ms: (n, hint) => hint ?? Math.min(60000, 2000 * Math.pow(2, Math.max(0, n - 1))),
|
|
24
|
+
},
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
validate(_payload) {
|
|
28
|
+
return { ok: true };
|
|
29
|
+
}
|
|
30
|
+
async send(payload, ctx) {
|
|
31
|
+
const url = await this.opts.resolveUrl(ctx.scope_id);
|
|
32
|
+
if (!url) {
|
|
33
|
+
return {
|
|
34
|
+
ok: false,
|
|
35
|
+
error: `no_url: no webhook URL configured for scope '${ctx.scope_id}'`,
|
|
36
|
+
retryable: false,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
const envelope = {
|
|
40
|
+
inbox_id: ctx.inbox_id,
|
|
41
|
+
user_id: ctx.user_id,
|
|
42
|
+
scope_id: ctx.scope_id,
|
|
43
|
+
rendered_bodies: ctx.rendered_bodies,
|
|
44
|
+
metadata: payload.metadata ?? {},
|
|
45
|
+
timestamp: new Date().toISOString(),
|
|
46
|
+
};
|
|
47
|
+
const body = JSON.stringify(envelope);
|
|
48
|
+
const headers = {
|
|
49
|
+
'Content-Type': 'application/json',
|
|
50
|
+
};
|
|
51
|
+
if (this.opts.secret) {
|
|
52
|
+
const sig = createHmac('sha256', this.opts.secret).update(body).digest('hex');
|
|
53
|
+
headers['X-Hazo-Signature'] = `sha256=${sig}`;
|
|
54
|
+
}
|
|
55
|
+
const controller = new AbortController();
|
|
56
|
+
const timeout = setTimeout(() => controller.abort(), this.opts.timeout_ms ?? 30000);
|
|
57
|
+
try {
|
|
58
|
+
const res = await fetch(url, {
|
|
59
|
+
method: 'POST',
|
|
60
|
+
headers,
|
|
61
|
+
body,
|
|
62
|
+
signal: controller.signal,
|
|
63
|
+
});
|
|
64
|
+
if (res.ok) {
|
|
65
|
+
return { ok: true };
|
|
66
|
+
}
|
|
67
|
+
const retry_after_raw = res.headers.get('Retry-After');
|
|
68
|
+
const retry_after_ms = retry_after_raw
|
|
69
|
+
? Number(retry_after_raw) * 1000
|
|
70
|
+
: undefined;
|
|
71
|
+
return {
|
|
72
|
+
ok: false,
|
|
73
|
+
error: `HTTP ${res.status}`,
|
|
74
|
+
retryable: res.status === 429 || res.status >= 500,
|
|
75
|
+
retry_after_ms,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
catch (err) {
|
|
79
|
+
return {
|
|
80
|
+
ok: false,
|
|
81
|
+
error: err instanceof Error ? err.message : String(err),
|
|
82
|
+
retryable: true,
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
finally {
|
|
86
|
+
clearTimeout(timeout);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
//# sourceMappingURL=adapter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"adapter.js","sourceRoot":"","sources":["../../../../src/lib/adapters/webhook/adapter.ts"],"names":[],"mappings":"AAAA,sCAAsC;AACtC;;;;;;;GAOG;AACH,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAUzC,MAAM,OAAO,cAAc;IAGzB,YAA6B,IAA2B;QAA3B,SAAI,GAAJ,IAAI,CAAuB;QACtD,IAAI,CAAC,YAAY,GAAG;YAClB,UAAU,EAAE,IAAI,CAAC,UAAU,IAAI,SAAS;YACxC,YAAY,EAAE,SAAS;YACvB,kBAAkB,EAAE,EAAE;YACtB,eAAe,EAAE,IAAI;YACrB,oBAAoB,EAAE,KAAK;YAC3B,2BAA2B,EAAE,KAAK;YAClC,KAAK,EAAE;gBACL,YAAY,EAAE,CAAC;gBACf,UAAU,EAAE,CAAC,CAAS,EAAE,IAAa,EAAE,EAAE,CACvC,IAAI,IAAI,IAAI,CAAC,GAAG,CAAC,KAAM,EAAE,IAAK,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;aACpE;SACF,CAAC;IACJ,CAAC;IAED,QAAQ,CAAC,QAAwB;QAC/B,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC;IACtB,CAAC;IAED,KAAK,CAAC,IAAI,CAAC,OAAuB,EAAE,GAAuB;QACzD,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;QACrD,IAAI,CAAC,GAAG,EAAE,CAAC;YACT,OAAO;gBACL,EAAE,EAAE,KAAK;gBACT,KAAK,EAAE,gDAAgD,GAAG,CAAC,QAAQ,GAAG;gBACtE,SAAS,EAAE,KAAK;aACjB,CAAC;QACJ,CAAC;QAED,MAAM,QAAQ,GAAoB;YAChC,QAAQ,EAAE,GAAG,CAAC,QAAQ;YACtB,OAAO,EAAE,GAAG,CAAC,OAAO;YACpB,QAAQ,EAAE,GAAG,CAAC,QAAQ;YACtB,eAAe,EAAE,GAAG,CAAC,eAAe;YACpC,QAAQ,EAAE,OAAO,CAAC,QAAQ,IAAI,EAAE;YAChC,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;SACpC,CAAC;QAEF,MAAM,IAAI,GAAG,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC;QACtC,MAAM,OAAO,GAA2B;YACtC,cAAc,EAAE,kBAAkB;SACnC,CAAC;QAEF,IAAI,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC;YACrB,MAAM,GAAG,GAAG,UAAU,CAAC,QAAQ,EAAE,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;YAC9E,OAAO,CAAC,kBAAkB,CAAC,GAAG,UAAU,GAAG,EAAE,CAAC;QAChD,CAAC;QAED,MAAM,UAAU,GAAG,IAAI,eAAe,EAAE,CAAC;QACzC,MAAM,OAAO,GAAG,UAAU,CACxB,GAAG,EAAE,CAAC,UAAU,CAAC,KAAK,EAAE,EACxB,IAAI,CAAC,IAAI,CAAC,UAAU,IAAI,KAAM,CAC/B,CAAC;QAEF,IAAI,CAAC;YACH,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,GAAG,EAAE;gBAC3B,MAAM,EAAE,MAAM;gBACd,OAAO;gBACP,IAAI;gBACJ,MAAM,EAAE,UAAU,CAAC,MAAM;aAC1B,CAAC,CAAC;YAEH,IAAI,GAAG,CAAC,EAAE,EAAE,CAAC;gBACX,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC;YACtB,CAAC;YAED,MAAM,eAAe,GAAG,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,aAAa,CAAC,CAAC;YACvD,MAAM,cAAc,GAAG,eAAe;gBACpC,CAAC,CAAC,MAAM,CAAC,eAAe,CAAC,GAAG,IAAK;gBACjC,CAAC,CAAC,SAAS,CAAC;YAEd,OAAO;gBACL,EAAE,EAAE,KAAK;gBACT,KAAK,EAAE,QAAQ,GAAG,CAAC,MAAM,EAAE;gBAC3B,SAAS,EAAE,GAAG,CAAC,MAAM,KAAK,GAAG,IAAI,GAAG,CAAC,MAAM,IAAI,GAAG;gBAClD,cAAc;aACf,CAAC;QACJ,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,OAAO;gBACL,EAAE,EAAE,KAAK;gBACT,KAAK,EAAE,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC;gBACvD,SAAS,EAAE,IAAI;aAChB,CAAC;QACJ,CAAC;gBAAS,CAAC;YACT,YAAY,CAAC,OAAO,CAAC,CAAC;QACxB,CAAC;IACH,CAAC;CACF"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export { WebhookChannel } from './adapter.js';
|
|
2
|
+
export type { WebhookPayload, WebhookEnvelope, WebhookChannelOptions, } from './types.js';
|
|
3
|
+
declare module 'hazo_notify/channels' {
|
|
4
|
+
interface ChannelPayloadMap {
|
|
5
|
+
webhook: import('./types.js').WebhookPayload;
|
|
6
|
+
}
|
|
7
|
+
}
|
|
8
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/lib/adapters/webhook/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAC9C,YAAY,EACV,cAAc,EACd,eAAe,EACf,qBAAqB,GACtB,MAAM,YAAY,CAAC;AAGpB,OAAO,QAAQ,sBAAsB,CAAC;IACpC,UAAU,iBAAiB;QACzB,OAAO,EAAE,OAAO,YAAY,EAAE,cAAc,CAAC;KAC9C;CACF"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/lib/adapters/webhook/index.ts"],"names":[],"mappings":"AAAA,oCAAoC;AACpC,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Types for the webhook channel adapter.
|
|
3
|
+
*
|
|
4
|
+
* The webhook adapter POSTs a signed JSON envelope to a per-scope URL.
|
|
5
|
+
* URL management is the consumer's responsibility — the adapter calls
|
|
6
|
+
* `resolveUrl(scope_id)` at send time.
|
|
7
|
+
*
|
|
8
|
+
* @packageDocumentation
|
|
9
|
+
*/
|
|
10
|
+
/** channel_payloads entry for the webhook channel. */
|
|
11
|
+
export interface WebhookPayload {
|
|
12
|
+
/** Extra metadata merged into the envelope `metadata` field. */
|
|
13
|
+
metadata?: Record<string, unknown>;
|
|
14
|
+
}
|
|
15
|
+
/** Shape of the JSON body POSTed to the webhook endpoint. */
|
|
16
|
+
export interface WebhookEnvelope {
|
|
17
|
+
inbox_id: string;
|
|
18
|
+
user_id: string;
|
|
19
|
+
scope_id: string;
|
|
20
|
+
rendered_bodies: Record<string, string>;
|
|
21
|
+
metadata: Record<string, unknown>;
|
|
22
|
+
timestamp: string;
|
|
23
|
+
}
|
|
24
|
+
/** Constructor options for WebhookChannel. */
|
|
25
|
+
export interface WebhookChannelOptions {
|
|
26
|
+
/**
|
|
27
|
+
* Stable channel ID (default: 'webhook'). Override to register multiple
|
|
28
|
+
* webhook channels with different IDs (e.g. 'webhook_slack', 'webhook_crm').
|
|
29
|
+
*/
|
|
30
|
+
channel_id?: string;
|
|
31
|
+
/**
|
|
32
|
+
* Resolves the destination URL for a given scope_id. Return null to skip
|
|
33
|
+
* the send (delivery will be marked failed with reason "no_url").
|
|
34
|
+
*/
|
|
35
|
+
resolveUrl: (scope_id: string) => Promise<string | null>;
|
|
36
|
+
/**
|
|
37
|
+
* HMAC-SHA256 signing secret. When provided, adds header:
|
|
38
|
+
* X-Hazo-Signature: sha256=<hex>
|
|
39
|
+
* The signature covers the raw JSON body bytes.
|
|
40
|
+
*/
|
|
41
|
+
secret?: string;
|
|
42
|
+
/** Request timeout in ms (default: 30_000). */
|
|
43
|
+
timeout_ms?: number;
|
|
44
|
+
}
|
|
45
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../../src/lib/adapters/webhook/types.ts"],"names":[],"mappings":"AACA;;;;;;;;GAQG;AAEH,sDAAsD;AACtD,MAAM,WAAW,cAAc;IAC7B,gEAAgE;IAChE,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACpC;AAED,6DAA6D;AAC7D,MAAM,WAAW,eAAe;IAC9B,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACxC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAClC,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,8CAA8C;AAC9C,MAAM,WAAW,qBAAqB;IACpC;;;OAGG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;;OAGG;IACH,UAAU,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IACzD;;;;OAIG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,+CAA+C;IAC/C,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// src/lib/adapters/webhook/types.ts
|
|
2
|
+
/**
|
|
3
|
+
* Types for the webhook channel adapter.
|
|
4
|
+
*
|
|
5
|
+
* The webhook adapter POSTs a signed JSON envelope to a per-scope URL.
|
|
6
|
+
* URL management is the consumer's responsibility — the adapter calls
|
|
7
|
+
* `resolveUrl(scope_id)` at send time.
|
|
8
|
+
*
|
|
9
|
+
* @packageDocumentation
|
|
10
|
+
*/
|
|
11
|
+
export {};
|
|
12
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../../../src/lib/adapters/webhook/types.ts"],"names":[],"mappings":"AAAA,oCAAoC;AACpC;;;;;;;;GAQG"}
|
package/dist/lib/api/inbox.d.ts
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
*
|
|
7
7
|
* @packageDocumentation
|
|
8
8
|
*/
|
|
9
|
+
import type { InboxBroadcastEvent } from "../inbox/broadcaster.js";
|
|
9
10
|
interface SimpleRequest {
|
|
10
11
|
url: string;
|
|
11
12
|
}
|
|
@@ -29,5 +30,36 @@ export declare function createInboxRoutes(opts: CreateInboxRoutesOptions): {
|
|
|
29
30
|
}): Promise<SimpleResponse>;
|
|
30
31
|
markAllReadHandler(req: SimpleRequest): Promise<SimpleResponse>;
|
|
31
32
|
};
|
|
33
|
+
export interface CreateInboxStreamRouteOptions {
|
|
34
|
+
/** Extract authenticated user_id from the request. Return null to reject. */
|
|
35
|
+
getAuth: (req: Request) => Promise<{
|
|
36
|
+
user_id: string;
|
|
37
|
+
} | null>;
|
|
38
|
+
/**
|
|
39
|
+
* Subscribe a listener for a user_id. Returns an unsubscribe function.
|
|
40
|
+
* Wire this to the subscribe returned by createInboxBroadcaster().
|
|
41
|
+
*/
|
|
42
|
+
subscribe: (user_id: string, fn: (event: InboxBroadcastEvent) => void) => () => void;
|
|
43
|
+
/** Heartbeat interval in ms (default 20 000). Keeps proxies alive. */
|
|
44
|
+
heartbeat_ms?: number;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Returns a Next.js-compatible GET handler for `text/event-stream`.
|
|
48
|
+
*
|
|
49
|
+
* Consumer mounting:
|
|
50
|
+
* ```ts
|
|
51
|
+
* // app/api/notifications/stream/route.ts
|
|
52
|
+
* import { createInboxStreamRoute } from 'hazo_notify/api/inbox';
|
|
53
|
+
* import { subscribe } from '@/lib/notify-init';
|
|
54
|
+
* export const GET = createInboxStreamRoute({ getAuth, subscribe });
|
|
55
|
+
* ```
|
|
56
|
+
*
|
|
57
|
+
* Events pushed to clients:
|
|
58
|
+
* event: notification.created\ndata: {"user_id":"…","inbox_id":"…"}\n\n
|
|
59
|
+
*
|
|
60
|
+
* The client (NotificationBell with realtime enabled) listens for
|
|
61
|
+
* `notification.created` and refreshes its unread count on receipt.
|
|
62
|
+
*/
|
|
63
|
+
export declare function createInboxStreamRoute(opts: CreateInboxStreamRouteOptions): (req: Request) => Promise<Response>;
|
|
32
64
|
export {};
|
|
33
65
|
//# sourceMappingURL=inbox.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"inbox.d.ts","sourceRoot":"","sources":["../../../src/lib/api/inbox.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;
|
|
1
|
+
{"version":3,"file":"inbox.d.ts","sourceRoot":"","sources":["../../../src/lib/api/inbox.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAQH,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,yBAAyB,CAAC;AAEnE,UAAU,aAAa;IAAG,GAAG,EAAE,MAAM,CAAA;CAAE;AACvC,UAAU,cAAc;IAAG,MAAM,EAAE,MAAM,CAAC;IAAC,IAAI,IAAI,OAAO,CAAC,OAAO,CAAC,CAAA;CAAE;AAMrE,MAAM,WAAW,wBAAwB;IACvC,OAAO,EAAE,CAAC,GAAG,EAAE,aAAa,KAAK,OAAO,CAAC;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC,CAAC;CACxF;AAED,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,wBAAwB;qBAErC,aAAa,GAAG,OAAO,CAAC,cAAc,CAAC;4BAiBhC,aAAa,GAAG,OAAO,CAAC,cAAc,CAAC;yBAQ9D,aAAa,OACb;QAAE,MAAM,EAAE;YAAE,EAAE,EAAE,MAAM,CAAA;SAAE,CAAA;KAAE,GAC9B,OAAO,CAAC,cAAc,CAAC;4BAOI,aAAa,GAAG,OAAO,CAAC,cAAc,CAAC;EAOxE;AAID,MAAM,WAAW,6BAA6B;IAC5C,6EAA6E;IAC7E,OAAO,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,OAAO,CAAC;QAAE,OAAO,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC,CAAC;IAC/D;;;OAGG;IACH,SAAS,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC,KAAK,EAAE,mBAAmB,KAAK,IAAI,KAAK,MAAM,IAAI,CAAC;IACrF,sEAAsE;IACtE,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,6BAA6B,IAG9C,KAAK,OAAO,KAAG,OAAO,CAAC,QAAQ,CAAC,CAwD3D"}
|
package/dist/lib/api/inbox.js
CHANGED
|
@@ -49,4 +49,72 @@ export function createInboxRoutes(opts) {
|
|
|
49
49
|
},
|
|
50
50
|
};
|
|
51
51
|
}
|
|
52
|
+
/**
|
|
53
|
+
* Returns a Next.js-compatible GET handler for `text/event-stream`.
|
|
54
|
+
*
|
|
55
|
+
* Consumer mounting:
|
|
56
|
+
* ```ts
|
|
57
|
+
* // app/api/notifications/stream/route.ts
|
|
58
|
+
* import { createInboxStreamRoute } from 'hazo_notify/api/inbox';
|
|
59
|
+
* import { subscribe } from '@/lib/notify-init';
|
|
60
|
+
* export const GET = createInboxStreamRoute({ getAuth, subscribe });
|
|
61
|
+
* ```
|
|
62
|
+
*
|
|
63
|
+
* Events pushed to clients:
|
|
64
|
+
* event: notification.created\ndata: {"user_id":"…","inbox_id":"…"}\n\n
|
|
65
|
+
*
|
|
66
|
+
* The client (NotificationBell with realtime enabled) listens for
|
|
67
|
+
* `notification.created` and refreshes its unread count on receipt.
|
|
68
|
+
*/
|
|
69
|
+
export function createInboxStreamRoute(opts) {
|
|
70
|
+
const heartbeat_ms = opts.heartbeat_ms ?? 20000;
|
|
71
|
+
return async function GET(req) {
|
|
72
|
+
const auth = await opts.getAuth(req);
|
|
73
|
+
if (!auth) {
|
|
74
|
+
return new Response('Unauthorized', { status: 401 });
|
|
75
|
+
}
|
|
76
|
+
const { user_id } = auth;
|
|
77
|
+
const encoder = new TextEncoder();
|
|
78
|
+
let unsub = null;
|
|
79
|
+
let heartbeat = null;
|
|
80
|
+
const stream = new ReadableStream({
|
|
81
|
+
start(controller) {
|
|
82
|
+
// Send an initial connection confirmation event
|
|
83
|
+
controller.enqueue(encoder.encode(`event: connected\ndata: {"user_id":"${user_id}"}\n\n`));
|
|
84
|
+
// Subscribe to broadcaster events for this user
|
|
85
|
+
unsub = opts.subscribe(user_id, (event) => {
|
|
86
|
+
try {
|
|
87
|
+
controller.enqueue(encoder.encode(`event: notification.created\ndata: ${JSON.stringify(event)}\n\n`));
|
|
88
|
+
}
|
|
89
|
+
catch {
|
|
90
|
+
// Stream closed — subscriber will be cleaned up in cancel()
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
// Heartbeat to keep proxy connections alive
|
|
94
|
+
heartbeat = setInterval(() => {
|
|
95
|
+
try {
|
|
96
|
+
controller.enqueue(encoder.encode(`: ping\n\n`));
|
|
97
|
+
}
|
|
98
|
+
catch {
|
|
99
|
+
if (heartbeat)
|
|
100
|
+
clearInterval(heartbeat);
|
|
101
|
+
}
|
|
102
|
+
}, heartbeat_ms);
|
|
103
|
+
},
|
|
104
|
+
cancel() {
|
|
105
|
+
if (heartbeat)
|
|
106
|
+
clearInterval(heartbeat);
|
|
107
|
+
unsub?.();
|
|
108
|
+
},
|
|
109
|
+
});
|
|
110
|
+
return new Response(stream, {
|
|
111
|
+
headers: {
|
|
112
|
+
'Content-Type': 'text/event-stream',
|
|
113
|
+
'Cache-Control': 'no-cache, no-transform',
|
|
114
|
+
Connection: 'keep-alive',
|
|
115
|
+
'X-Accel-Buffering': 'no', // Disable nginx buffering
|
|
116
|
+
},
|
|
117
|
+
});
|
|
118
|
+
};
|
|
119
|
+
}
|
|
52
120
|
//# sourceMappingURL=inbox.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"inbox.js","sourceRoot":"","sources":["../../../src/lib/api/inbox.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EACL,iBAAiB,EACjB,WAAW,EACX,QAAQ,EACR,WAAW,GACZ,MAAM,qBAAqB,CAAC;
|
|
1
|
+
{"version":3,"file":"inbox.js","sourceRoot":"","sources":["../../../src/lib/api/inbox.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { DispatchInput, DispatchResult } from
|
|
2
|
-
export type { DispatchInput, DispatchResult } from
|
|
1
|
+
import type { DispatchInput, DispatchResult } from '../inbox/types.js';
|
|
2
|
+
export type { DispatchInput, DispatchResult } from '../inbox/types.js';
|
|
3
3
|
export declare function dispatch(input: DispatchInput): Promise<DispatchResult>;
|
|
4
4
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/lib/dispatcher/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/lib/dispatcher/index.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAEvE,YAAY,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAIvE,wBAAsB,QAAQ,CAAC,KAAK,EAAE,aAAa,GAAG,OAAO,CAAC,cAAc,CAAC,CA0F5E"}
|