@cogenta/channels 0.1.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.
Files changed (177) hide show
  1. package/dist/adapter.d.ts +111 -0
  2. package/dist/adapter.d.ts.map +1 -0
  3. package/dist/adapter.js +2 -0
  4. package/dist/adapter.js.map +1 -0
  5. package/dist/approvals/commands.d.ts +28 -0
  6. package/dist/approvals/commands.d.ts.map +1 -0
  7. package/dist/approvals/commands.js +66 -0
  8. package/dist/approvals/commands.js.map +1 -0
  9. package/dist/approvals/dispatch.d.ts +22 -0
  10. package/dist/approvals/dispatch.d.ts.map +1 -0
  11. package/dist/approvals/dispatch.js +22 -0
  12. package/dist/approvals/dispatch.js.map +1 -0
  13. package/dist/approvals/message.d.ts +18 -0
  14. package/dist/approvals/message.d.ts.map +1 -0
  15. package/dist/approvals/message.js +40 -0
  16. package/dist/approvals/message.js.map +1 -0
  17. package/dist/approvals/signed-link.d.ts +19 -0
  18. package/dist/approvals/signed-link.d.ts.map +1 -0
  19. package/dist/approvals/signed-link.js +42 -0
  20. package/dist/approvals/signed-link.js.map +1 -0
  21. package/dist/approvals/store.d.ts +41 -0
  22. package/dist/approvals/store.d.ts.map +1 -0
  23. package/dist/approvals/store.js +41 -0
  24. package/dist/approvals/store.js.map +1 -0
  25. package/dist/approvals/token.d.ts +7 -0
  26. package/dist/approvals/token.d.ts.map +1 -0
  27. package/dist/approvals/token.js +40 -0
  28. package/dist/approvals/token.js.map +1 -0
  29. package/dist/formats/alert.d.ts +18 -0
  30. package/dist/formats/alert.d.ts.map +1 -0
  31. package/dist/formats/alert.js +49 -0
  32. package/dist/formats/alert.js.map +1 -0
  33. package/dist/formats/budget.d.ts +14 -0
  34. package/dist/formats/budget.d.ts.map +1 -0
  35. package/dist/formats/budget.js +13 -0
  36. package/dist/formats/budget.js.map +1 -0
  37. package/dist/formats/notification.d.ts +8 -0
  38. package/dist/formats/notification.d.ts.map +1 -0
  39. package/dist/formats/notification.js +26 -0
  40. package/dist/formats/notification.js.map +1 -0
  41. package/dist/formats/report.d.ts +18 -0
  42. package/dist/formats/report.d.ts.map +1 -0
  43. package/dist/formats/report.js +44 -0
  44. package/dist/formats/report.js.map +1 -0
  45. package/dist/inbound/authorize.d.ts +57 -0
  46. package/dist/inbound/authorize.d.ts.map +1 -0
  47. package/dist/inbound/authorize.js +32 -0
  48. package/dist/inbound/authorize.js.map +1 -0
  49. package/dist/inbound/router.d.ts +67 -0
  50. package/dist/inbound/router.d.ts.map +1 -0
  51. package/dist/inbound/router.js +69 -0
  52. package/dist/inbound/router.js.map +1 -0
  53. package/dist/index.d.ts +77 -0
  54. package/dist/index.d.ts.map +1 -0
  55. package/dist/index.js +42 -0
  56. package/dist/index.js.map +1 -0
  57. package/dist/linking/codes.d.ts +6 -0
  58. package/dist/linking/codes.d.ts.map +1 -0
  59. package/dist/linking/codes.js +37 -0
  60. package/dist/linking/codes.js.map +1 -0
  61. package/dist/linking/store.d.ts +39 -0
  62. package/dist/linking/store.d.ts.map +1 -0
  63. package/dist/linking/store.js +77 -0
  64. package/dist/linking/store.js.map +1 -0
  65. package/dist/linking/tables.d.ts +14 -0
  66. package/dist/linking/tables.d.ts.map +1 -0
  67. package/dist/linking/tables.js +52 -0
  68. package/dist/linking/tables.js.map +1 -0
  69. package/dist/preferences/dispatcher.d.ts +37 -0
  70. package/dist/preferences/dispatcher.d.ts.map +1 -0
  71. package/dist/preferences/dispatcher.js +103 -0
  72. package/dist/preferences/dispatcher.js.map +1 -0
  73. package/dist/preferences/quiet-hours.d.ts +12 -0
  74. package/dist/preferences/quiet-hours.d.ts.map +1 -0
  75. package/dist/preferences/quiet-hours.js +22 -0
  76. package/dist/preferences/quiet-hours.js.map +1 -0
  77. package/dist/preferences/store.d.ts +8 -0
  78. package/dist/preferences/store.d.ts.map +1 -0
  79. package/dist/preferences/store.js +60 -0
  80. package/dist/preferences/store.js.map +1 -0
  81. package/dist/preferences/tables.d.ts +12 -0
  82. package/dist/preferences/tables.d.ts.map +1 -0
  83. package/dist/preferences/tables.js +55 -0
  84. package/dist/preferences/tables.js.map +1 -0
  85. package/dist/preferences/types.d.ts +37 -0
  86. package/dist/preferences/types.d.ts.map +1 -0
  87. package/dist/preferences/types.js +31 -0
  88. package/dist/preferences/types.js.map +1 -0
  89. package/dist/providers/discord/adapter.d.ts +38 -0
  90. package/dist/providers/discord/adapter.d.ts.map +1 -0
  91. package/dist/providers/discord/adapter.js +97 -0
  92. package/dist/providers/discord/adapter.js.map +1 -0
  93. package/dist/providers/discord/client.d.ts +67 -0
  94. package/dist/providers/discord/client.d.ts.map +1 -0
  95. package/dist/providers/discord/client.js +65 -0
  96. package/dist/providers/discord/client.js.map +1 -0
  97. package/dist/providers/discord/gateway.d.ts +55 -0
  98. package/dist/providers/discord/gateway.d.ts.map +1 -0
  99. package/dist/providers/discord/gateway.js +114 -0
  100. package/dist/providers/discord/gateway.js.map +1 -0
  101. package/dist/providers/discord/inbound.d.ts +22 -0
  102. package/dist/providers/discord/inbound.d.ts.map +1 -0
  103. package/dist/providers/discord/inbound.js +75 -0
  104. package/dist/providers/discord/inbound.js.map +1 -0
  105. package/dist/providers/discord/render.d.ts +9 -0
  106. package/dist/providers/discord/render.d.ts.map +1 -0
  107. package/dist/providers/discord/render.js +73 -0
  108. package/dist/providers/discord/render.js.map +1 -0
  109. package/dist/providers/email/adapter.d.ts +18 -0
  110. package/dist/providers/email/adapter.d.ts.map +1 -0
  111. package/dist/providers/email/adapter.js +41 -0
  112. package/dist/providers/email/adapter.js.map +1 -0
  113. package/dist/providers/email/file-transport.d.ts +14 -0
  114. package/dist/providers/email/file-transport.d.ts.map +1 -0
  115. package/dist/providers/email/file-transport.js +33 -0
  116. package/dist/providers/email/file-transport.js.map +1 -0
  117. package/dist/providers/email/render.d.ts +20 -0
  118. package/dist/providers/email/render.d.ts.map +1 -0
  119. package/dist/providers/email/render.js +89 -0
  120. package/dist/providers/email/render.js.map +1 -0
  121. package/dist/providers/email/transport.d.ts +25 -0
  122. package/dist/providers/email/transport.d.ts.map +1 -0
  123. package/dist/providers/email/transport.js +2 -0
  124. package/dist/providers/email/transport.js.map +1 -0
  125. package/dist/providers/slack/adapter.d.ts +35 -0
  126. package/dist/providers/slack/adapter.d.ts.map +1 -0
  127. package/dist/providers/slack/adapter.js +91 -0
  128. package/dist/providers/slack/adapter.js.map +1 -0
  129. package/dist/providers/slack/client.d.ts +45 -0
  130. package/dist/providers/slack/client.d.ts.map +1 -0
  131. package/dist/providers/slack/client.js +54 -0
  132. package/dist/providers/slack/client.js.map +1 -0
  133. package/dist/providers/slack/inbound.d.ts +23 -0
  134. package/dist/providers/slack/inbound.d.ts.map +1 -0
  135. package/dist/providers/slack/inbound.js +76 -0
  136. package/dist/providers/slack/inbound.js.map +1 -0
  137. package/dist/providers/slack/render.d.ts +8 -0
  138. package/dist/providers/slack/render.d.ts.map +1 -0
  139. package/dist/providers/slack/render.js +72 -0
  140. package/dist/providers/slack/render.js.map +1 -0
  141. package/dist/providers/slack/socket.d.ts +34 -0
  142. package/dist/providers/slack/socket.d.ts.map +1 -0
  143. package/dist/providers/slack/socket.js +70 -0
  144. package/dist/providers/slack/socket.js.map +1 -0
  145. package/dist/providers/telegram/adapter.d.ts +35 -0
  146. package/dist/providers/telegram/adapter.d.ts.map +1 -0
  147. package/dist/providers/telegram/adapter.js +109 -0
  148. package/dist/providers/telegram/adapter.js.map +1 -0
  149. package/dist/providers/telegram/client.d.ts +78 -0
  150. package/dist/providers/telegram/client.d.ts.map +1 -0
  151. package/dist/providers/telegram/client.js +53 -0
  152. package/dist/providers/telegram/client.js.map +1 -0
  153. package/dist/providers/telegram/inbound.d.ts +21 -0
  154. package/dist/providers/telegram/inbound.d.ts.map +1 -0
  155. package/dist/providers/telegram/inbound.js +72 -0
  156. package/dist/providers/telegram/inbound.js.map +1 -0
  157. package/dist/providers/telegram/render.d.ts +15 -0
  158. package/dist/providers/telegram/render.d.ts.map +1 -0
  159. package/dist/providers/telegram/render.js +87 -0
  160. package/dist/providers/telegram/render.js.map +1 -0
  161. package/dist/providers/webhook/adapter.d.ts +40 -0
  162. package/dist/providers/webhook/adapter.d.ts.map +1 -0
  163. package/dist/providers/webhook/adapter.js +71 -0
  164. package/dist/providers/webhook/adapter.js.map +1 -0
  165. package/dist/providers/webhook/render.d.ts +26 -0
  166. package/dist/providers/webhook/render.d.ts.map +1 -0
  167. package/dist/providers/webhook/render.js +41 -0
  168. package/dist/providers/webhook/render.js.map +1 -0
  169. package/dist/providers/webhook/signing.d.ts +59 -0
  170. package/dist/providers/webhook/signing.d.ts.map +1 -0
  171. package/dist/providers/webhook/signing.js +108 -0
  172. package/dist/providers/webhook/signing.js.map +1 -0
  173. package/dist/registry.d.ts +16 -0
  174. package/dist/registry.d.ts.map +1 -0
  175. package/dist/registry.js +39 -0
  176. package/dist/registry.js.map +1 -0
  177. package/package.json +42 -0
