@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,98 @@
1
+ "use strict";
2
+ /**
3
+ * The webhook simulator — the producer side of §10.8.
4
+ *
5
+ * An integrator can point this at their own receiver and watch a real delivery
6
+ * arrive, signed exactly as CrowdSource signs one, without a jury having sat and
7
+ * without anything being enforced against a real user.
8
+ *
9
+ * It deliberately also makes it easy to send a delivery that is WRONG:
10
+ * `expired`, `wrongSecret`, `tampered` and a raw `signature` override are here
11
+ * so an integration test can assert that the receiver REFUSES those. A test
12
+ * suite that only ever sends valid deliveries proves the receiver can say yes.
13
+ * Proving it can say no is the part that matters, and it is the part nobody
14
+ * writes unless the tooling makes it a one-liner.
15
+ *
16
+ * Signing lives here rather than in `@crowdsource.you/core/express` because the two
17
+ * are opposite sides of the contract: the verifier must never import a signer
18
+ * that could be "corrected" until they agree with each other and both disagree
19
+ * with the service. What they share instead is
20
+ * `buildWebhookSignedPayload` from the contracts package, which is the one
21
+ * definition of what gets signed.
22
+ */
23
+ Object.defineProperty(exports, "__esModule", { value: true });
24
+ exports.WebhookSimulator = void 0;
25
+ exports.signWebhookDelivery = signWebhookDelivery;
26
+ const node_crypto_1 = require("node:crypto");
27
+ const contracts_1 = require("@crowdsource.you/contracts");
28
+ function eventIdOf(event) {
29
+ if (typeof event === 'object' && event !== null && 'id' in event) {
30
+ const id = event.id;
31
+ if (typeof id === 'string')
32
+ return id;
33
+ }
34
+ throw new TypeError('A webhook event must carry a string `id` (§10.7).');
35
+ }
36
+ /** Signs one delivery. */
37
+ function signWebhookDelivery(input) {
38
+ const signedBody = JSON.stringify(input.event);
39
+ const timestamp = String(input.timestampSeconds ?? Math.floor(Date.now() / 1000));
40
+ const signature = input.signature ??
41
+ `${contracts_1.WEBHOOK_SIGNATURE_VERSION}=${(0, node_crypto_1.createHmac)('sha256', input.secret)
42
+ .update((0, contracts_1.buildWebhookSignedPayload)(timestamp, signedBody), 'utf8')
43
+ .digest('hex')}`;
44
+ return {
45
+ headers: {
46
+ 'content-type': 'application/json',
47
+ [contracts_1.WEBHOOK_EVENT_ID_HEADER]: input.eventId ?? eventIdOf(input.event),
48
+ [contracts_1.WEBHOOK_TIMESTAMP_HEADER]: timestamp,
49
+ [contracts_1.WEBHOOK_SIGNATURE_HEADER]: signature,
50
+ },
51
+ // The tampered body is sent WITHOUT re-signing, which is the whole point:
52
+ // the signature above covers `signedBody` and the receiver is handed
53
+ // something else.
54
+ body: input.tamperedBody ?? signedBody,
55
+ };
56
+ }
57
+ /** Comfortably outside the five-minute window, in either direction. */
58
+ const EXPIRED_SKEW_SECONDS = 3600;
59
+ class WebhookSimulator {
60
+ constructor(options) {
61
+ this.options = options;
62
+ }
63
+ /** Signs a delivery without sending it. */
64
+ sign(event, overrides = {}) {
65
+ return signWebhookDelivery({
66
+ secret: overrides.wrongSecret ?? this.options.secret,
67
+ event,
68
+ timestampSeconds: overrides.timestampSeconds ??
69
+ Math.floor(Date.now() / 1000) - (overrides.expired === true ? EXPIRED_SKEW_SECONDS : 0),
70
+ ...(overrides.tamperedBody === undefined ? {} : { tamperedBody: overrides.tamperedBody }),
71
+ ...(overrides.signature === undefined ? {} : { signature: overrides.signature }),
72
+ ...(overrides.eventId === undefined ? {} : { eventId: overrides.eventId }),
73
+ });
74
+ }
75
+ /** Signs and POSTs a delivery to the receiver. */
76
+ async deliver(event, overrides = {}) {
77
+ const delivery = this.sign(event, overrides);
78
+ const fetchImpl = this.options.fetch ?? globalThis.fetch;
79
+ const response = await fetchImpl(this.options.url, {
80
+ method: 'POST',
81
+ headers: delivery.headers,
82
+ body: delivery.body,
83
+ });
84
+ const text = await response.text();
85
+ let body = null;
86
+ if (text.length > 0) {
87
+ try {
88
+ body = JSON.parse(text);
89
+ }
90
+ catch {
91
+ body = text;
92
+ }
93
+ }
94
+ return { status: response.status, body };
95
+ }
96
+ }
97
+ exports.WebhookSimulator = WebhookSimulator;
98
+ //# sourceMappingURL=webhook-simulator.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"webhook-simulator.js","sourceRoot":"","sources":["../../src/testing/webhook-simulator.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,59 @@
1
+ /**
2
+ * The HTTP transport: one authenticated call, and what happens when it fails.
3
+ *
4
+ * Two things here are worth reading before changing anything.
5
+ *
6
+ * **Retries do not replace the integrator's outbox.** §7.1 is explicit that the
7
+ * durable retry path belongs to the application: it stores the report and an
8
+ * outbox row in one operation, and a worker re-delivers until CrowdSource
9
+ * accepts. The retries below only smooth over a blip inside a single delivery
10
+ * attempt — they are bounded, they only fire for the statuses §10.5 marks as
11
+ * "come back later", and when they give up the error they throw still says
12
+ * `retryable: true` so the outbox keeps its job.
13
+ *
14
+ * **A retry is only safe because the write is idempotent.** Every write this
15
+ * client makes carries an `Idempotency-Key` (Appendix D), so a request that was
16
+ * received but whose response was lost returns the same `reportId` on the next
17
+ * attempt rather than creating a second report. A write without one must not be
18
+ * retried, and `request` refuses to retry a mutation that has no key rather than
19
+ * trusting the caller to have thought about it.
20
+ */
21
+ export type FetchLike = typeof globalThis.fetch;
22
+ /** How long a single attempt may take before it is abandoned. */
23
+ export declare const DEFAULT_TIMEOUT_MS = 10000;
24
+ /** Attempts per call, including the first. */
25
+ export declare const DEFAULT_MAX_ATTEMPTS = 3;
26
+ export interface TransportConfig {
27
+ readonly baseUrl: string;
28
+ /**
29
+ * What goes in `Authorization`, resolved per attempt.
30
+ *
31
+ * A function and not a string because a first-party service authenticates with
32
+ * an Oxy service token that EXPIRES (oxy ADR 0026) — a value captured once at
33
+ * construction would work for an hour and then fail forever. A CrowdSource
34
+ * service key never changes, so its provider simply returns the same string.
35
+ */
36
+ readonly bearerToken: () => string | Promise<string>;
37
+ readonly timeoutMs: number;
38
+ readonly maxAttempts: number;
39
+ readonly fetch: FetchLike;
40
+ }
41
+ export interface TransportRequest {
42
+ readonly method: 'GET' | 'POST';
43
+ /** Absolute path from the service root, e.g. `/v1/reports`. */
44
+ readonly path: string;
45
+ readonly body?: unknown;
46
+ /** Appendix D. Required for every mutation; a mutation without one is a defect. */
47
+ readonly idempotencyKey?: string;
48
+ readonly signal?: AbortSignal;
49
+ }
50
+ export declare class Transport {
51
+ private readonly config;
52
+ constructor(config: TransportConfig);
53
+ request<T>(request: TransportRequest): Promise<T>;
54
+ private attempt;
55
+ /** The caller's cancellation and this attempt's timeout, as one signal. */
56
+ private attemptSignal;
57
+ private readJson;
58
+ }
59
+ //# sourceMappingURL=transport.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"transport.d.ts","sourceRoot":"","sources":["../src/transport.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AASH,MAAM,MAAM,SAAS,GAAG,OAAO,UAAU,CAAC,KAAK,CAAC;AAEhD,iEAAiE;AACjE,eAAO,MAAM,kBAAkB,QAAS,CAAC;AAEzC,8CAA8C;AAC9C,eAAO,MAAM,oBAAoB,IAAI,CAAC;AAQtC,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB;;;;;;;OAOG;IACH,QAAQ,CAAC,WAAW,EAAE,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IACrD,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,KAAK,EAAE,SAAS,CAAC;CAC3B;AAED,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,MAAM,EAAE,KAAK,GAAG,MAAM,CAAC;IAChC,+DAA+D;IAC/D,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,CAAC,EAAE,OAAO,CAAC;IACxB,mFAAmF;IACnF,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAC;IACjC,QAAQ,CAAC,MAAM,CAAC,EAAE,WAAW,CAAC;CAC/B;AAgED,qBAAa,SAAS;IACpB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAkB;gBAE7B,MAAM,EAAE,eAAe;IAI7B,OAAO,CAAC,CAAC,EAAE,OAAO,EAAE,gBAAgB,GAAG,OAAO,CAAC,CAAC,CAAC;YA4BzC,OAAO;IAkErB,2EAA2E;IAC3E,OAAO,CAAC,aAAa;YAKP,QAAQ;CAYvB"}
@@ -0,0 +1,181 @@
1
+ "use strict";
2
+ /**
3
+ * The HTTP transport: one authenticated call, and what happens when it fails.
4
+ *
5
+ * Two things here are worth reading before changing anything.
6
+ *
7
+ * **Retries do not replace the integrator's outbox.** §7.1 is explicit that the
8
+ * durable retry path belongs to the application: it stores the report and an
9
+ * outbox row in one operation, and a worker re-delivers until CrowdSource
10
+ * accepts. The retries below only smooth over a blip inside a single delivery
11
+ * attempt — they are bounded, they only fire for the statuses §10.5 marks as
12
+ * "come back later", and when they give up the error they throw still says
13
+ * `retryable: true` so the outbox keeps its job.
14
+ *
15
+ * **A retry is only safe because the write is idempotent.** Every write this
16
+ * client makes carries an `Idempotency-Key` (Appendix D), so a request that was
17
+ * received but whose response was lost returns the same `reportId` on the next
18
+ * attempt rather than creating a second report. A write without one must not be
19
+ * retried, and `request` refuses to retry a mutation that has no key rather than
20
+ * trusting the caller to have thought about it.
21
+ */
22
+ Object.defineProperty(exports, "__esModule", { value: true });
23
+ exports.Transport = exports.DEFAULT_MAX_ATTEMPTS = exports.DEFAULT_TIMEOUT_MS = void 0;
24
+ const errors_js_1 = require("./errors.js");
25
+ /** How long a single attempt may take before it is abandoned. */
26
+ exports.DEFAULT_TIMEOUT_MS = 10000;
27
+ /** Attempts per call, including the first. */
28
+ exports.DEFAULT_MAX_ATTEMPTS = 3;
29
+ /** The base of the exponential backoff between attempts. */
30
+ const RETRY_BASE_DELAY_MS = 250;
31
+ /** A cap so a `Retry-After` of an hour cannot hang a delivery worker. */
32
+ const RETRY_DELAY_CEILING_MS = 5000;
33
+ function parsedErrorBody(payload) {
34
+ if (typeof payload !== 'object' || payload === null || !('error' in payload))
35
+ return null;
36
+ const error = payload.error;
37
+ if (typeof error !== 'object' || error === null)
38
+ return null;
39
+ const record = { ...error };
40
+ if (typeof record.code !== 'string' || typeof record.message !== 'string')
41
+ return null;
42
+ const details = typeof record.details === 'object' && record.details !== null
43
+ ? record.details
44
+ : undefined;
45
+ return { code: record.code, message: record.message, details };
46
+ }
47
+ /** `Retry-After` in either of its two legal forms, in milliseconds. */
48
+ function retryAfterMs(header) {
49
+ if (header === null)
50
+ return null;
51
+ const seconds = Number(header);
52
+ if (Number.isFinite(seconds) && seconds >= 0)
53
+ return seconds * 1000;
54
+ const date = Date.parse(header);
55
+ if (Number.isNaN(date))
56
+ return null;
57
+ return Math.max(0, date - Date.now());
58
+ }
59
+ /**
60
+ * Full jitter over an exponential base. Without jitter, every worker that hit
61
+ * the same 503 comes back at the same millisecond and reproduces it.
62
+ */
63
+ function backoffMs(attempt) {
64
+ const ceiling = Math.min(RETRY_BASE_DELAY_MS * 2 ** (attempt - 1), RETRY_DELAY_CEILING_MS);
65
+ return Math.random() * ceiling;
66
+ }
67
+ function delay(ms, signal) {
68
+ return new Promise((resolve, reject) => {
69
+ const timer = setTimeout(() => {
70
+ signal?.removeEventListener('abort', onAbort);
71
+ resolve();
72
+ }, ms);
73
+ const onAbort = () => {
74
+ clearTimeout(timer);
75
+ reject(new errors_js_1.CrowdSourceTransportError('The request was aborted.', { retryable: false }));
76
+ };
77
+ if (signal?.aborted === true) {
78
+ onAbort();
79
+ return;
80
+ }
81
+ signal?.addEventListener('abort', onAbort, { once: true });
82
+ });
83
+ }
84
+ class Transport {
85
+ constructor(config) {
86
+ this.config = config;
87
+ }
88
+ async request(request) {
89
+ if (request.method !== 'GET' && request.idempotencyKey === undefined) {
90
+ throw new Error(`A ${request.method} to ${request.path} was made without an idempotency key. Appendix D requires one on every write.`);
91
+ }
92
+ let lastError = null;
93
+ for (let attempt = 1; attempt <= this.config.maxAttempts; attempt += 1) {
94
+ const outcome = await this.attempt(request);
95
+ if (outcome.ok)
96
+ return outcome.value;
97
+ lastError = outcome.error;
98
+ if (!outcome.error.retryable || attempt === this.config.maxAttempts)
99
+ break;
100
+ await delay(outcome.retryAfterMs ?? backoffMs(attempt), request.signal);
101
+ }
102
+ // Unreachable with `maxAttempts >= 1`; the loop either returns or assigns.
103
+ if (lastError === null) {
104
+ throw new errors_js_1.CrowdSourceTransportError('The request was never attempted.', {
105
+ retryable: false,
106
+ });
107
+ }
108
+ throw lastError;
109
+ }
110
+ async attempt(request) {
111
+ const headers = {
112
+ accept: 'application/json',
113
+ // Resolved per ATTEMPT, not per call: a retry that outlives a short-lived
114
+ // token must carry the new one, and this is the only place that knows an
115
+ // attempt is starting.
116
+ authorization: `Bearer ${await this.config.bearerToken()}`,
117
+ };
118
+ if (request.body !== undefined)
119
+ headers['content-type'] = 'application/json';
120
+ if (request.idempotencyKey !== undefined) {
121
+ headers['idempotency-key'] = request.idempotencyKey;
122
+ }
123
+ let response;
124
+ try {
125
+ response = await this.config.fetch(`${this.config.baseUrl}${request.path}`, {
126
+ method: request.method,
127
+ headers,
128
+ body: request.body === undefined ? undefined : JSON.stringify(request.body),
129
+ signal: this.attemptSignal(request.signal),
130
+ });
131
+ }
132
+ catch (cause) {
133
+ const callerAborted = request.signal?.aborted === true;
134
+ return {
135
+ ok: false,
136
+ error: new errors_js_1.CrowdSourceTransportError(callerAborted
137
+ ? 'The request was aborted by the caller.'
138
+ : `The request to ${request.path} did not complete.`, { cause, retryable: !callerAborted }),
139
+ };
140
+ }
141
+ if (response.ok) {
142
+ return { ok: true, value: (await this.readJson(response)) };
143
+ }
144
+ const payload = await this.readJson(response).catch(() => null);
145
+ const body = parsedErrorBody(payload);
146
+ const code = body !== null && (0, errors_js_1.isCrowdSourceApiErrorCode)(body.code)
147
+ ? body.code
148
+ : (0, errors_js_1.apiErrorCodeForStatus)(response.status);
149
+ return {
150
+ ok: false,
151
+ error: new errors_js_1.CrowdSourceApiError({
152
+ status: response.status,
153
+ code,
154
+ message: body?.message ?? `CrowdSource answered ${response.status} for ${request.path}.`,
155
+ details: body?.details,
156
+ }),
157
+ retryAfterMs: retryAfterMs(response.headers.get('retry-after')) ?? undefined,
158
+ };
159
+ }
160
+ /** The caller's cancellation and this attempt's timeout, as one signal. */
161
+ attemptSignal(callerSignal) {
162
+ const timeout = AbortSignal.timeout(this.config.timeoutMs);
163
+ return callerSignal === undefined ? timeout : AbortSignal.any([callerSignal, timeout]);
164
+ }
165
+ async readJson(response) {
166
+ const text = await response.text();
167
+ if (text.length === 0)
168
+ return null;
169
+ try {
170
+ return JSON.parse(text);
171
+ }
172
+ catch (cause) {
173
+ throw new errors_js_1.CrowdSourceTransportError('CrowdSource answered with a body that is not JSON.', {
174
+ cause,
175
+ retryable: false,
176
+ });
177
+ }
178
+ }
179
+ }
180
+ exports.Transport = Transport;
181
+ //# sourceMappingURL=transport.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"transport.js","sourceRoot":"","sources":["../src/transport.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,147 @@
1
+ /**
2
+ * Telling CrowdSource where to deliver decisions, and holding the secret it
3
+ * mints once.
4
+ *
5
+ * This is the half of the webhook integration that used to have no client at
6
+ * all: `@crowdsource.you/core/express` verifies deliveries against
7
+ * `CROWDSOURCE_WEBHOOK_SECRET`, and there was no supported way to obtain that
8
+ * value except hand-rolling an HTTP call with the bearer half of the service
9
+ * key. "Near-zero configuration" cannot mean an integrator writes their own
10
+ * fetch for a mandatory step, so §10.2's two management routes are here.
11
+ *
12
+ * Two properties of the secret shape everything below, and both are the server's
13
+ * design rather than this client's choice:
14
+ *
15
+ * * **It is returned once and never again.** A webhook secret is decryptable
16
+ * by CrowdSource because deliveries have to be signed with it, but re-serving
17
+ * it would turn any credential holding `webhooks:manage` into a way to read
18
+ * the signing key of an endpoint somebody else configured. So `secret` on a
19
+ * registration is the only time that value exists outside the service.
20
+ * * **Re-registering an existing URL mints nothing.** That is what makes a
21
+ * deploy script safe to run on every boot — it will not invalidate the
22
+ * secret the running process is verifying with. The corollary is that
23
+ * re-registering cannot RECOVER a secret you failed to store; that is what
24
+ * `rotateSecret` is for.
25
+ *
26
+ * Which is why `secret` is optional on the result. Its presence is not a detail:
27
+ * it means a value has just come into existence that nothing can produce again,
28
+ * and the integration is responsible for persisting it before the process exits.
29
+ */
30
+ import type { Transport } from './transport.js';
31
+ /**
32
+ * A signing secret, at the one moment it is visible.
33
+ *
34
+ * `signingStartsAt` is what makes a rotation overlap followable rather than
35
+ * guesswork: deliveries begin carrying this signature at that instant, so
36
+ * "install both, retire the old one after this time" is a procedure instead of
37
+ * an estimate.
38
+ */
39
+ export interface WebhookSecret {
40
+ readonly version: number;
41
+ /** The value to configure as `CROWDSOURCE_WEBHOOK_SECRET`. Shown once. */
42
+ readonly value: string;
43
+ /** ISO-8601 UTC. When deliveries start being signed with this version. */
44
+ readonly signingStartsAt: string;
45
+ }
46
+ /** A registered endpoint, and the secret if this call is what created it. */
47
+ export interface WebhookEndpoint {
48
+ readonly webhookEndpointId: string;
49
+ readonly url: string;
50
+ readonly eventTypes: readonly string[];
51
+ /**
52
+ * Kept open deliberately. §10.11 requires a newer server not to break an older
53
+ * client, and a status added later is exactly that case.
54
+ */
55
+ readonly status: string;
56
+ readonly disabledReason: string | null;
57
+ /** ISO-8601 UTC, as the API sends it. */
58
+ readonly createdAt: string;
59
+ readonly updatedAt: string;
60
+ /**
61
+ * Present ONLY when this call minted one — a newly created endpoint, or one
62
+ * whose subscription revived it. Absent when an existing URL was updated,
63
+ * because an update deliberately leaves the running secret alone.
64
+ *
65
+ * When it is present, store it before doing anything else. Nothing can return
66
+ * it again and `rotateSecret` is the only way to get a working secret back.
67
+ */
68
+ readonly secret?: WebhookSecret;
69
+ }
70
+ /** The outcome of a rotation: the new secret, and when the old one dies. */
71
+ export interface RotatedWebhookSecret {
72
+ readonly webhookEndpointId: string;
73
+ readonly secret: WebhookSecret;
74
+ /**
75
+ * The secret being retired, and the instant it stops being accepted — set it
76
+ * as `CROWDSOURCE_WEBHOOK_SECRET_PREVIOUS` until then and a rotation drops no
77
+ * delivery. `null` when the cutover was immediate, which is what a leaked
78
+ * secret needs.
79
+ */
80
+ readonly previousSecret: {
81
+ readonly version: number;
82
+ readonly expiresAt: string;
83
+ } | null;
84
+ }
85
+ export interface RegisterWebhookEndpointInput {
86
+ /** Where CrowdSource should POST. HTTPS in production. */
87
+ readonly url: string;
88
+ /**
89
+ * The event types to subscribe to. Import `WEBHOOK_EVENT_TYPES` from
90
+ * `@crowdsource.you/contracts` for the current set; unknown types are
91
+ * accepted so a newer contract does not need a new client.
92
+ */
93
+ readonly eventTypes: readonly string[];
94
+ }
95
+ export interface RotateSecretOptions {
96
+ /**
97
+ * How long the outgoing secret keeps being accepted. `0` is an immediate
98
+ * cutover — the right choice for a leaked secret and the wrong one for routine
99
+ * hygiene, because in-flight deliveries signed with the old secret are refused.
100
+ * Omitted, the server applies its own default overlap.
101
+ */
102
+ readonly overlapSeconds?: number;
103
+ /**
104
+ * Appendix D. Defaults to a key derived from the endpoint id, which makes a
105
+ * RETRY of one rotation safe. Two deliberate rotations are two different
106
+ * operations, so pass a distinct key for the second one.
107
+ */
108
+ readonly idempotencyKey?: string;
109
+ readonly signal?: AbortSignal;
110
+ }
111
+ export interface WebhookEndpointRequestOptions {
112
+ /** Appendix D. Defaults to a key derived from the URL being registered. */
113
+ readonly idempotencyKey?: string;
114
+ readonly signal?: AbortSignal;
115
+ }
116
+ /**
117
+ * §10.2's two webhook-management routes.
118
+ *
119
+ * There is no list, no read-back and no delete, because the API serves none —
120
+ * §10.2 defines exactly two routes. An integrator therefore cannot enumerate
121
+ * what it has registered, which is a real gap in the service rather than
122
+ * something this client can paper over; re-registering a URL is the documented
123
+ * way to change a subscription or revive a disabled endpoint.
124
+ *
125
+ * Both routes need a credential with the `crowdsource:webhooks:manage` scope,
126
+ * and both derive the application from that credential. There is no
127
+ * `applicationId` in any request or response here.
128
+ */
129
+ export declare class WebhookEndpoints {
130
+ private readonly transport;
131
+ constructor(transport: Transport);
132
+ /**
133
+ * Registers, or updates, the endpoint CrowdSource delivers to.
134
+ *
135
+ * Safe to call on every deploy: an existing URL is updated and no new secret
136
+ * is minted, so the secret the running process verifies with keeps working.
137
+ */
138
+ register(input: RegisterWebhookEndpointInput, options?: WebhookEndpointRequestOptions): Promise<WebhookEndpoint>;
139
+ /**
140
+ * Mints a new signing secret, retiring the current one after an overlap.
141
+ *
142
+ * The only way to recover from a secret that was never stored, and the correct
143
+ * response to one that leaked.
144
+ */
145
+ rotateSecret(webhookEndpointId: string, options?: RotateSecretOptions): Promise<RotatedWebhookSecret>;
146
+ }
147
+ //# sourceMappingURL=webhookEndpoints.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"webhookEndpoints.d.ts","sourceRoot":"","sources":["../src/webhookEndpoints.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAMH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAEhD;;;;;;;GAOG;AACH,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,0EAA0E;IAC1E,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,0EAA0E;IAC1E,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;CAClC;AAED,6EAA6E;AAC7E,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,iBAAiB,EAAE,MAAM,CAAC;IACnC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,UAAU,EAAE,SAAS,MAAM,EAAE,CAAC;IACvC;;;OAGG;IACH,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IACvC,yCAAyC;IACzC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B;;;;;;;OAOG;IACH,QAAQ,CAAC,MAAM,CAAC,EAAE,aAAa,CAAC;CACjC;AAED,4EAA4E;AAC5E,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,iBAAiB,EAAE,MAAM,CAAC;IACnC,QAAQ,CAAC,MAAM,EAAE,aAAa,CAAC;IAC/B;;;;;OAKG;IACH,QAAQ,CAAC,cAAc,EAAE;QAAE,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;CAC1F;AAED,MAAM,WAAW,4BAA4B;IAC3C,0DAA0D;IAC1D,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB;;;;OAIG;IACH,QAAQ,CAAC,UAAU,EAAE,SAAS,MAAM,EAAE,CAAC;CACxC;AAmBD,MAAM,WAAW,mBAAmB;IAClC;;;;;OAKG;IACH,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAC;IACjC;;;;OAIG;IACH,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAC;IACjC,QAAQ,CAAC,MAAM,CAAC,EAAE,WAAW,CAAC;CAC/B;AAED,MAAM,WAAW,6BAA6B;IAC5C,2EAA2E;IAC3E,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAC;IACjC,QAAQ,CAAC,MAAM,CAAC,EAAE,WAAW,CAAC;CAC/B;AA2BD;;;;;;;;;;;;GAYG;AACH,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAY;gBAE1B,SAAS,EAAE,SAAS;IAIhC;;;;;OAKG;IACG,QAAQ,CACZ,KAAK,EAAE,4BAA4B,EACnC,OAAO,GAAE,6BAAkC,GAC1C,OAAO,CAAC,eAAe,CAAC;IAyB3B;;;;;OAKG;IACG,YAAY,CAChB,iBAAiB,EAAE,MAAM,EACzB,OAAO,GAAE,mBAAwB,GAChC,OAAO,CAAC,oBAAoB,CAAC;CA0BjC"}
@@ -0,0 +1,143 @@
1
+ "use strict";
2
+ /**
3
+ * Telling CrowdSource where to deliver decisions, and holding the secret it
4
+ * mints once.
5
+ *
6
+ * This is the half of the webhook integration that used to have no client at
7
+ * all: `@crowdsource.you/core/express` verifies deliveries against
8
+ * `CROWDSOURCE_WEBHOOK_SECRET`, and there was no supported way to obtain that
9
+ * value except hand-rolling an HTTP call with the bearer half of the service
10
+ * key. "Near-zero configuration" cannot mean an integrator writes their own
11
+ * fetch for a mandatory step, so §10.2's two management routes are here.
12
+ *
13
+ * Two properties of the secret shape everything below, and both are the server's
14
+ * design rather than this client's choice:
15
+ *
16
+ * * **It is returned once and never again.** A webhook secret is decryptable
17
+ * by CrowdSource because deliveries have to be signed with it, but re-serving
18
+ * it would turn any credential holding `webhooks:manage` into a way to read
19
+ * the signing key of an endpoint somebody else configured. So `secret` on a
20
+ * registration is the only time that value exists outside the service.
21
+ * * **Re-registering an existing URL mints nothing.** That is what makes a
22
+ * deploy script safe to run on every boot — it will not invalidate the
23
+ * secret the running process is verifying with. The corollary is that
24
+ * re-registering cannot RECOVER a secret you failed to store; that is what
25
+ * `rotateSecret` is for.
26
+ *
27
+ * Which is why `secret` is optional on the result. Its presence is not a detail:
28
+ * it means a value has just come into existence that nothing can produce again,
29
+ * and the integration is responsible for persisting it before the process exits.
30
+ */
31
+ Object.defineProperty(exports, "__esModule", { value: true });
32
+ exports.WebhookEndpoints = void 0;
33
+ const zod_1 = require("zod");
34
+ const digest_js_1 = require("./digest.js");
35
+ const errors_js_1 = require("./errors.js");
36
+ /**
37
+ * Appendix D's key for a registration.
38
+ *
39
+ * Derived from the URL because the URL is the endpoint's identity as far as the
40
+ * server is concerned — the same URL registered twice is one logical operation,
41
+ * which is exactly what makes a deploy-time `register` safe to retry. Hashed
42
+ * rather than interpolated because a URL carries `:` and `/`, and a composite
43
+ * value with separators in it is how an id grammar gets ambiguous.
44
+ *
45
+ * Note that the two webhook routes do NOT currently read `Idempotency-Key`
46
+ * server-side; only `POST /v1/reports` does. This satisfies the transport's
47
+ * Appendix D requirement and is inert at the service until that changes.
48
+ */
49
+ function registrationIdempotencyKey(url) {
50
+ return `webhook-endpoint.${(0, digest_js_1.sha256Digest)(url).replace('sha256:', '')}`;
51
+ }
52
+ const WebhookSecretSchema = zod_1.z.looseObject({
53
+ version: zod_1.z.number(),
54
+ value: zod_1.z.string(),
55
+ signingStartsAt: zod_1.z.string(),
56
+ });
57
+ const WebhookEndpointSchema = zod_1.z.looseObject({
58
+ webhookEndpointId: zod_1.z.string(),
59
+ url: zod_1.z.string(),
60
+ eventTypes: zod_1.z.array(zod_1.z.string()),
61
+ status: zod_1.z.string(),
62
+ disabledReason: zod_1.z.string().nullish(),
63
+ createdAt: zod_1.z.string(),
64
+ updatedAt: zod_1.z.string(),
65
+ secret: WebhookSecretSchema.optional(),
66
+ });
67
+ const RotatedWebhookSecretSchema = zod_1.z.looseObject({
68
+ webhookEndpointId: zod_1.z.string(),
69
+ secret: WebhookSecretSchema,
70
+ previousSecret: zod_1.z
71
+ .looseObject({ version: zod_1.z.number(), expiresAt: zod_1.z.string() })
72
+ .nullish(),
73
+ });
74
+ /**
75
+ * §10.2's two webhook-management routes.
76
+ *
77
+ * There is no list, no read-back and no delete, because the API serves none —
78
+ * §10.2 defines exactly two routes. An integrator therefore cannot enumerate
79
+ * what it has registered, which is a real gap in the service rather than
80
+ * something this client can paper over; re-registering a URL is the documented
81
+ * way to change a subscription or revive a disabled endpoint.
82
+ *
83
+ * Both routes need a credential with the `crowdsource:webhooks:manage` scope,
84
+ * and both derive the application from that credential. There is no
85
+ * `applicationId` in any request or response here.
86
+ */
87
+ class WebhookEndpoints {
88
+ constructor(transport) {
89
+ this.transport = transport;
90
+ }
91
+ /**
92
+ * Registers, or updates, the endpoint CrowdSource delivers to.
93
+ *
94
+ * Safe to call on every deploy: an existing URL is updated and no new secret
95
+ * is minted, so the secret the running process verifies with keeps working.
96
+ */
97
+ async register(input, options = {}) {
98
+ const response = await this.transport.request({
99
+ method: 'POST',
100
+ path: '/v1/webhook-endpoints',
101
+ body: { url: input.url, eventTypes: [...input.eventTypes] },
102
+ idempotencyKey: options.idempotencyKey ?? registrationIdempotencyKey(input.url),
103
+ signal: options.signal,
104
+ });
105
+ const parsed = WebhookEndpointSchema.safeParse(response);
106
+ if (!parsed.success) {
107
+ throw new errors_js_1.CrowdSourceTransportError('CrowdSource registered the webhook endpoint but answered with a body this client does not recognise.', { retryable: false, cause: parsed.error });
108
+ }
109
+ const { disabledReason, secret, ...rest } = parsed.data;
110
+ return {
111
+ ...rest,
112
+ disabledReason: disabledReason ?? null,
113
+ ...(secret === undefined ? {} : { secret }),
114
+ };
115
+ }
116
+ /**
117
+ * Mints a new signing secret, retiring the current one after an overlap.
118
+ *
119
+ * The only way to recover from a secret that was never stored, and the correct
120
+ * response to one that leaked.
121
+ */
122
+ async rotateSecret(webhookEndpointId, options = {}) {
123
+ const response = await this.transport.request({
124
+ method: 'POST',
125
+ path: `/v1/webhook-endpoints/${encodeURIComponent(webhookEndpointId)}/rotate-secret`,
126
+ body: options.overlapSeconds === undefined ? {} : { overlapSeconds: options.overlapSeconds },
127
+ idempotencyKey: options.idempotencyKey ??
128
+ `webhook-endpoint.${(0, digest_js_1.sha256Digest)(webhookEndpointId).replace('sha256:', '')}.rotate`,
129
+ signal: options.signal,
130
+ });
131
+ const parsed = RotatedWebhookSecretSchema.safeParse(response);
132
+ if (!parsed.success) {
133
+ throw new errors_js_1.CrowdSourceTransportError('CrowdSource rotated the webhook secret but answered with a body this client does not recognise.', { retryable: false, cause: parsed.error });
134
+ }
135
+ return {
136
+ webhookEndpointId: parsed.data.webhookEndpointId,
137
+ secret: parsed.data.secret,
138
+ previousSecret: parsed.data.previousSecret ?? null,
139
+ };
140
+ }
141
+ }
142
+ exports.WebhookEndpoints = WebhookEndpoints;
143
+ //# sourceMappingURL=webhookEndpoints.js.map