@crowdsource.you/core 1.2.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 (339) hide show
  1. package/README.md +571 -0
  2. package/dist/cases.d.ts +66 -0
  3. package/dist/cases.d.ts.map +1 -0
  4. package/dist/cases.js +79 -0
  5. package/dist/cases.js.map +1 -0
  6. package/dist/client.d.ts +90 -0
  7. package/dist/client.d.ts.map +1 -0
  8. package/dist/client.js +125 -0
  9. package/dist/client.js.map +1 -0
  10. package/dist/communityNotes.d.ts +49 -0
  11. package/dist/communityNotes.d.ts.map +1 -0
  12. package/dist/communityNotes.js +104 -0
  13. package/dist/communityNotes.js.map +1 -0
  14. package/dist/credential.d.ts +58 -0
  15. package/dist/credential.d.ts.map +1 -0
  16. package/dist/credential.js +71 -0
  17. package/dist/credential.js.map +1 -0
  18. package/dist/defaults.d.ts +61 -0
  19. package/dist/defaults.d.ts.map +1 -0
  20. package/dist/defaults.js +82 -0
  21. package/dist/defaults.js.map +1 -0
  22. package/dist/digest.d.ts +44 -0
  23. package/dist/digest.d.ts.map +1 -0
  24. package/dist/digest.js +79 -0
  25. package/dist/digest.js.map +1 -0
  26. package/dist/envelope.d.ts +143 -0
  27. package/dist/envelope.d.ts.map +1 -0
  28. package/dist/envelope.js +258 -0
  29. package/dist/envelope.js.map +1 -0
  30. package/dist/errors.d.ts +90 -0
  31. package/dist/errors.d.ts.map +1 -0
  32. package/dist/errors.js +151 -0
  33. package/dist/errors.js.map +1 -0
  34. package/dist/esm/cases.js +74 -0
  35. package/dist/esm/cases.js.map +1 -0
  36. package/dist/esm/client.js +121 -0
  37. package/dist/esm/client.js.map +1 -0
  38. package/dist/esm/communityNotes.js +100 -0
  39. package/dist/esm/communityNotes.js.map +1 -0
  40. package/dist/esm/credential.js +67 -0
  41. package/dist/esm/credential.js.map +1 -0
  42. package/dist/esm/defaults.js +77 -0
  43. package/dist/esm/defaults.js.map +1 -0
  44. package/dist/esm/digest.js +74 -0
  45. package/dist/esm/digest.js.map +1 -0
  46. package/dist/esm/envelope.js +252 -0
  47. package/dist/esm/envelope.js.map +1 -0
  48. package/dist/esm/errors.js +140 -0
  49. package/dist/esm/errors.js.map +1 -0
  50. package/dist/esm/express/index.js +29 -0
  51. package/dist/esm/express/index.js.map +1 -0
  52. package/dist/esm/express/middleware.js +293 -0
  53. package/dist/esm/express/middleware.js.map +1 -0
  54. package/dist/esm/express/store.js +69 -0
  55. package/dist/esm/express/store.js.map +1 -0
  56. package/dist/esm/express/verify.js +133 -0
  57. package/dist/esm/express/verify.js.map +1 -0
  58. package/dist/esm/index.js +57 -0
  59. package/dist/esm/index.js.map +1 -0
  60. package/dist/esm/outbox/client.js +43 -0
  61. package/dist/esm/outbox/client.js.map +1 -0
  62. package/dist/esm/outbox/decision.js +153 -0
  63. package/dist/esm/outbox/decision.js.map +1 -0
  64. package/dist/esm/outbox/delivery.js +129 -0
  65. package/dist/esm/outbox/delivery.js.map +1 -0
  66. package/dist/esm/outbox/enforcement/executor.js +219 -0
  67. package/dist/esm/outbox/enforcement/executor.js.map +1 -0
  68. package/dist/esm/outbox/enforcement/planner.js +326 -0
  69. package/dist/esm/outbox/enforcement/planner.js.map +1 -0
  70. package/dist/esm/outbox/evidence.js +173 -0
  71. package/dist/esm/outbox/evidence.js.map +1 -0
  72. package/dist/esm/outbox/inbound.js +87 -0
  73. package/dist/esm/outbox/inbound.js.map +1 -0
  74. package/dist/esm/outbox/index.js +71 -0
  75. package/dist/esm/outbox/index.js.map +1 -0
  76. package/dist/esm/outbox/intake.js +140 -0
  77. package/dist/esm/outbox/intake.js.map +1 -0
  78. package/dist/esm/outbox/integration.js +116 -0
  79. package/dist/esm/outbox/integration.js.map +1 -0
  80. package/dist/esm/outbox/outbox/dispatcher.js +114 -0
  81. package/dist/esm/outbox/outbox/dispatcher.js.map +1 -0
  82. package/dist/esm/outbox/outbox/service.js +271 -0
  83. package/dist/esm/outbox/outbox/service.js.map +1 -0
  84. package/dist/esm/outbox/postgres/index.js +41 -0
  85. package/dist/esm/outbox/postgres/index.js.map +1 -0
  86. package/dist/esm/outbox/postgres/registries.js +155 -0
  87. package/dist/esm/outbox/postgres/registries.js.map +1 -0
  88. package/dist/esm/outbox/postgres/reportColumns.js +191 -0
  89. package/dist/esm/outbox/postgres/reportColumns.js.map +1 -0
  90. package/dist/esm/outbox/postgres/store/enforcement.js +158 -0
  91. package/dist/esm/outbox/postgres/store/enforcement.js.map +1 -0
  92. package/dist/esm/outbox/postgres/store/events.js +101 -0
  93. package/dist/esm/outbox/postgres/store/events.js.map +1 -0
  94. package/dist/esm/outbox/postgres/store/index.js +59 -0
  95. package/dist/esm/outbox/postgres/store/index.js.map +1 -0
  96. package/dist/esm/outbox/postgres/store/outbox.js +207 -0
  97. package/dist/esm/outbox/postgres/store/outbox.js.map +1 -0
  98. package/dist/esm/outbox/postgres/store/reports.js +231 -0
  99. package/dist/esm/outbox/postgres/store/reports.js.map +1 -0
  100. package/dist/esm/outbox/postgres/store/transaction.js +29 -0
  101. package/dist/esm/outbox/postgres/store/transaction.js.map +1 -0
  102. package/dist/esm/outbox/postgres/tables.js +270 -0
  103. package/dist/esm/outbox/postgres/tables.js.map +1 -0
  104. package/dist/esm/outbox/reconciliation.js +157 -0
  105. package/dist/esm/outbox/reconciliation.js.map +1 -0
  106. package/dist/esm/outbox/reportStatus.js +22 -0
  107. package/dist/esm/outbox/reportStatus.js.map +1 -0
  108. package/dist/esm/outbox/retention.js +33 -0
  109. package/dist/esm/outbox/retention.js.map +1 -0
  110. package/dist/esm/outbox/store/types.js +30 -0
  111. package/dist/esm/outbox/store/types.js.map +1 -0
  112. package/dist/esm/outbox/types.js +23 -0
  113. package/dist/esm/outbox/types.js.map +1 -0
  114. package/dist/esm/outbox/webhook.js +145 -0
  115. package/dist/esm/outbox/webhook.js.map +1 -0
  116. package/dist/esm/package.json +1 -0
  117. package/dist/esm/reports.js +74 -0
  118. package/dist/esm/reports.js.map +1 -0
  119. package/dist/esm/testing/fixtures.js +132 -0
  120. package/dist/esm/testing/fixtures.js.map +1 -0
  121. package/dist/esm/testing/index.js +33 -0
  122. package/dist/esm/testing/index.js.map +1 -0
  123. package/dist/esm/testing/sandbox.js +337 -0
  124. package/dist/esm/testing/sandbox.js.map +1 -0
  125. package/dist/esm/testing/webhook-simulator.js +93 -0
  126. package/dist/esm/testing/webhook-simulator.js.map +1 -0
  127. package/dist/esm/transport.js +177 -0
  128. package/dist/esm/transport.js.map +1 -0
  129. package/dist/esm/webhookEndpoints.js +139 -0
  130. package/dist/esm/webhookEndpoints.js.map +1 -0
  131. package/dist/express/index.d.ts +32 -0
  132. package/dist/express/index.d.ts.map +1 -0
  133. package/dist/express/index.js +40 -0
  134. package/dist/express/index.js.map +1 -0
  135. package/dist/express/middleware.d.ts +97 -0
  136. package/dist/express/middleware.d.ts.map +1 -0
  137. package/dist/express/middleware.js +298 -0
  138. package/dist/express/middleware.js.map +1 -0
  139. package/dist/express/store.d.ts +45 -0
  140. package/dist/express/store.d.ts.map +1 -0
  141. package/dist/express/store.js +72 -0
  142. package/dist/express/store.js.map +1 -0
  143. package/dist/express/verify.d.ts +86 -0
  144. package/dist/express/verify.d.ts.map +1 -0
  145. package/dist/express/verify.js +138 -0
  146. package/dist/express/verify.js.map +1 -0
  147. package/dist/index.d.ts +67 -0
  148. package/dist/index.d.ts.map +1 -0
  149. package/dist/index.js +89 -0
  150. package/dist/index.js.map +1 -0
  151. package/dist/outbox/client.d.ts +35 -0
  152. package/dist/outbox/client.d.ts.map +1 -0
  153. package/dist/outbox/client.js +46 -0
  154. package/dist/outbox/client.js.map +1 -0
  155. package/dist/outbox/decision.d.ts +36 -0
  156. package/dist/outbox/decision.d.ts.map +1 -0
  157. package/dist/outbox/decision.js +159 -0
  158. package/dist/outbox/decision.js.map +1 -0
  159. package/dist/outbox/delivery.d.ts +50 -0
  160. package/dist/outbox/delivery.d.ts.map +1 -0
  161. package/dist/outbox/delivery.js +135 -0
  162. package/dist/outbox/delivery.js.map +1 -0
  163. package/dist/outbox/enforcement/executor.d.ts +26 -0
  164. package/dist/outbox/enforcement/executor.d.ts.map +1 -0
  165. package/dist/outbox/enforcement/executor.js +222 -0
  166. package/dist/outbox/enforcement/executor.js.map +1 -0
  167. package/dist/outbox/enforcement/planner.d.ts +50 -0
  168. package/dist/outbox/enforcement/planner.d.ts.map +1 -0
  169. package/dist/outbox/enforcement/planner.js +333 -0
  170. package/dist/outbox/enforcement/planner.js.map +1 -0
  171. package/dist/outbox/evidence.d.ts +114 -0
  172. package/dist/outbox/evidence.d.ts.map +1 -0
  173. package/dist/outbox/evidence.js +181 -0
  174. package/dist/outbox/evidence.js.map +1 -0
  175. package/dist/outbox/inbound.d.ts +78 -0
  176. package/dist/outbox/inbound.d.ts.map +1 -0
  177. package/dist/outbox/inbound.js +91 -0
  178. package/dist/outbox/inbound.js.map +1 -0
  179. package/dist/outbox/index.d.ts +79 -0
  180. package/dist/outbox/index.d.ts.map +1 -0
  181. package/dist/outbox/index.js +98 -0
  182. package/dist/outbox/index.js.map +1 -0
  183. package/dist/outbox/intake.d.ts +62 -0
  184. package/dist/outbox/intake.d.ts.map +1 -0
  185. package/dist/outbox/intake.js +145 -0
  186. package/dist/outbox/intake.js.map +1 -0
  187. package/dist/outbox/integration.d.ts +62 -0
  188. package/dist/outbox/integration.d.ts.map +1 -0
  189. package/dist/outbox/integration.js +119 -0
  190. package/dist/outbox/integration.js.map +1 -0
  191. package/dist/outbox/outbox/dispatcher.d.ts +26 -0
  192. package/dist/outbox/outbox/dispatcher.d.ts.map +1 -0
  193. package/dist/outbox/outbox/dispatcher.js +119 -0
  194. package/dist/outbox/outbox/dispatcher.js.map +1 -0
  195. package/dist/outbox/outbox/service.d.ts +124 -0
  196. package/dist/outbox/outbox/service.d.ts.map +1 -0
  197. package/dist/outbox/outbox/service.js +279 -0
  198. package/dist/outbox/outbox/service.js.map +1 -0
  199. package/dist/outbox/postgres/index.d.ts +44 -0
  200. package/dist/outbox/postgres/index.d.ts.map +1 -0
  201. package/dist/outbox/postgres/index.js +51 -0
  202. package/dist/outbox/postgres/index.js.map +1 -0
  203. package/dist/outbox/postgres/registries.d.ts +69 -0
  204. package/dist/outbox/postgres/registries.d.ts.map +1 -0
  205. package/dist/outbox/postgres/registries.js +159 -0
  206. package/dist/outbox/postgres/registries.js.map +1 -0
  207. package/dist/outbox/postgres/reportColumns.d.ts +154 -0
  208. package/dist/outbox/postgres/reportColumns.d.ts.map +1 -0
  209. package/dist/outbox/postgres/reportColumns.js +195 -0
  210. package/dist/outbox/postgres/reportColumns.js.map +1 -0
  211. package/dist/outbox/postgres/store/enforcement.d.ts +33 -0
  212. package/dist/outbox/postgres/store/enforcement.d.ts.map +1 -0
  213. package/dist/outbox/postgres/store/enforcement.js +161 -0
  214. package/dist/outbox/postgres/store/enforcement.js.map +1 -0
  215. package/dist/outbox/postgres/store/events.d.ts +46 -0
  216. package/dist/outbox/postgres/store/events.d.ts.map +1 -0
  217. package/dist/outbox/postgres/store/events.js +104 -0
  218. package/dist/outbox/postgres/store/events.js.map +1 -0
  219. package/dist/outbox/postgres/store/index.d.ts +27 -0
  220. package/dist/outbox/postgres/store/index.d.ts.map +1 -0
  221. package/dist/outbox/postgres/store/index.js +62 -0
  222. package/dist/outbox/postgres/store/index.js.map +1 -0
  223. package/dist/outbox/postgres/store/outbox.d.ts +8 -0
  224. package/dist/outbox/postgres/store/outbox.d.ts.map +1 -0
  225. package/dist/outbox/postgres/store/outbox.js +210 -0
  226. package/dist/outbox/postgres/store/outbox.js.map +1 -0
  227. package/dist/outbox/postgres/store/reports.d.ts +69 -0
  228. package/dist/outbox/postgres/store/reports.d.ts.map +1 -0
  229. package/dist/outbox/postgres/store/reports.js +234 -0
  230. package/dist/outbox/postgres/store/reports.js.map +1 -0
  231. package/dist/outbox/postgres/store/transaction.d.ts +49 -0
  232. package/dist/outbox/postgres/store/transaction.d.ts.map +1 -0
  233. package/dist/outbox/postgres/store/transaction.js +32 -0
  234. package/dist/outbox/postgres/store/transaction.js.map +1 -0
  235. package/dist/outbox/postgres/tables.d.ts +737 -0
  236. package/dist/outbox/postgres/tables.d.ts.map +1 -0
  237. package/dist/outbox/postgres/tables.js +273 -0
  238. package/dist/outbox/postgres/tables.js.map +1 -0
  239. package/dist/outbox/reconciliation.d.ts +49 -0
  240. package/dist/outbox/reconciliation.d.ts.map +1 -0
  241. package/dist/outbox/reconciliation.js +162 -0
  242. package/dist/outbox/reconciliation.js.map +1 -0
  243. package/dist/outbox/reportStatus.d.ts +12 -0
  244. package/dist/outbox/reportStatus.d.ts.map +1 -0
  245. package/dist/outbox/reportStatus.js +25 -0
  246. package/dist/outbox/reportStatus.js.map +1 -0
  247. package/dist/outbox/retention.d.ts +33 -0
  248. package/dist/outbox/retention.d.ts.map +1 -0
  249. package/dist/outbox/retention.js +36 -0
  250. package/dist/outbox/retention.js.map +1 -0
  251. package/dist/outbox/store/types.d.ts +466 -0
  252. package/dist/outbox/store/types.d.ts.map +1 -0
  253. package/dist/outbox/store/types.js +31 -0
  254. package/dist/outbox/store/types.js.map +1 -0
  255. package/dist/outbox/types.d.ts +660 -0
  256. package/dist/outbox/types.d.ts.map +1 -0
  257. package/dist/outbox/types.js +24 -0
  258. package/dist/outbox/types.js.map +1 -0
  259. package/dist/outbox/webhook.d.ts +15 -0
  260. package/dist/outbox/webhook.d.ts.map +1 -0
  261. package/dist/outbox/webhook.js +148 -0
  262. package/dist/outbox/webhook.js.map +1 -0
  263. package/dist/reports.d.ts +73 -0
  264. package/dist/reports.d.ts.map +1 -0
  265. package/dist/reports.js +78 -0
  266. package/dist/reports.js.map +1 -0
  267. package/dist/testing/fixtures.d.ts +62 -0
  268. package/dist/testing/fixtures.d.ts.map +1 -0
  269. package/dist/testing/fixtures.js +137 -0
  270. package/dist/testing/fixtures.js.map +1 -0
  271. package/dist/testing/index.d.ts +36 -0
  272. package/dist/testing/index.d.ts.map +1 -0
  273. package/dist/testing/index.js +43 -0
  274. package/dist/testing/index.js.map +1 -0
  275. package/dist/testing/sandbox.d.ts +109 -0
  276. package/dist/testing/sandbox.d.ts.map +1 -0
  277. package/dist/testing/sandbox.js +342 -0
  278. package/dist/testing/sandbox.js.map +1 -0
  279. package/dist/testing/webhook-simulator.d.ts +78 -0
  280. package/dist/testing/webhook-simulator.d.ts.map +1 -0
  281. package/dist/testing/webhook-simulator.js +98 -0
  282. package/dist/testing/webhook-simulator.js.map +1 -0
  283. package/dist/transport.d.ts +59 -0
  284. package/dist/transport.d.ts.map +1 -0
  285. package/dist/transport.js +181 -0
  286. package/dist/transport.js.map +1 -0
  287. package/dist/webhookEndpoints.d.ts +147 -0
  288. package/dist/webhookEndpoints.d.ts.map +1 -0
  289. package/dist/webhookEndpoints.js +143 -0
  290. package/dist/webhookEndpoints.js.map +1 -0
  291. package/package.json +127 -0
  292. package/src/cases.ts +119 -0
  293. package/src/client.ts +202 -0
  294. package/src/communityNotes.ts +194 -0
  295. package/src/credential.ts +100 -0
  296. package/src/defaults.ts +87 -0
  297. package/src/digest.ts +91 -0
  298. package/src/envelope.ts +432 -0
  299. package/src/errors.ts +165 -0
  300. package/src/express/index.ts +43 -0
  301. package/src/express/middleware.ts +387 -0
  302. package/src/express/store.ts +85 -0
  303. package/src/express/verify.ts +166 -0
  304. package/src/index.ts +107 -0
  305. package/src/outbox/client.ts +75 -0
  306. package/src/outbox/decision.ts +209 -0
  307. package/src/outbox/delivery.ts +163 -0
  308. package/src/outbox/enforcement/executor.ts +288 -0
  309. package/src/outbox/enforcement/planner.ts +377 -0
  310. package/src/outbox/evidence.ts +231 -0
  311. package/src/outbox/inbound.ts +140 -0
  312. package/src/outbox/index.ts +160 -0
  313. package/src/outbox/intake.ts +175 -0
  314. package/src/outbox/integration.ts +205 -0
  315. package/src/outbox/outbox/dispatcher.ts +131 -0
  316. package/src/outbox/outbox/service.ts +444 -0
  317. package/src/outbox/postgres/index.ts +59 -0
  318. package/src/outbox/postgres/registries.ts +177 -0
  319. package/src/outbox/postgres/reportColumns.ts +281 -0
  320. package/src/outbox/postgres/store/enforcement.ts +186 -0
  321. package/src/outbox/postgres/store/events.ts +112 -0
  322. package/src/outbox/postgres/store/index.ts +72 -0
  323. package/src/outbox/postgres/store/outbox.ts +273 -0
  324. package/src/outbox/postgres/store/reports.ts +349 -0
  325. package/src/outbox/postgres/store/transaction.ts +64 -0
  326. package/src/outbox/postgres/tables.ts +339 -0
  327. package/src/outbox/reconciliation.ts +199 -0
  328. package/src/outbox/reportStatus.ts +24 -0
  329. package/src/outbox/retention.ts +34 -0
  330. package/src/outbox/store/types.ts +528 -0
  331. package/src/outbox/types.ts +753 -0
  332. package/src/outbox/webhook.ts +165 -0
  333. package/src/reports.ts +138 -0
  334. package/src/testing/fixtures.ts +183 -0
  335. package/src/testing/index.ts +57 -0
  336. package/src/testing/sandbox.ts +469 -0
  337. package/src/testing/webhook-simulator.ts +166 -0
  338. package/src/transport.ts +250 -0
  339. package/src/webhookEndpoints.ts +256 -0