@@ -0,0 +1,78 @@
1
+ /**
2
+ * A minimal, hand-typed client for the four Telegram Bot API methods this
3
+ * adapter needs. Telegram's Bot API is plain HTTPS/JSON — no official SDK
4
+ * is required, and this codebase's established precedent (`@cogenta/import`'s
5
+ * hand-rolled WXR parser, `@cogenta/mcp`'s hand-rolled JSON-RPC subset)
6
+ * favours a small, zero-dependency client over a general-purpose SDK for an
7
+ * API this simple, rather than adding a new direct dependency for four HTTP
8
+ * calls.
9
+ */
10
+ export interface TelegramInlineButton {
11
+ readonly text: string;
12
+ readonly callback_data: string;
13
+ }
14
+ export interface TelegramReplyMarkup {
15
+ readonly inline_keyboard: readonly (readonly TelegramInlineButton[])[];
16
+ }
17
+ export interface TelegramSendMessageParams {
18
+ readonly chat_id: string;
19
+ readonly text: string;
20
+ readonly parse_mode?: 'MarkdownV2';
21
+ readonly reply_markup?: TelegramReplyMarkup;
22
+ }
23
+ export interface TelegramEditMessageParams {
24
+ readonly chat_id: string;
25
+ readonly message_id: number;
26
+ readonly text: string;
27
+ readonly parse_mode?: 'MarkdownV2';
28
+ readonly reply_markup?: TelegramReplyMarkup;
29
+ }
30
+ export interface TelegramMessage {
31
+ readonly message_id: number;
32
+ readonly chat: {
33
+ readonly id: number;
34
+ };
35
+ }
36
+ export interface TelegramUser {
37
+ readonly id: number;
38
+ }
39
+ export interface TelegramIncomingMessage {
40
+ readonly message_id: number;
41
+ readonly chat: {
42
+ readonly id: number;
43
+ };
44
+ readonly from?: TelegramUser;
45
+ readonly text?: string;
46
+ }
47
+ export interface TelegramCallbackQuery {
48
+ readonly id: string;
49
+ readonly from: TelegramUser;
50
+ readonly message?: TelegramIncomingMessage;
51
+ readonly data?: string;
52
+ }
53
+ export interface TelegramUpdate {
54
+ readonly update_id: number;
55
+ readonly message?: TelegramIncomingMessage;
56
+ readonly callback_query?: TelegramCallbackQuery;
57
+ }
58
+ export interface TelegramClientConfig {
59
+ readonly token: string;
60
+ /** Overridable for tests; defaults to the real Telegram API host. */
61
+ readonly baseUrl?: string;
62
+ readonly fetchImpl?: typeof fetch;
63
+ /** Overridable for tests, so a 429 retry doesn't really wait. */
64
+ readonly sleepImpl?: (ms: number) => Promise<void>;
65
+ }
66
+ export interface TelegramClient {
67
+ sendMessage(params: TelegramSendMessageParams): Promise<TelegramMessage>;
68
+ editMessageText(params: TelegramEditMessageParams): Promise<void>;
69
+ answerCallbackQuery(callbackQueryId: string, text?: string): Promise<void>;
70
+ getUpdates(offset: number, timeoutSeconds: number): Promise<readonly TelegramUpdate[]>;
71
+ }
72
+ /**
73
+ * `createTelegramClient` retries a rate-limited call using Telegram's own
74
+ * `retry_after` value (seconds) rather than a guessed backoff — "Prévoir la
75
+ * file, le backoff et le regroupement dès le premier adaptateur" (lot doc).
76
+ */
77
+ export declare function createTelegramClient(config: TelegramClientConfig): TelegramClient;
78
+ //# sourceMappingURL=client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../../src/providers/telegram/client.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,53 @@
1
+ import { CogentaError } from '@cogenta/core';
2
+ const DEFAULT_BASE_URL = 'https://api.telegram.org';
3
+ function defaultSleep(ms) {
4
+ return new Promise((resolve) => setTimeout(resolve, ms));
5
+ }
6
+ /**
7
+ * `createTelegramClient` retries a rate-limited call using Telegram's own
8
+ * `retry_after` value (seconds) rather than a guessed backoff — "Prévoir la
9
+ * file, le backoff et le regroupement dès le premier adaptateur" (lot doc).
10
+ */
11
+ export function createTelegramClient(config) {
12
+ const baseUrl = config.baseUrl ?? DEFAULT_BASE_URL;
13
+ const fetchImpl = config.fetchImpl ?? fetch;
14
+ const sleepImpl = config.sleepImpl ?? defaultSleep;
15
+ async function call(method, body) {
16
+ for (;;) {
17
+ const response = await fetchImpl(`${baseUrl}/bot${config.token}/${method}`, {
18
+ method: 'POST',
19
+ headers: { 'content-type': 'application/json' },
20
+ body: JSON.stringify(body),
21
+ });
22
+ const json = (await response.json());
23
+ if (response.status === 429 && !json.ok) {
24
+ const retryAfterSeconds = json.parameters?.retry_after ?? 1;
25
+ await sleepImpl(retryAfterSeconds * 1000);
26
+ continue;
27
+ }
28
+ if (!json.ok) {
29
+ throw new CogentaError({
30
+ code: 'CHANNEL_TELEGRAM_API_ERROR',
31
+ message: `Telegram API call "${method}" failed: ${json.description}`,
32
+ hint: 'Check the bot token, and that the chat or message this call references still exists.',
33
+ details: { method, errorCode: json.error_code },
34
+ });
35
+ }
36
+ return json.result;
37
+ }
38
+ }
39
+ return {
40
+ sendMessage: (params) => call('sendMessage', params),
41
+ async editMessageText(params) {
42
+ await call('editMessageText', params);
43
+ },
44
+ async answerCallbackQuery(callbackQueryId, text) {
45
+ await call('answerCallbackQuery', {
46
+ callback_query_id: callbackQueryId,
47
+ ...(text === undefined ? {} : { text }),
48
+ });
49
+ },
50
+ getUpdates: (offset, timeoutSeconds) => call('getUpdates', { offset, timeout: timeoutSeconds }),
51
+ };
52
+ }
53
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.js","sourceRoot":"","sources":["../../../src/providers/telegram/client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAA;AA+F5C,MAAM,gBAAgB,GAAG,0BAA0B,CAAA;AAEnD,SAAS,YAAY,CAAC,EAAU;IAC9B,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,CAAA;AAC1D,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,oBAAoB,CAAC,MAA4B;IAC/D,MAAM,OAAO,GAAG,MAAM,CAAC,OAAO,IAAI,gBAAgB,CAAA;IAClD,MAAM,SAAS,GAAG,MAAM,CAAC,SAAS,IAAI,KAAK,CAAA;IAC3C,MAAM,SAAS,GAAG,MAAM,CAAC,SAAS,IAAI,YAAY,CAAA;IAElD,KAAK,UAAU,IAAI,CAAI,MAAc,EAAE,IAAa;QAClD,SAAS,CAAC;YACR,MAAM,QAAQ,GAAG,MAAM,SAAS,CAAC,GAAG,OAAO,OAAO,MAAM,CAAC,KAAK,IAAI,MAAM,EAAE,EAAE;gBAC1E,MAAM,EAAE,MAAM;gBACd,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE;gBAC/C,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC;aAC3B,CAAC,CAAA;YACF,MAAM,IAAI,GAAG,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,CAA2B,CAAA;YAE9D,IAAI,QAAQ,CAAC,MAAM,KAAK,GAAG,IAAI,CAAC,IAAI,CAAC,EAAE,EAAE,CAAC;gBACxC,MAAM,iBAAiB,GAAG,IAAI,CAAC,UAAU,EAAE,WAAW,IAAI,CAAC,CAAA;gBAC3D,MAAM,SAAS,CAAC,iBAAiB,GAAG,IAAI,CAAC,CAAA;gBACzC,SAAQ;YACV,CAAC;YAED,IAAI,CAAC,IAAI,CAAC,EAAE,EAAE,CAAC;gBACb,MAAM,IAAI,YAAY,CAAC;oBACrB,IAAI,EAAE,4BAA4B;oBAClC,OAAO,EAAE,sBAAsB,MAAM,aAAa,IAAI,CAAC,WAAW,EAAE;oBACpE,IAAI,EAAE,sFAAsF;oBAC5F,OAAO,EAAE,EAAE,MAAM,EAAE,SAAS,EAAE,IAAI,CAAC,UAAU,EAAE;iBAChD,CAAC,CAAA;YACJ,CAAC;YAED,OAAO,IAAI,CAAC,MAAM,CAAA;QACpB,CAAC;IACH,CAAC;IAED,OAAO;QACL,WAAW,EAAE,CAAC,MAAM,EAAE,EAAE,CAAC,IAAI,CAAkB,aAAa,EAAE,MAAM,CAAC;QAErE,KAAK,CAAC,eAAe,CAAC,MAAM;YAC1B,MAAM,IAAI,CAAyB,iBAAiB,EAAE,MAAM,CAAC,CAAA;QAC/D,CAAC;QAED,KAAK,CAAC,mBAAmB,CAAC,eAAe,EAAE,IAAI;YAC7C,MAAM,IAAI,CAAO,qBAAqB,EAAE;gBACtC,iBAAiB,EAAE,eAAe;gBAClC,GAAG,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC;aACxC,CAAC,CAAA;QACJ,CAAC;QAED,UAAU,EAAE,CAAC,MAAM,EAAE,cAAc,EAAE,EAAE,CACrC,IAAI,CAA4B,YAAY,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,cAAc,EAAE,CAAC;KACrF,CAAA;AACH,CAAC"}
@@ -0,0 +1,21 @@
1
+ import type { InboundHandler } from '../../adapter.js';
2
+ import type { CommandRouter } from '../../inbound/router.js';
3
+ import type { ChannelLinkStore } from '../../linking/store.js';
4
+ import type { TelegramClient, TelegramUpdate } from './client.js';
5
+ export interface TelegramInboundDeps {
6
+ readonly client: TelegramClient;
7
+ readonly linkStore: ChannelLinkStore;
8
+ readonly router: CommandRouter;
9
+ /** The handler registered via the adapter's `onInbound()`, if any. */
10
+ getExternalHandler(): InboundHandler | undefined;
11
+ }
12
+ /**
13
+ * The real routing/authorization wiring for one inbound Telegram update —
14
+ * extracted from the adapter so it is directly testable without spinning
15
+ * the real long-polling loop. Handles both a typed text command and a
16
+ * button press (`callback_query`, whose `data` is itself a command string
17
+ * rendered by `render.ts`) through the exact same `CommandRouter.route()`,
18
+ * never a second, parallel authorization path.
19
+ */
20
+ export declare function createTelegramInboundHandler(deps: TelegramInboundDeps): (update: TelegramUpdate) => Promise<void>;
21
+ //# sourceMappingURL=inbound.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"inbound.d.ts","sourceRoot":"","sources":["../../../src/providers/telegram/inbound.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAmB,cAAc,EAAE,MAAM,kBAAkB,CAAA;AACvE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAA;AAC5D,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAA;AAC9D,OAAO,KAAK,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,aAAa,CAAA;AASjE,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,MAAM,EAAE,cAAc,CAAA;IAC/B,QAAQ,CAAC,SAAS,EAAE,gBAAgB,CAAA;IACpC,QAAQ,CAAC,MAAM,EAAE,aAAa,CAAA;IAC9B,sEAAsE;IACtE,kBAAkB,IAAI,cAAc,GAAG,SAAS,CAAA;CACjD;AAED;;;;;;;GAOG;AACH,wBAAgB,4BAA4B,CAC1C,IAAI,EAAE,mBAAmB,GACxB,CAAC,MAAM,EAAE,cAAc,KAAK,OAAO,CAAC,IAAI,CAAC,CAgE3C"}
@@ -0,0 +1,72 @@
1
+ const CHANNEL_NAME = 'telegram';
2
+ const MARKDOWN_V2_SPECIAL = /[_*[\]()~`>#+\-=|{}.!\\]/g;
3
+ function escapePlainReply(text) {
4
+ return text.replace(MARKDOWN_V2_SPECIAL, (char) => `\\${char}`);
5
+ }
6
+ /**
7
+ * The real routing/authorization wiring for one inbound Telegram update —
8
+ * extracted from the adapter so it is directly testable without spinning
9
+ * the real long-polling loop. Handles both a typed text command and a
10
+ * button press (`callback_query`, whose `data` is itself a command string
11
+ * rendered by `render.ts`) through the exact same `CommandRouter.route()`,
12
+ * never a second, parallel authorization path.
13
+ */
14
+ export function createTelegramInboundHandler(deps) {
15
+ async function resolveIdentity(channelUserId) {
16
+ return deps.linkStore.resolveIdentity(CHANNEL_NAME, channelUserId);
17
+ }
18
+ async function reply(chatId, text) {
19
+ await deps.client.sendMessage({ chat_id: chatId, text: escapePlainReply(text) });
20
+ }
21
+ async function handleText(chatId, channelUserId, text) {
22
+ let identity = await resolveIdentity(channelUserId);
23
+ if (identity.linkedUserId === null) {
24
+ // An unlinked identity gets exactly one thing to try: the message AS
25
+ // a linking code. Any other failure stays silent — "Une identité de
26
+ // canal non liée à un compte est ignorée, sans réponse."
27
+ try {
28
+ await deps.linkStore.verifyCode(text.trim(), CHANNEL_NAME, channelUserId);
29
+ identity = await resolveIdentity(channelUserId);
30
+ await reply(chatId, '✅ Compte lié. Vous pouvez maintenant utiliser les commandes.');
31
+ }
32
+ catch {
33
+ // Wrong/expired/nonexistent code from an unlinked identity: silence.
34
+ }
35
+ const handler = deps.getExternalHandler();
36
+ if (handler !== undefined)
37
+ await handler({ text, identity });
38
+ return;
39
+ }
40
+ const handler = deps.getExternalHandler();
41
+ if (handler !== undefined)
42
+ await handler({ text, identity });
43
+ const result = await deps.router.route(text, identity);
44
+ if (!result.shouldReply)
45
+ return;
46
+ if (result.kind === 'forbidden') {
47
+ await reply(chatId, "🚫 Vous n'avez pas la permission d'exécuter cette commande.");
48
+ }
49
+ else if (result.kind === 'unrecognized') {
50
+ await reply(chatId, `Commande inconnue : ${result.commandName}`);
51
+ }
52
+ }
53
+ return async function handleUpdate(update) {
54
+ if (update.message?.text !== undefined && update.message.from !== undefined) {
55
+ await handleText(String(update.message.chat.id), String(update.message.from.id), update.message.text);
56
+ return;
57
+ }
58
+ if (update.callback_query !== undefined) {
59
+ const query = update.callback_query;
60
+ const chatId = query.message === undefined ? undefined : String(query.message.chat.id);
61
+ const channelUserId = String(query.from.id);
62
+ const data = query.data ?? '';
63
+ if (chatId !== undefined && data.length > 0) {
64
+ await handleText(chatId, channelUserId, data);
65
+ }
66
+ // Dismiss the button's loading state regardless of outcome — a UI
67
+ // affordance, not a disclosure.
68
+ await deps.client.answerCallbackQuery(query.id);
69
+ }
70
+ };
71
+ }
72
+ //# sourceMappingURL=inbound.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"inbound.js","sourceRoot":"","sources":["../../../src/providers/telegram/inbound.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,15 @@
1
+ import type { ChannelMessage } from '../../adapter.js';
2
+ import type { TelegramReplyMarkup } from './client.js';
3
+ export declare function escapeMarkdownV2(text: string): string;
4
+ export interface RenderedTelegramMessage {
5
+ readonly text: string;
6
+ readonly replyMarkup?: TelegramReplyMarkup;
7
+ }
8
+ /**
9
+ * Renders one of the lot's three fixed message levels into Telegram
10
+ * MarkdownV2 text plus (for an alert with actions) inline buttons. Every
11
+ * piece of user-authored text is escaped; only the structural markup this
12
+ * function itself adds (bold, bullets) is left unescaped.
13
+ */
14
+ export declare function renderTelegramMessage(message: ChannelMessage): RenderedTelegramMessage;
15
+ //# sourceMappingURL=render.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"render.d.ts","sourceRoot":"","sources":["../../../src/providers/telegram/render.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAiB,cAAc,EAAmB,MAAM,kBAAkB,CAAA;AACtF,OAAO,KAAK,EAAwB,mBAAmB,EAAE,MAAM,aAAa,CAAA;AAY5E,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAErD;AAkCD,MAAM,WAAW,uBAAuB;IACtC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,WAAW,CAAC,EAAE,mBAAmB,CAAA;CAC3C;AAED;;;;;GAKG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,cAAc,GAAG,uBAAuB,CA+CtF"}
@@ -0,0 +1,87 @@
1
+ /**
2
+ * "On n'écrit pas de Markdown Telegram dans le code métier." All
3
+ * Telegram-specific rendering lives here, entirely inside this adapter —
4
+ * `ChannelMessage` itself never knows Telegram's escaping rules exist.
5
+ */
6
+ // Telegram's MarkdownV2 requires escaping these characters outside of an
7
+ // entity (https://core.telegram.org/bots/api#markdownv2-style).
8
+ const MARKDOWN_V2_SPECIAL = /[_*[\]()~`>#+\-=|{}.!\\]/g;
9
+ export function escapeMarkdownV2(text) {
10
+ return text.replace(MARKDOWN_V2_SPECIAL, (char) => `\\${char}`);
11
+ }
12
+ const SEVERITY_PREFIX = {
13
+ info: 'ℹ️',
14
+ warning: '⚠️',
15
+ critical: '🔴',
16
+ };
17
+ /**
18
+ * Telegram's real, hard API limit on `sendMessage`'s `text`
19
+ * (https://core.telegram.org/bots/api#sendmessage) — not the abstract
20
+ * screen-budget `buildReport` (`../../formats/report.js`) already enforces
21
+ * at construction time. That budget is deliberately smaller and about
22
+ * readability; this one is a last-resort safety net so a `ReportChannelMessage`
23
+ * assembled without going through `buildReport` can never produce a request
24
+ * Telegram would reject outright.
25
+ */
26
+ const TELEGRAM_MAX_TEXT_CHARS = 4096;
27
+ const TRUNCATION_MARK = '…';
28
+ function renderActions(actions) {
29
+ if (actions === undefined || actions.length === 0)
30
+ return undefined;
31
+ const buttons = actions.map((action) => ({
32
+ text: action.label,
33
+ // The callback_data IS the command text a button press routes as —
34
+ // reusing the exact same `CommandRouter.route()` a typed command goes
35
+ // through, never a second, parallel authorization path.
36
+ callback_data: action.id,
37
+ }));
38
+ return { inline_keyboard: [buttons] };
39
+ }
40
+ /**
41
+ * Renders one of the lot's three fixed message levels into Telegram
42
+ * MarkdownV2 text plus (for an alert with actions) inline buttons. Every
43
+ * piece of user-authored text is escaped; only the structural markup this
44
+ * function itself adds (bold, bullets) is left unescaped.
45
+ */
46
+ export function renderTelegramMessage(message) {
47
+ if (message.level === 'notification') {
48
+ // "Information sans action. Une ligne."
49
+ return { text: escapeMarkdownV2(message.text) };
50
+ }
51
+ if (message.level === 'alert') {
52
+ const prefix = SEVERITY_PREFIX[message.severity];
53
+ const lines = [
54
+ `${prefix} *${escapeMarkdownV2(message.title)}*`,
55
+ escapeMarkdownV2(message.context),
56
+ `➡️ ${escapeMarkdownV2(message.expectedAction)}`,
57
+ `[${escapeMarkdownV2('Voir dans l’admin')}](${message.adminUrl})`,
58
+ ];
59
+ const replyMarkup = renderActions(message.actions);
60
+ return { text: lines.join('\n\n'), ...(replyMarkup === undefined ? {} : { replyMarkup }) };
61
+ }
62
+ // 'report' — "Chiffres clés en tête, détail ensuite, jamais plus d'un
63
+ // écran sans repli."
64
+ const lines = [`*${escapeMarkdownV2(message.title)}*`];
65
+ if (message.keyFigures.length > 0) {
66
+ lines.push(message.keyFigures
67
+ .map((figure) => `*${escapeMarkdownV2(figure.value)}* ${escapeMarkdownV2(figure.label)}`)
68
+ .join(' · '));
69
+ }
70
+ for (const section of message.sections) {
71
+ const heading = section.heading === undefined ? '' : `*${escapeMarkdownV2(section.heading)}*\n`;
72
+ lines.push(`${heading}${escapeMarkdownV2(section.body)}`);
73
+ }
74
+ const footer = message.moreUrl === undefined
75
+ ? ''
76
+ : `\n\n[${escapeMarkdownV2('Voir le détail')}](${message.moreUrl})`;
77
+ const body = lines.join('\n\n');
78
+ // Telegram's real limit applies to `body + footer` together — the footer
79
+ // (the reader's only way to the full detail) must never itself be the
80
+ // part that gets cut off, so the body is what shrinks.
81
+ const budget = TELEGRAM_MAX_TEXT_CHARS - footer.length;
82
+ const truncated = body.length > budget
83
+ ? `${body.slice(0, Math.max(0, budget - TRUNCATION_MARK.length))}${TRUNCATION_MARK}`
84
+ : body;
85
+ return { text: `${truncated}${footer}` };
86
+ }
87
+ //# sourceMappingURL=render.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"render.js","sourceRoot":"","sources":["../../../src/providers/telegram/render.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,40 @@
1
+ import type { ChannelAdapter } from '../../adapter.js';
2
+ import { type WebhookActionLinkOptions } from './render.js';
3
+ export type WebhookFetch = (url: string, init: {
4
+ method: 'POST';
5
+ headers: Record<string, string>;
6
+ body: string;
7
+ }) => Promise<{
8
+ readonly ok: boolean;
9
+ readonly status: number;
10
+ }>;
11
+ export interface WebhookAdapterOptions {
12
+ /** Shared secret signing every outbound request — the receiver's `verifyIncomingWebhook` must use the same value. */
13
+ readonly secret: string;
14
+ /** Required only if messages with actions (e.g. approvals) are ever sent — see `render.ts`. */
15
+ readonly actionLinks?: WebhookActionLinkOptions;
16
+ /** Defaults to the global `fetch`; overridable for tests. */
17
+ readonly fetchImpl?: WebhookFetch;
18
+ }
19
+ /**
20
+ * "Webhook générique signé" (L6 task 11) — the general-purpose "any HTTP
21
+ * endpoint" channel. `target.id` is the receiving URL itself (this is what
22
+ * `ChannelTarget`'s "platform-side identifier" means for a channel whose
23
+ * platform IS arbitrary HTTP): one adapter instance, one shared signing
24
+ * secret, any number of destination URLs.
25
+ *
26
+ * Outbound-only for real command execution: `capabilities.buttons` is
27
+ * `false` (no UI to click, hence the signed-link rendering, same pattern as
28
+ * the email adapter, L6 task 8) and `capabilities.inbound` is `false` —
29
+ * building real inbound COMMAND execution for an arbitrary third-party
30
+ * caller is a materially different, larger undertaking (it would need a
31
+ * real HTTP route wired to a live site, per-integration identity linking
32
+ * decisions, and its own authorization story) than this task's actual,
33
+ * security-critical deliverable: the signing/verification PRIMITIVE
34
+ * (`signing.ts`), which is real, complete, and exercised end-to-end here by
35
+ * `send()`. `verifyIdentity` is an honest refusal, exactly matching the
36
+ * email adapter's precedent, rather than a half-built linking flow with no
37
+ * real inbound transport to carry it.
38
+ */
39
+ export declare function createWebhookAdapter(options: WebhookAdapterOptions): ChannelAdapter;
40
+ //# sourceMappingURL=adapter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"adapter.d.ts","sourceRoot":"","sources":["../../../src/providers/webhook/adapter.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,cAAc,EAKf,MAAM,kBAAkB,CAAA;AACzB,OAAO,EAAwB,KAAK,wBAAwB,EAAE,MAAM,aAAa,CAAA;AASjF,MAAM,MAAM,YAAY,GAAG,CACzB,GAAG,EAAE,MAAM,EACX,IAAI,EAAE;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,KACpE,OAAO,CAAC;IACX,QAAQ,CAAC,EAAE,EAAE,OAAO,CAAA;IACpB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;CACxB,CAAC,CAAA;AAEF,MAAM,WAAW,qBAAqB;IACpC,qHAAqH;IACrH,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,+FAA+F;IAC/F,QAAQ,CAAC,WAAW,CAAC,EAAE,wBAAwB,CAAA;IAC/C,6DAA6D;IAC7D,QAAQ,CAAC,SAAS,CAAC,EAAE,YAAY,CAAA;CAClC;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,qBAAqB,GAAG,cAAc,CAoDnF"}
@@ -0,0 +1,71 @@
1
+ import { CogentaError } from '@cogenta/core';
2
+ import { renderWebhookPayload } from './render.js';
3
+ import { signOutgoingWebhook, WEBHOOK_SIGNATURE_HEADER, WEBHOOK_TIMESTAMP_HEADER, } from './signing.js';
4
+ const CHANNEL_NAME = 'webhook';
5
+ /**
6
+ * "Webhook générique signé" (L6 task 11) — the general-purpose "any HTTP
7
+ * endpoint" channel. `target.id` is the receiving URL itself (this is what
8
+ * `ChannelTarget`'s "platform-side identifier" means for a channel whose
9
+ * platform IS arbitrary HTTP): one adapter instance, one shared signing
10
+ * secret, any number of destination URLs.
11
+ *
12
+ * Outbound-only for real command execution: `capabilities.buttons` is
13
+ * `false` (no UI to click, hence the signed-link rendering, same pattern as
14
+ * the email adapter, L6 task 8) and `capabilities.inbound` is `false` —
15
+ * building real inbound COMMAND execution for an arbitrary third-party
16
+ * caller is a materially different, larger undertaking (it would need a
17
+ * real HTTP route wired to a live site, per-integration identity linking
18
+ * decisions, and its own authorization story) than this task's actual,
19
+ * security-critical deliverable: the signing/verification PRIMITIVE
20
+ * (`signing.ts`), which is real, complete, and exercised end-to-end here by
21
+ * `send()`. `verifyIdentity` is an honest refusal, exactly matching the
22
+ * email adapter's precedent, rather than a half-built linking flow with no
23
+ * real inbound transport to carry it.
24
+ */
25
+ export function createWebhookAdapter(options) {
26
+ const fetchImpl = options.fetchImpl ?? fetch;
27
+ return {
28
+ name: CHANNEL_NAME,
29
+ capabilities: {
30
+ richText: false,
31
+ buttons: false,
32
+ threads: false,
33
+ attachments: false,
34
+ inbound: false,
35
+ },
36
+ async send(target, message) {
37
+ const payload = renderWebhookPayload(message, options.actionLinks);
38
+ const rawBody = JSON.stringify(payload);
39
+ const signed = signOutgoingWebhook(options.secret, rawBody);
40
+ const response = await fetchImpl(target.id, {
41
+ method: 'POST',
42
+ headers: {
43
+ 'content-type': 'application/json',
44
+ [WEBHOOK_TIMESTAMP_HEADER]: signed.timestamp,
45
+ [WEBHOOK_SIGNATURE_HEADER]: signed.signature,
46
+ },
47
+ body: rawBody,
48
+ });
49
+ if (!response.ok) {
50
+ throw new CogentaError({
51
+ code: 'CHANNEL_WEBHOOK_DELIVERY_FAILED',
52
+ message: `Webhook delivery to ${target.id} failed with status ${response.status}.`,
53
+ hint: 'Check that the target URL is reachable and returns a 2xx status for a valid signed request.',
54
+ details: { url: target.id, status: response.status },
55
+ });
56
+ }
57
+ // A generic webhook receiver has no message-id concept of its own to
58
+ // hand back — the timestamp+signature pair is the only real,
59
+ // independently-verifiable identifier this send actually produced.
60
+ return `${signed.timestamp}:${signed.signature}`;
61
+ },
62
+ async verifyIdentity() {
63
+ throw new CogentaError({
64
+ code: 'CHANNEL_WEBHOOK_INBOUND_UNSUPPORTED',
65
+ message: 'The generic webhook adapter does not support identity linking — it is outbound-only.',
66
+ hint: 'Link accounts through a channel with capabilities.inbound === true (e.g. Telegram, Slack, Discord).',
67
+ });
68
+ },
69
+ };
70
+ }
71
+ //# sourceMappingURL=adapter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"adapter.js","sourceRoot":"","sources":["../../../src/providers/webhook/adapter.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAA;AAQ5C,OAAO,EAAE,oBAAoB,EAAiC,MAAM,aAAa,CAAA;AACjF,OAAO,EACL,mBAAmB,EACnB,wBAAwB,EACxB,wBAAwB,GACzB,MAAM,cAAc,CAAA;AAErB,MAAM,YAAY,GAAG,SAAS,CAAA;AAmB9B;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,UAAU,oBAAoB,CAAC,OAA8B;IACjE,MAAM,SAAS,GAAiB,OAAO,CAAC,SAAS,IAAK,KAAiC,CAAA;IAEvF,OAAO;QACL,IAAI,EAAE,YAAY;QAClB,YAAY,EAAE;YACZ,QAAQ,EAAE,KAAK;YACf,OAAO,EAAE,KAAK;YACd,OAAO,EAAE,KAAK;YACd,WAAW,EAAE,KAAK;YAClB,OAAO,EAAE,KAAK;SACf;QAED,KAAK,CAAC,IAAI,CAAC,MAAqB,EAAE,OAAuB;YACvD,MAAM,OAAO,GAAG,oBAAoB,CAAC,OAAO,EAAE,OAAO,CAAC,WAAW,CAAC,CAAA;YAClE,MAAM,OAAO,GAAG,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,CAAA;YACvC,MAAM,MAAM,GAAG,mBAAmB,CAAC,OAAO,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;YAE3D,MAAM,QAAQ,GAAG,MAAM,SAAS,CAAC,MAAM,CAAC,EAAE,EAAE;gBAC1C,MAAM,EAAE,MAAM;gBACd,OAAO,EAAE;oBACP,cAAc,EAAE,kBAAkB;oBAClC,CAAC,wBAAwB,CAAC,EAAE,MAAM,CAAC,SAAS;oBAC5C,CAAC,wBAAwB,CAAC,EAAE,MAAM,CAAC,SAAS;iBAC7C;gBACD,IAAI,EAAE,OAAO;aACd,CAAC,CAAA;YAEF,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;gBACjB,MAAM,IAAI,YAAY,CAAC;oBACrB,IAAI,EAAE,iCAAiC;oBACvC,OAAO,EAAE,uBAAuB,MAAM,CAAC,EAAE,uBAAuB,QAAQ,CAAC,MAAM,GAAG;oBAClF,IAAI,EAAE,6FAA6F;oBACnG,OAAO,EAAE,EAAE,GAAG,EAAE,MAAM,CAAC,EAAE,EAAE,MAAM,EAAE,QAAQ,CAAC,MAAM,EAAE;iBACrD,CAAC,CAAA;YACJ,CAAC;YAED,qEAAqE;YACrE,6DAA6D;YAC7D,mEAAmE;YACnE,OAAO,GAAG,MAAM,CAAC,SAAS,IAAI,MAAM,CAAC,SAAS,EAAE,CAAA;QAClD,CAAC;QAED,KAAK,CAAC,cAAc;YAClB,MAAM,IAAI,YAAY,CAAC;gBACrB,IAAI,EAAE,qCAAqC;gBAC3C,OAAO,EACL,sFAAsF;gBACxF,IAAI,EAAE,qGAAqG;aAC5G,CAAC,CAAA;QACJ,CAAC;KACF,CAAA;AACH,CAAC"}
@@ -0,0 +1,26 @@
1
+ import type { ChannelMessage } from '../../adapter.js';
2
+ import { type EmailActionLinkOptions } from '../email/render.js';
3
+ export interface WebhookActionLinkOptions extends EmailActionLinkOptions {
4
+ }
5
+ export interface WebhookPayload {
6
+ readonly level: ChannelMessage['level'];
7
+ readonly title: string;
8
+ readonly text: string;
9
+ readonly actions?: readonly {
10
+ readonly label: string;
11
+ readonly url: string;
12
+ }[];
13
+ }
14
+ /**
15
+ * A generic webhook receiver has no rendering surface of its own (no
16
+ * Markdown dialect, no Block Kit) — the payload is plain, structured JSON.
17
+ * Actions still can't be buttons ("## Approbations depuis le canal": "Sur un
18
+ * canal sans boutons [...] l'action est un lien signé à usage unique"), so
19
+ * this reuses `email/render.ts`'s real validation (it already throws
20
+ * `CHANNEL_EMAIL_TRANSPORT_ERROR` for an unrenderable action or a missing
21
+ * `actionLinks` config — that check is not duplicated here) for its plain
22
+ * text and only rebuilds the action *links* locally, since the payload shape
23
+ * (label/url pairs) differs from email's HTML anchor rendering.
24
+ */
25
+ export declare function renderWebhookPayload(message: ChannelMessage, actionLinks?: WebhookActionLinkOptions): WebhookPayload;
26
+ //# sourceMappingURL=render.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"render.d.ts","sourceRoot":"","sources":["../../../src/providers/webhook/render.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAiB,cAAc,EAAE,MAAM,kBAAkB,CAAA;AAErE,OAAO,EAAE,KAAK,sBAAsB,EAAsB,MAAM,oBAAoB,CAAA;AAEpF,MAAM,WAAW,wBAAyB,SAAQ,sBAAsB;CAAG;AAE3E,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,KAAK,EAAE,cAAc,CAAC,OAAO,CAAC,CAAA;IACvC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,OAAO,CAAC,EAAE,SAAS;QAAE,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;KAAE,EAAE,CAAA;CAC/E;AAqBD;;;;;;;;;;GAUG;AACH,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,cAAc,EACvB,WAAW,CAAC,EAAE,wBAAwB,GACrC,cAAc,CAgBhB"}
@@ -0,0 +1,41 @@
1
+ import { buildSignedApprovalLink } from '../../approvals/signed-link.js';
2
+ import { renderEmailMessage } from '../email/render.js';
3
+ /**
4
+ * `action.id` is command text (`"approve <token>"`/`"deny <token>"`, the
5
+ * same shape `email/render.ts`'s `actionToLink` already parses) — mirrored
6
+ * here rather than imported, since that helper is private to its own
7
+ * module; both wrap the one real primitive, `buildSignedApprovalLink`.
8
+ */
9
+ function actionToUrl(action, links) {
10
+ const match = /^(approve|deny)\s+(.+)$/.exec(action.id);
11
+ const [, word = 'approve', token = action.id] = match ?? [];
12
+ const decision = word === 'approve' ? 'approved' : 'rejected';
13
+ return buildSignedApprovalLink(links.baseUrl, links.signingKey, token, decision, links.expiresInSeconds);
14
+ }
15
+ /**
16
+ * A generic webhook receiver has no rendering surface of its own (no
17
+ * Markdown dialect, no Block Kit) — the payload is plain, structured JSON.
18
+ * Actions still can't be buttons ("## Approbations depuis le canal": "Sur un
19
+ * canal sans boutons [...] l'action est un lien signé à usage unique"), so
20
+ * this reuses `email/render.ts`'s real validation (it already throws
21
+ * `CHANNEL_EMAIL_TRANSPORT_ERROR` for an unrenderable action or a missing
22
+ * `actionLinks` config — that check is not duplicated here) for its plain
23
+ * text and only rebuilds the action *links* locally, since the payload shape
24
+ * (label/url pairs) differs from email's HTML anchor rendering.
25
+ */
26
+ export function renderWebhookPayload(message, actionLinks) {
27
+ const rendered = renderEmailMessage(message, actionLinks);
28
+ const actions = message.level === 'alert' && message.actions !== undefined && message.actions.length > 0
29
+ ? message.actions.map((action) => ({
30
+ label: action.label,
31
+ url: actionToUrl(action, actionLinks),
32
+ }))
33
+ : undefined;
34
+ return {
35
+ level: message.level,
36
+ title: rendered.subject,
37
+ text: rendered.text,
38
+ ...(actions === undefined ? {} : { actions }),
39
+ };
40
+ }
41
+ //# sourceMappingURL=render.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"render.js","sourceRoot":"","sources":["../../../src/providers/webhook/render.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,uBAAuB,EAAE,MAAM,gCAAgC,CAAA;AACxE,OAAO,EAA+B,kBAAkB,EAAE,MAAM,oBAAoB,CAAA;AAWpF;;;;;GAKG;AACH,SAAS,WAAW,CAAC,MAAqB,EAAE,KAA+B;IACzE,MAAM,KAAK,GAAG,yBAAyB,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,CAAA;IACvD,MAAM,CAAC,EAAE,IAAI,GAAG,SAAS,EAAE,KAAK,GAAG,MAAM,CAAC,EAAE,CAAC,GAAG,KAAK,IAAI,EAAE,CAAA;IAC3D,MAAM,QAAQ,GAAG,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,UAAU,CAAA;IAC7D,OAAO,uBAAuB,CAC5B,KAAK,CAAC,OAAO,EACb,KAAK,CAAC,UAAU,EAChB,KAAK,EACL,QAAQ,EACR,KAAK,CAAC,gBAAgB,CACvB,CAAA;AACH,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,UAAU,oBAAoB,CAClC,OAAuB,EACvB,WAAsC;IAEtC,MAAM,QAAQ,GAAG,kBAAkB,CAAC,OAAO,EAAE,WAAW,CAAC,CAAA;IACzD,MAAM,OAAO,GACX,OAAO,CAAC,KAAK,KAAK,OAAO,IAAI,OAAO,CAAC,OAAO,KAAK,SAAS,IAAI,OAAO,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC;QACtF,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;YAC/B,KAAK,EAAE,MAAM,CAAC,KAAK;YACnB,GAAG,EAAE,WAAW,CAAC,MAAM,EAAE,WAAuC,CAAC;SAClE,CAAC,CAAC;QACL,CAAC,CAAC,SAAS,CAAA;IAEf,OAAO;QACL,KAAK,EAAE,OAAO,CAAC,KAAK;QACpB,KAAK,EAAE,QAAQ,CAAC,OAAO;QACvB,IAAI,EAAE,QAAQ,CAAC,IAAI;QACnB,GAAG,CAAC,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC;KAC9C,CAAA;AACH,CAAC"}
@@ -0,0 +1,59 @@
1
+ export interface SignedWebhookRequest {
2
+ readonly timestamp: string;
3
+ readonly signature: string;
4
+ }
5
+ /** The two headers an outbound signed webhook carries; a receiver names them however it configures its route. */
6
+ export declare const WEBHOOK_TIMESTAMP_HEADER = "X-Cogenta-Timestamp";
7
+ export declare const WEBHOOK_SIGNATURE_HEADER = "X-Cogenta-Signature";
8
+ /** The outbound half: what `createWebhookAdapter`'s `send()` attaches to every request. */
9
+ export declare function signOutgoingWebhook(secret: string, rawBody: string, nowSeconds?: number): SignedWebhookRequest;
10
+ export type WebhookVerificationResult = {
11
+ readonly ok: true;
12
+ } | {
13
+ readonly ok: false;
14
+ readonly reason: 'invalid_signature' | 'expired' | 'replayed';
15
+ };
16
+ /**
17
+ * Bounded, in-memory replay guard — proportionate to the freshness window a
18
+ * verifier already enforces (an entry older than the window can never pass
19
+ * freshness anyway, so nothing needs to be remembered past it). Not a
20
+ * general-purpose dedup store; one instance per verifying process/secret is
21
+ * the expected usage, matching how a route handler would own it.
22
+ */
23
+ export declare class WebhookReplayGuard {
24
+ #private;
25
+ /** Returns `true` if `signature` was already recorded within a still-valid window. */
26
+ hasSeen(signature: string, nowSeconds: number, freshnessWindowSeconds: number): boolean;
27
+ remember(signature: string, nowSeconds: number): void;
28
+ }
29
+ export interface VerifyIncomingWebhookInput {
30
+ readonly headers: SignedWebhookRequest;
31
+ readonly rawBody: string;
32
+ readonly secret: string;
33
+ readonly now?: number;
34
+ readonly freshnessWindowSeconds?: number;
35
+ /** Omit to skip replay checking (e.g. a caller that already dedupes upstream). */
36
+ readonly replayGuard?: WebhookReplayGuard;
37
+ }
38
+ /**
39
+ * Three independent checks, all mandatory, in the order that fails cheapest
40
+ * first: a malformed/non-numeric timestamp or a stale one is rejected before
41
+ * ever computing an HMAC over it, and only a request that is both fresh and
42
+ * authentic is checked for replay (recording a signature for a request that
43
+ * never even had a valid signature would let an attacker exhaust the replay
44
+ * guard with garbage).
45
+ *
46
+ * Unlike `linking/codes.ts`'s deliberately uniform `CHANNEL_LINK_CODE_INVALID`
47
+ * (chosen to deny an attacker an enumeration oracle over *which accounts
48
+ * exist*), this returns a *distinguishable* reason. There is no identity to
49
+ * enumerate here — a webhook secret is either configured correctly or it
50
+ * isn't, and telling an operator debugging a misconfigured integration
51
+ * "your signature doesn't match" versus "your timestamp is stale" versus
52
+ * "this exact request was already processed" is a legitimate operational
53
+ * need, not an attack surface: none of the three reasons reveal anything an
54
+ * attacker couldn't already infer from a generic 401.
55
+ */
56
+ export declare function verifyIncomingWebhook(input: VerifyIncomingWebhookInput): WebhookVerificationResult;
57
+ /** Throws the typed `CogentaError` matching a failed `verifyIncomingWebhook` result; a no-op on success. */
58
+ export declare function assertValidIncomingWebhook(result: WebhookVerificationResult): void;
59
+ //# sourceMappingURL=signing.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"signing.d.ts","sourceRoot":"","sources":["../../../src/providers/webhook/signing.ts"],"names":[],"mappings":"AAmBA,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;IAC1B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;CAC3B;AAED,iHAAiH;AACjH,eAAO,MAAM,wBAAwB,wBAAwB,CAAA;AAC7D,eAAO,MAAM,wBAAwB,wBAAwB,CAAA;AAM7D,2FAA2F;AAC3F,wBAAgB,mBAAmB,CACjC,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,MAAM,EACf,UAAU,GAAE,MAAsC,GACjD,oBAAoB,CAGtB;AAED,MAAM,MAAM,yBAAyB,GACjC;IAAE,QAAQ,CAAC,EAAE,EAAE,IAAI,CAAA;CAAE,GACrB;IAAE,QAAQ,CAAC,EAAE,EAAE,KAAK,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,mBAAmB,GAAG,SAAS,GAAG,UAAU,CAAA;CAAE,CAAA;AAEzF;;;;;;GAMG;AACH,qBAAa,kBAAkB;;IAG7B,sFAAsF;IACtF,OAAO,CAAC,SAAS,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,sBAAsB,EAAE,MAAM,GAAG,OAAO,CAGtF;IAED,QAAQ,CAAC,SAAS,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,IAAI,CAEpD;CAOF;AAED,MAAM,WAAW,0BAA0B;IACzC,QAAQ,CAAC,OAAO,EAAE,oBAAoB,CAAA;IACtC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,sBAAsB,CAAC,EAAE,MAAM,CAAA;IACxC,kFAAkF;IAClF,QAAQ,CAAC,WAAW,CAAC,EAAE,kBAAkB,CAAA;CAC1C;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,qBAAqB,CACnC,KAAK,EAAE,0BAA0B,GAChC,yBAAyB,CAwB3B;AAQD,4GAA4G;AAC5G,wBAAgB,0BAA0B,CAAC,MAAM,EAAE,yBAAyB,GAAG,IAAI,CAQlF"}