@@ -0,0 +1,293 @@
1
+ /**
2
+ * The webhook receiver.
3
+ *
4
+ * ```ts
5
+ * app.post('/webhooks/crowdsource', crowdsourceWebhooks({
6
+ * on: {
7
+ * 'case.decided': async (event) => { await moderationQueue.add(event.id, event.data); },
8
+ * },
9
+ * }));
10
+ * ```
11
+ *
12
+ * No `express.raw`, no body-parser ordering to get right, no secret to plumb
13
+ * through — the secret comes from the environment and the handler reads the
14
+ * request stream itself. That is not convenience for its own sake. The single
15
+ * most likely way to ship a broken CrowdSource integration is to verify a
16
+ * signature over `JSON.stringify(req.body)`, which succeeds on every test
17
+ * payload a developer writes by hand and fails on the first real delivery whose
18
+ * key order or unicode escaping differs — or, far worse, succeeds on a forged
19
+ * body that happens to re-serialise identically. This middleware makes that
20
+ * unreachable: it verifies the bytes it read, and if something upstream already
21
+ * consumed them it REFUSES rather than reconstructing them.
22
+ *
23
+ * What it guarantees, in order:
24
+ *
25
+ * 1. The signature is checked over the bytes that arrived (§10.8).
26
+ * 2. A timestamp outside the five-minute window is rejected before anything
27
+ * is dispatched, in both directions.
28
+ * 3. An event id already claimed is acknowledged without running the handler
29
+ * again (§10.8), and a handler that throws releases the claim so the
30
+ * retry in §10.9 still works.
31
+ * 4. An event type this integration does not handle — including one that did
32
+ * not exist when it was written — is acknowledged and ignored (§10.11). A
33
+ * new event type never breaks an existing integration.
34
+ * 5. Nothing sensitive is logged. The `onRejected` hook is given the reason
35
+ * and never the body (§10.8's last line).
36
+ */
37
+ import { KnownWebhookEventSchema, WEBHOOK_EVENT_ID_HEADER, WEBHOOK_SIGNATURE_HEADER, WEBHOOK_TIMESTAMP_HEADER, WebhookEventEnvelopeSchema, } from '@crowdsource.you/contracts';
38
+ import { memoryProcessedEventStore } from './store.js';
39
+ import { verifyWebhookDelivery } from './verify.js';
40
+ /** The active signing secret. */
41
+ export const WEBHOOK_SECRET_ENV_VAR = 'CROWDSOURCE_WEBHOOK_SECRET';
42
+ /** The secret being retired, during the overlap §10.8 requires for rotation. */
43
+ export const WEBHOOK_PREVIOUS_SECRET_ENV_VAR = 'CROWDSOURCE_WEBHOOK_SECRET_PREVIOUS';
44
+ /** Matches the backend's own `express.json` limit. */
45
+ const DEFAULT_MAX_BODY_BYTES = 1048576;
46
+ /**
47
+ * Thrown when the request cannot be verified because of how the application is
48
+ * assembled, not because of anything the sender did.
49
+ *
50
+ * Passed to `next()` so it reaches the application's error handler and is
51
+ * impossible to miss. The alternative — falling back to `req.body` — is the bug
52
+ * this whole package exists to prevent.
53
+ */
54
+ export class CrowdSourceWebhookConfigurationError extends Error {
55
+ constructor(message) {
56
+ super(message);
57
+ this.name = 'CrowdSourceWebhookConfigurationError';
58
+ }
59
+ }
60
+ export function crowdsourceWebhooks(options = {}) {
61
+ const store = options.store ?? memoryProcessedEventStore();
62
+ const handlers = options.on ?? {};
63
+ const maxBodyBytes = options.maxBodyBytes ?? DEFAULT_MAX_BODY_BYTES;
64
+ return (request, response, next) => {
65
+ void handleDelivery({
66
+ request,
67
+ response,
68
+ next,
69
+ store,
70
+ handlers,
71
+ maxBodyBytes,
72
+ options,
73
+ });
74
+ };
75
+ }
76
+ async function handleDelivery(context) {
77
+ const { request, response, next, options } = context;
78
+ let rawBody;
79
+ try {
80
+ rawBody = await readRawBody(request, context.maxBodyBytes);
81
+ }
82
+ catch (error) {
83
+ if (error instanceof BodyTooLargeError) {
84
+ options.onRejected?.('payload_too_large');
85
+ response.status(413).json({ received: false, rejection: 'payload_too_large' });
86
+ return;
87
+ }
88
+ next(error);
89
+ return;
90
+ }
91
+ const verification = verifyWebhookDelivery({
92
+ eventIdHeader: request.get(WEBHOOK_EVENT_ID_HEADER),
93
+ timestampHeader: request.get(WEBHOOK_TIMESTAMP_HEADER),
94
+ signatureHeader: request.get(WEBHOOK_SIGNATURE_HEADER),
95
+ rawBody,
96
+ secrets: configuredSecrets(options),
97
+ nowMs: Date.now(),
98
+ ...(options.toleranceSeconds === undefined
99
+ ? {}
100
+ : { toleranceSeconds: options.toleranceSeconds }),
101
+ });
102
+ if (!verification.ok) {
103
+ options.onRejected?.(verification.rejection);
104
+ /**
105
+ * 401, not 400. A refused delivery is an authentication failure from the
106
+ * sender's point of view, and §10.9 keeps retrying 4xx a bounded number of
107
+ * times either way — what matters is that it is never a 2xx, because a 2xx
108
+ * would retire the delivery as successfully processed.
109
+ *
110
+ * `no_secret_configured` is the one case that is OUR fault, and it still
111
+ * answers 401 rather than 500: a receiver with no secret must not tell the
112
+ * world it is running unconfigured, and answering non-2xx keeps the event on
113
+ * the sender's retry schedule until somebody fixes it.
114
+ */
115
+ response.status(401).json({ received: false, rejection: verification.rejection });
116
+ return;
117
+ }
118
+ const parsed = WebhookEventEnvelopeSchema.safeParse(parseJson(rawBody));
119
+ if (!parsed.success) {
120
+ options.onRejected?.('malformed_event');
121
+ response.status(400).json({ received: false, rejection: 'malformed_event' });
122
+ return;
123
+ }
124
+ const envelope = parsed.data;
125
+ /**
126
+ * The signed header and the signed body must name the same event. They are
127
+ * both inside the signature, so a disagreement is not an attack — it is a
128
+ * sender defect — but a receiver that deduped on the header while handling the
129
+ * body would process one event under another's id.
130
+ */
131
+ if (envelope.id !== verification.eventId) {
132
+ options.onRejected?.('event_id_mismatch');
133
+ response.status(400).json({ received: false, rejection: 'event_id_mismatch' });
134
+ return;
135
+ }
136
+ const claimed = await context.store.claim(envelope.id);
137
+ if (!claimed) {
138
+ response.status(200).json({ received: true, eventId: envelope.id, duplicate: true });
139
+ return;
140
+ }
141
+ try {
142
+ const handled = await dispatch(context.handlers, envelope, options.onUnhandled);
143
+ response.status(200).json({ received: true, eventId: envelope.id, duplicate: false, handled });
144
+ }
145
+ catch (error) {
146
+ // The claim goes back so §10.9's retry schedule can deliver it again. Losing
147
+ // a decision because a queue was briefly down is not an acceptable outcome.
148
+ await context.store.release(envelope.id);
149
+ next(error);
150
+ }
151
+ }
152
+ /**
153
+ * Dispatches a verified event, and returns whether anything handled it.
154
+ *
155
+ * The switch is exhaustive over the contract's event types on purpose: adding an
156
+ * event to `WEBHOOK_EVENT_TYPES` stops this file compiling until it is handled
157
+ * here, which is the right failure for the SDK. For an INTEGRATION it is the
158
+ * opposite — the `default` branch means an unrecognised type is acknowledged and
159
+ * ignored, so a newer CrowdSource never breaks an older receiver (§10.11).
160
+ *
161
+ * The payload is only parsed against the known-event schema when a handler is
162
+ * actually registered for that type. An integration handling `case.decided` is
163
+ * therefore unaffected by a later change to what `appeal.created` carries.
164
+ */
165
+ async function dispatch(handlers, envelope, onUnhandled) {
166
+ const known = KnownWebhookEventSchema.safeParse(envelope);
167
+ if (known.success && (await invoke(handlers, known.data)))
168
+ return true;
169
+ if (onUnhandled) {
170
+ await onUnhandled(envelope);
171
+ return true;
172
+ }
173
+ return false;
174
+ }
175
+ /** Calls `handler` with `event` if there is one, and says whether there was. */
176
+ async function call(handler, event) {
177
+ if (handler === undefined)
178
+ return false;
179
+ await handler(event);
180
+ return true;
181
+ }
182
+ /**
183
+ * The exhaustive half of `dispatch`, written out rather than looked up.
184
+ *
185
+ * Indexing the handler map with `event.type` while `event.type` is still a union
186
+ * would need a cast to call the result, and a cast is exactly what must not sit
187
+ * between a signed event and the code that acts on it. Narrowing per case is
188
+ * what makes each call provably well-typed, and it means adding an event to the
189
+ * contract stops this file compiling until it is handled.
190
+ */
191
+ async function invoke(handlers, event) {
192
+ switch (event.type) {
193
+ case 'report.received':
194
+ return await call(handlers['report.received'], event);
195
+ case 'case.created':
196
+ return await call(handlers['case.created'], event);
197
+ case 'case.escalated':
198
+ return await call(handlers['case.escalated'], event);
199
+ case 'case.decided':
200
+ return await call(handlers['case.decided'], event);
201
+ case 'decision.corrected':
202
+ return await call(handlers['decision.corrected'], event);
203
+ case 'appeal.created':
204
+ return await call(handlers['appeal.created'], event);
205
+ case 'appeal.decided':
206
+ return await call(handlers['appeal.decided'], event);
207
+ case 'case.closed':
208
+ return await call(handlers['case.closed'], event);
209
+ case 'community_note.status_changed':
210
+ return await call(handlers['community_note.status_changed'], event);
211
+ default:
212
+ return false;
213
+ }
214
+ }
215
+ function configuredSecrets(options) {
216
+ const active = options.secret ?? process.env[WEBHOOK_SECRET_ENV_VAR];
217
+ const previous = options.previousSecret ?? process.env[WEBHOOK_PREVIOUS_SECRET_ENV_VAR];
218
+ return [active, previous].filter((secret) => typeof secret === 'string' && secret.length > 0);
219
+ }
220
+ function parseJson(rawBody) {
221
+ try {
222
+ return JSON.parse(rawBody.toString('utf8'));
223
+ }
224
+ catch {
225
+ return null;
226
+ }
227
+ }
228
+ class BodyTooLargeError extends Error {
229
+ }
230
+ /**
231
+ * The bytes as they arrived.
232
+ *
233
+ * Three sources, in the order that keeps the guarantee:
234
+ *
235
+ * 1. `req.body` is already a Buffer — `express.raw()` is mounted ahead of this
236
+ * handler, which is correct and supported.
237
+ * 2. `req.rawBody` is a Buffer — the `express.json({ verify })` idiom, also
238
+ * correct.
239
+ * 3. Nothing has touched the request — read the stream.
240
+ *
241
+ * Anything else REFUSES. A parsed `req.body` means a JSON parser ran first and
242
+ * the original bytes are gone; re-serialising them would produce a different
243
+ * value from the one that was signed, and the only way to make that "work"
244
+ * would be to verify a signature over bytes CrowdSource never sent.
245
+ */
246
+ async function readRawBody(request, maxBodyBytes) {
247
+ const carried = Reflect.get(request, 'rawBody');
248
+ if (Buffer.isBuffer(carried))
249
+ return carried;
250
+ const parsedBody = request.body;
251
+ if (Buffer.isBuffer(parsedBody))
252
+ return parsedBody;
253
+ if (parsedBody !== undefined && parsedBody !== null) {
254
+ throw new CrowdSourceWebhookConfigurationError('A body parser ran before the CrowdSource webhook handler, so the signed bytes no longer exist. Mount this handler before express.json(), scope express.json() away from the webhook route, or use express.json({ verify }) to keep the raw body.');
255
+ }
256
+ if (request.readableEnded) {
257
+ throw new CrowdSourceWebhookConfigurationError('The request body was already consumed before the CrowdSource webhook handler ran, so the signed bytes no longer exist.');
258
+ }
259
+ return await new Promise((resolve, reject) => {
260
+ const chunks = [];
261
+ let received = 0;
262
+ let settled = false;
263
+ request.on('data', (chunk) => {
264
+ if (settled)
265
+ return;
266
+ received += chunk.length;
267
+ if (received > maxBodyBytes) {
268
+ settled = true;
269
+ // Paused rather than destroyed: destroying the request takes the socket
270
+ // with it, and the 413 the caller is about to write would never reach
271
+ // the sender — which would turn a clear refusal into a connection reset
272
+ // that a delivery worker can only report as "unknown".
273
+ request.pause();
274
+ reject(new BodyTooLargeError());
275
+ return;
276
+ }
277
+ chunks.push(chunk);
278
+ });
279
+ request.on('end', () => {
280
+ if (settled)
281
+ return;
282
+ settled = true;
283
+ resolve(Buffer.concat(chunks));
284
+ });
285
+ request.on('error', (error) => {
286
+ if (settled)
287
+ return;
288
+ settled = true;
289
+ reject(error);
290
+ });
291
+ });
292
+ }
293
+ //# sourceMappingURL=middleware.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"middleware.js","sourceRoot":"","sources":["../../../src/express/middleware.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,69 @@
1
+ /**
2
+ * Idempotency by event id (§10.8: "store the processed event id").
3
+ *
4
+ * A delivery is retried on the schedule in §10.9 — six times over 24 hours —
5
+ * and a retry can also happen because the receiver's 2xx was lost rather than
6
+ * because it failed. So the same event WILL arrive twice at a healthy receiver,
7
+ * and "did I already do this?" is the only thing standing between that and a
8
+ * piece of content being removed twice or a user being suspended twice.
9
+ *
10
+ * The interface is claim/release rather than a "seen?" check, because the
11
+ * obvious design is wrong in a way that is hard to see. Recording the id BEFORE
12
+ * the handler runs makes a handler failure permanent: every retry is deduped
13
+ * away and the work is lost silently, which is the worst possible outcome for a
14
+ * moderation decision. Recording it AFTER lets two concurrent deliveries of the
15
+ * same event both run. Claiming before and releasing on failure gets both: one
16
+ * in flight, and a failure still retryable.
17
+ */
18
+ /** The retry schedule of §10.9 ends at 24 hours; a claim outlives it. */
19
+ const DEFAULT_TTL_MS = 25 * 60 * 60 * 1000;
20
+ const DEFAULT_MAX_ENTRIES = 10000;
21
+ /**
22
+ * The default store: an in-process map with a TTL.
23
+ *
24
+ * **Per process.** Two instances behind a load balancer each keep their own,
25
+ * so a redelivery that lands on the other instance is NOT deduped. That is
26
+ * usually fine — the handlers a receiver should be writing are idempotent
27
+ * anyway, because §7.6 makes the application responsible for recording what it
28
+ * did about a decision — but an application whose enforcement is not idempotent
29
+ * must pass a shared store (Redis, its own database) instead. Saying so here is
30
+ * the point: a dedupe that silently only works on one instance is exactly the
31
+ * kind of thing that looks correct in staging.
32
+ *
33
+ * There is no timer. Expiry is evaluated on read and the map is pruned on
34
+ * insert, so nothing here keeps a Node event loop alive — a module-level
35
+ * `setInterval` in a library is how a consumer's test run stops exiting.
36
+ */
37
+ export function memoryProcessedEventStore(options = {}) {
38
+ const ttlMs = options.ttlMs ?? DEFAULT_TTL_MS;
39
+ const maxEntries = options.maxEntries ?? DEFAULT_MAX_ENTRIES;
40
+ const claims = new Map();
41
+ const prune = (now) => {
42
+ for (const [eventId, expiresAt] of claims) {
43
+ if (expiresAt <= now)
44
+ claims.delete(eventId);
45
+ }
46
+ // Insertion-ordered, so the oldest claims go first once the cap is hit.
47
+ while (claims.size >= maxEntries) {
48
+ const oldest = claims.keys().next();
49
+ if (oldest.done === true)
50
+ break;
51
+ claims.delete(oldest.value);
52
+ }
53
+ };
54
+ return {
55
+ claim(eventId) {
56
+ const now = Date.now();
57
+ const held = claims.get(eventId);
58
+ if (held !== undefined && held > now)
59
+ return false;
60
+ prune(now);
61
+ claims.set(eventId, now + ttlMs);
62
+ return true;
63
+ },
64
+ release(eventId) {
65
+ claims.delete(eventId);
66
+ },
67
+ };
68
+ }
69
+ //# sourceMappingURL=store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"store.js","sourceRoot":"","sources":["../../../src/express/store.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,yEAAyE;AACzE,MAAM,cAAc,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,IAAK,CAAC;AAE5C,MAAM,mBAAmB,GAAG,KAAM,CAAC;AAcnC;;;;;;;;;;;;;;;GAeG;AACH,MAAM,UAAU,yBAAyB,CACvC,UAA4C,EAAE;IAE9C,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,IAAI,cAAc,CAAC;IAC9C,MAAM,UAAU,GAAG,OAAO,CAAC,UAAU,IAAI,mBAAmB,CAAC;IAC7D,MAAM,MAAM,GAAG,IAAI,GAAG,EAAkB,CAAC;IAEzC,MAAM,KAAK,GAAG,CAAC,GAAW,EAAQ,EAAE;QAClC,KAAK,MAAM,CAAC,OAAO,EAAE,SAAS,CAAC,IAAI,MAAM,EAAE,CAAC;YAC1C,IAAI,SAAS,IAAI,GAAG;gBAAE,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;QAC/C,CAAC;QACD,wEAAwE;QACxE,OAAO,MAAM,CAAC,IAAI,IAAI,UAAU,EAAE,CAAC;YACjC,MAAM,MAAM,GAAG,MAAM,CAAC,IAAI,EAAE,CAAC,IAAI,EAAE,CAAC;YACpC,IAAI,MAAM,CAAC,IAAI,KAAK,IAAI;gBAAE,MAAM;YAChC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QAC9B,CAAC;IACH,CAAC,CAAC;IAEF,OAAO;QACL,KAAK,CAAC,OAAe;YACnB,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;YACvB,MAAM,IAAI,GAAG,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;YACjC,IAAI,IAAI,KAAK,SAAS,IAAI,IAAI,GAAG,GAAG;gBAAE,OAAO,KAAK,CAAC;YAEnD,KAAK,CAAC,GAAG,CAAC,CAAC;YACX,MAAM,CAAC,GAAG,CAAC,OAAO,EAAE,GAAG,GAAG,KAAK,CAAC,CAAC;YACjC,OAAO,IAAI,CAAC;QACd,CAAC;QACD,OAAO,CAAC,OAAe;YACrB,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;QACzB,CAAC;KACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,133 @@
1
+ /**
2
+ * Webhook signature verification (§10.8), as a pure function.
3
+ *
4
+ * Kept free of Express, of I/O and of the clock so that the security-critical
5
+ * decision — do these bytes carry a valid, fresh signature from a secret we
6
+ * hold? — can be attacked directly by a test rather than through a request. A
7
+ * signature check that cannot be made to fail is worse than no check at all, so
8
+ * the corpus in `__tests__/verify.test.ts` is mutation-tested: each deliberately
9
+ * weakened verifier must be CAUGHT by at least one case, and a case that no
10
+ * mutant fails is a case that proves nothing.
11
+ *
12
+ * §10.8 in full, and where each line lands:
13
+ *
14
+ * * `signedPayload = timestamp + "." + rawBody` — `buildWebhookSignedPayload`
15
+ * in the contracts package, so the signer and the verifier cannot disagree
16
+ * about what gets signed. This module works on the BYTES of that value (see
17
+ * `signedPayloadBytes`).
18
+ * * "reject timestamps more than five minutes away" — `TOLERANCE`, applied in
19
+ * BOTH directions. A future timestamp is as much a replay signal as an old
20
+ * one, and only checking the past accepts a signature minted for a clock
21
+ * nobody controls.
22
+ * * "compare signatures in constant time" — `timingSafeEqual` over the decoded
23
+ * digests. Never `===`: it returns on the first differing byte, which is a
24
+ * forgery oracle with enough attempts.
25
+ * * "store the processed event id" — `store.ts`, not here. Freshness and
26
+ * authenticity are decidable from the request; whether it was already
27
+ * handled is not.
28
+ * * "allow two secrets during rotation" — `secrets` is a list, every entry is
29
+ * tried, and the result names WHICH one matched so an operator can see the
30
+ * old secret going quiet before they retire it.
31
+ */
32
+ import { createHmac, timingSafeEqual } from 'node:crypto';
33
+ import { WEBHOOK_SIGNATURE_VERSION, WEBHOOK_TIMESTAMP_TOLERANCE_SECONDS, WebhookSignatureHeaderSchema, WebhookTimestampHeaderSchema, buildWebhookSignedPayload, } from '@crowdsource.you/contracts';
34
+ /**
35
+ * Why a delivery was refused. Terse on purpose: it goes back on the wire, into
36
+ * the sender's delivery log, and into `onRejected`.
37
+ *
38
+ * The first eight are what verification itself decides; the last three are the
39
+ * middleware's. They share one vocabulary so the reason a receiver reports and
40
+ * the reason it answers with are always the same string — two lists would drift
41
+ * the first time one of them grew.
42
+ */
43
+ export const WEBHOOK_REJECTIONS = [
44
+ 'missing_event_id',
45
+ 'missing_timestamp',
46
+ 'missing_signature',
47
+ 'malformed_timestamp',
48
+ 'malformed_signature',
49
+ 'timestamp_out_of_window',
50
+ 'signature_mismatch',
51
+ 'no_secret_configured',
52
+ 'malformed_event',
53
+ 'event_id_mismatch',
54
+ 'payload_too_large',
55
+ ];
56
+ /**
57
+ * The exact bytes signed, without a UTF-8 round trip.
58
+ *
59
+ * §10.8 defines the signed payload as a STRING, and
60
+ * `buildWebhookSignedPayload` is that definition. Decoding the body to a string
61
+ * to rebuild it would map any invalid UTF-8 byte to U+FFFD — which is lossy, so
62
+ * two different bodies could produce the same signed payload. Concatenating the
63
+ * prefix bytes with the body bytes is the same value for every well-formed
64
+ * (UTF-8) JSON body and is not lossy for anything else. `verify.test.ts` asserts
65
+ * the two agree.
66
+ */
67
+ export function signedPayloadBytes(timestamp, rawBody) {
68
+ // The prefix is taken FROM the contract's own function rather than restated
69
+ // as `${timestamp}.`, so a change to the separator follows automatically
70
+ // instead of leaving two definitions to drift.
71
+ const prefix = buildWebhookSignedPayload(timestamp, '');
72
+ return Buffer.concat([Buffer.from(prefix, 'utf8'), rawBody]);
73
+ }
74
+ function digestsMatch(expectedHex, presentedHex) {
75
+ const expected = Buffer.from(expectedHex, 'hex');
76
+ const presented = Buffer.from(presentedHex, 'hex');
77
+ // The header schema fixes the length at 64 hex characters, so this is never
78
+ // the branch that rejects a real delivery — it is what stops `timingSafeEqual`
79
+ // from throwing if that ever stops being true.
80
+ if (expected.length !== presented.length)
81
+ return false;
82
+ return timingSafeEqual(expected, presented);
83
+ }
84
+ /**
85
+ * Verifies one delivery.
86
+ *
87
+ * Order matters: everything cheap and decidable from the headers is checked
88
+ * BEFORE any HMAC is computed, so a flood of malformed deliveries cannot be
89
+ * turned into a CPU cost. The one thing that must not be reordered is the
90
+ * timestamp window, which §10.8 puts before the comparison — a signature that
91
+ * was valid last week is still cryptographically valid, and freshness is the
92
+ * only thing that makes it a replay.
93
+ */
94
+ export function verifyWebhookDelivery(input) {
95
+ if (input.secrets.length === 0)
96
+ return { ok: false, rejection: 'no_secret_configured' };
97
+ const eventId = input.eventIdHeader?.trim();
98
+ if (!eventId)
99
+ return { ok: false, rejection: 'missing_event_id' };
100
+ if (input.timestampHeader === undefined)
101
+ return { ok: false, rejection: 'missing_timestamp' };
102
+ if (input.signatureHeader === undefined)
103
+ return { ok: false, rejection: 'missing_signature' };
104
+ if (!WebhookTimestampHeaderSchema.safeParse(input.timestampHeader).success) {
105
+ return { ok: false, rejection: 'malformed_timestamp' };
106
+ }
107
+ if (!WebhookSignatureHeaderSchema.safeParse(input.signatureHeader).success) {
108
+ return { ok: false, rejection: 'malformed_signature' };
109
+ }
110
+ const tolerance = input.toleranceSeconds ?? WEBHOOK_TIMESTAMP_TOLERANCE_SECONDS;
111
+ const skewSeconds = Math.abs(input.nowMs / 1000 - Number(input.timestampHeader));
112
+ if (skewSeconds > tolerance)
113
+ return { ok: false, rejection: 'timestamp_out_of_window' };
114
+ const presentedHex = input.signatureHeader.slice(`${WEBHOOK_SIGNATURE_VERSION}=`.length);
115
+ const payload = signedPayloadBytes(input.timestampHeader, input.rawBody);
116
+ /**
117
+ * Every secret is tried and the loop does not break early. Breaking would make
118
+ * "the active secret matched" measurably faster than "the previous one did",
119
+ * which tells a caller how far through a rotation the receiver is — small, but
120
+ * free to avoid.
121
+ */
122
+ let matchedIndex = -1;
123
+ input.secrets.forEach((secret, index) => {
124
+ const expected = createHmac('sha256', secret).update(payload).digest('hex');
125
+ if (digestsMatch(expected, presentedHex) && matchedIndex === -1) {
126
+ matchedIndex = index;
127
+ }
128
+ });
129
+ if (matchedIndex === -1)
130
+ return { ok: false, rejection: 'signature_mismatch' };
131
+ return { ok: true, eventId, secretIndex: matchedIndex };
132
+ }
133
+ //# sourceMappingURL=verify.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"verify.js","sourceRoot":"","sources":["../../../src/express/verify.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,57 @@
1
+ /**
2
+ * `@crowdsource.you/core` — the TypeScript client for the CrowdSource moderation
3
+ * API.
4
+ *
5
+ * The smallest integration this package supports, in full:
6
+ *
7
+ * ```ts
8
+ * import { CrowdSource } from '@crowdsource.you/core';
9
+ *
10
+ * const crowdsource = new CrowdSource();
11
+ *
12
+ * await crowdsource.reports.create({
13
+ * externalReportId: report.id,
14
+ * reportedBy: { oxyUserId: session.sub },
15
+ * subject: {
16
+ * externalId: post.id,
17
+ * type: 'social.post',
18
+ * author: { oxyUserId: post.authorId },
19
+ * },
20
+ * content: post.text,
21
+ * allegations: ['harassment.targeted_abuse'],
22
+ * });
23
+ * ```
24
+ *
25
+ * One environment variable (`CROWDSOURCE_SERVICE_KEY`) and the object being
26
+ * reported. The Case Envelope, its resource ids, its digests, its relations, its
27
+ * principal bindings, the identity binding proof, the policy version, the
28
+ * retention terms and the idempotency key are all composed from that — see
29
+ * `envelope.ts` for what each one is derived from and why it cannot be left to
30
+ * the caller.
31
+ *
32
+ * Types come from `@crowdsource.you/contracts`. This package re-exports none
33
+ * of them: an integrator imports the contract from the contract package, so a
34
+ * `Decision` or a `TaxonomyCode` has exactly one definition.
35
+ */
36
+ export { CrowdSource, SERVICE_KEY_ENV_VAR, BASE_URL_ENV_VAR } from './client.js';
37
+ export { formatServiceKey, parseServiceKey } from './credential.js';
38
+ export { COMMUNITY_REVIEW_FORBIDDEN_ALLEGATIONS, DEFAULT_BASE_URL, DEFAULT_POLICY, DEFAULT_RETENTION_DAYS, } from './defaults.js';
39
+ export { canonicalJson, resourceDigest, sha256Digest } from './digest.js';
40
+ /**
41
+ * `composeCaseEnvelope` is deliberately NOT exported.
42
+ *
43
+ * It is the one function in this package that takes an `applicationId`, and it
44
+ * takes it from `Reports`, which got it from the credential. Exporting it would
45
+ * put an `applicationId` parameter on the public surface — which is the thing
46
+ * Appendix F says must not exist, however carefully the doc comment above it
47
+ * were worded. The envelope a report produces is reachable by reading what
48
+ * `reports.create` sends, not by building one yourself.
49
+ */
50
+ export { CrowdSourceReportInputError, defaultIdempotencyKey } from './envelope.js';
51
+ export { CROWDSOURCE_API_ERROR_CODES, CrowdSourceApiError, CrowdSourceConfigurationError, CrowdSourceError, CrowdSourceTransportError, isCrowdSourceApiError, isCrowdSourceApiErrorCode, isCrowdSourceError, } from './errors.js';
52
+ export { Reports } from './reports.js';
53
+ export { Cases, Decisions } from './cases.js';
54
+ export { CommunityNotes } from './communityNotes.js';
55
+ export { WebhookEndpoints } from './webhookEndpoints.js';
56
+ export { DEFAULT_MAX_ATTEMPTS, DEFAULT_TIMEOUT_MS } from './transport.js';
57
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AAEH,OAAO,EAAE,WAAW,EAAE,mBAAmB,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAGjF,OAAO,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAGpE,OAAO,EACL,sCAAsC,EACtC,gBAAgB,EAChB,cAAc,EACd,sBAAsB,GACvB,MAAM,eAAe,CAAC;AAEvB,OAAO,EAAE,aAAa,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAG1E;;;;;;;;;GASG;AACH,OAAO,EAAE,2BAA2B,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAC;AAWnF,OAAO,EACL,2BAA2B,EAC3B,mBAAmB,EACnB,6BAA6B,EAC7B,gBAAgB,EAChB,yBAAyB,EACzB,qBAAqB,EACrB,yBAAyB,EACzB,kBAAkB,GACnB,MAAM,aAAa,CAAC;AAGrB,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAGvC,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAI9C,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAGrD,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAUzD,OAAO,EAAE,oBAAoB,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAC"}
@@ -0,0 +1,43 @@
1
+ import { CrowdSource } from '../index.js';
2
+ export function createClientProvider(input) {
3
+ let client = null;
4
+ let configurationError = null;
5
+ return {
6
+ get() {
7
+ if (!input.config.enabled)
8
+ return undefined;
9
+ if (client)
10
+ return client;
11
+ if (configurationError !== null)
12
+ return undefined;
13
+ const serviceKey = input.config.serviceKey;
14
+ if (!serviceKey) {
15
+ configurationError = 'no CrowdSource service key is configured';
16
+ input.logger.error('[CrowdSource] enabled but not configured', {
17
+ reason: configurationError,
18
+ });
19
+ return undefined;
20
+ }
21
+ try {
22
+ client = new CrowdSource({
23
+ serviceKey,
24
+ ...(input.config.baseUrl === undefined ? {} : { baseUrl: input.config.baseUrl }),
25
+ });
26
+ input.logger.info('[CrowdSource] client ready', {
27
+ applicationId: client.applicationId,
28
+ });
29
+ return client;
30
+ }
31
+ catch (error) {
32
+ // The SDK's configuration errors name which part of the key is wrong and
33
+ // never echo the secret, so the message is safe to log.
34
+ configurationError = error instanceof Error ? error.message : String(error);
35
+ input.logger.error('[CrowdSource] service key rejected', {
36
+ reason: configurationError,
37
+ });
38
+ return undefined;
39
+ }
40
+ },
41
+ };
42
+ }
43
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.js","sourceRoot":"","sources":["../../../src/outbox/client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAgC1C,MAAM,UAAU,oBAAoB,CAAC,KAGpC;IACC,IAAI,MAAM,GAAuB,IAAI,CAAC;IACtC,IAAI,kBAAkB,GAAkB,IAAI,CAAC;IAE7C,OAAO;QACL,GAAG;YACD,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,OAAO;gBAAE,OAAO,SAAS,CAAC;YAC5C,IAAI,MAAM;gBAAE,OAAO,MAAM,CAAC;YAC1B,IAAI,kBAAkB,KAAK,IAAI;gBAAE,OAAO,SAAS,CAAC;YAElD,MAAM,UAAU,GAAG,KAAK,CAAC,MAAM,CAAC,UAAU,CAAC;YAC3C,IAAI,CAAC,UAAU,EAAE,CAAC;gBAChB,kBAAkB,GAAG,0CAA0C,CAAC;gBAChE,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,0CAA0C,EAAE;oBAC7D,MAAM,EAAE,kBAAkB;iBAC3B,CAAC,CAAC;gBACH,OAAO,SAAS,CAAC;YACnB,CAAC;YAED,IAAI,CAAC;gBACH,MAAM,GAAG,IAAI,WAAW,CAAC;oBACvB,UAAU;oBACV,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,KAAK,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;iBACjF,CAAC,CAAC;gBACH,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,4BAA4B,EAAE;oBAC9C,aAAa,EAAE,MAAM,CAAC,aAAa;iBACpC,CAAC,CAAC;gBACH,OAAO,MAAM,CAAC;YAChB,CAAC;YAAC,OAAO,KAAc,EAAE,CAAC;gBACxB,yEAAyE;gBACzE,wDAAwD;gBACxD,kBAAkB,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;gBAC5E,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,oCAAoC,EAAE;oBACvD,MAAM,EAAE,kBAAkB;iBAC3B,CAAC,CAAC;gBACH,OAAO,SAAS,CAAC;YACnB,CAAC;QACH,CAAC;KACF,CAAC;AACJ,CAAC"}