@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.
- package/README.md +571 -0
- package/dist/cases.d.ts +66 -0
- package/dist/cases.d.ts.map +1 -0
- package/dist/cases.js +79 -0
- package/dist/cases.js.map +1 -0
- package/dist/client.d.ts +90 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +125 -0
- package/dist/client.js.map +1 -0
- package/dist/communityNotes.d.ts +49 -0
- package/dist/communityNotes.d.ts.map +1 -0
- package/dist/communityNotes.js +104 -0
- package/dist/communityNotes.js.map +1 -0
- package/dist/credential.d.ts +58 -0
- package/dist/credential.d.ts.map +1 -0
- package/dist/credential.js +71 -0
- package/dist/credential.js.map +1 -0
- package/dist/defaults.d.ts +61 -0
- package/dist/defaults.d.ts.map +1 -0
- package/dist/defaults.js +82 -0
- package/dist/defaults.js.map +1 -0
- package/dist/digest.d.ts +44 -0
- package/dist/digest.d.ts.map +1 -0
- package/dist/digest.js +79 -0
- package/dist/digest.js.map +1 -0
- package/dist/envelope.d.ts +143 -0
- package/dist/envelope.d.ts.map +1 -0
- package/dist/envelope.js +258 -0
- package/dist/envelope.js.map +1 -0
- package/dist/errors.d.ts +90 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +151 -0
- package/dist/errors.js.map +1 -0
- package/dist/esm/cases.js +74 -0
- package/dist/esm/cases.js.map +1 -0
- package/dist/esm/client.js +121 -0
- package/dist/esm/client.js.map +1 -0
- package/dist/esm/communityNotes.js +100 -0
- package/dist/esm/communityNotes.js.map +1 -0
- package/dist/esm/credential.js +67 -0
- package/dist/esm/credential.js.map +1 -0
- package/dist/esm/defaults.js +77 -0
- package/dist/esm/defaults.js.map +1 -0
- package/dist/esm/digest.js +74 -0
- package/dist/esm/digest.js.map +1 -0
- package/dist/esm/envelope.js +252 -0
- package/dist/esm/envelope.js.map +1 -0
- package/dist/esm/errors.js +140 -0
- package/dist/esm/errors.js.map +1 -0
- package/dist/esm/express/index.js +29 -0
- package/dist/esm/express/index.js.map +1 -0
- package/dist/esm/express/middleware.js +293 -0
- package/dist/esm/express/middleware.js.map +1 -0
- package/dist/esm/express/store.js +69 -0
- package/dist/esm/express/store.js.map +1 -0
- package/dist/esm/express/verify.js +133 -0
- package/dist/esm/express/verify.js.map +1 -0
- package/dist/esm/index.js +57 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/outbox/client.js +43 -0
- package/dist/esm/outbox/client.js.map +1 -0
- package/dist/esm/outbox/decision.js +153 -0
- package/dist/esm/outbox/decision.js.map +1 -0
- package/dist/esm/outbox/delivery.js +129 -0
- package/dist/esm/outbox/delivery.js.map +1 -0
- package/dist/esm/outbox/enforcement/executor.js +219 -0
- package/dist/esm/outbox/enforcement/executor.js.map +1 -0
- package/dist/esm/outbox/enforcement/planner.js +326 -0
- package/dist/esm/outbox/enforcement/planner.js.map +1 -0
- package/dist/esm/outbox/evidence.js +173 -0
- package/dist/esm/outbox/evidence.js.map +1 -0
- package/dist/esm/outbox/inbound.js +87 -0
- package/dist/esm/outbox/inbound.js.map +1 -0
- package/dist/esm/outbox/index.js +71 -0
- package/dist/esm/outbox/index.js.map +1 -0
- package/dist/esm/outbox/intake.js +140 -0
- package/dist/esm/outbox/intake.js.map +1 -0
- package/dist/esm/outbox/integration.js +116 -0
- package/dist/esm/outbox/integration.js.map +1 -0
- package/dist/esm/outbox/outbox/dispatcher.js +114 -0
- package/dist/esm/outbox/outbox/dispatcher.js.map +1 -0
- package/dist/esm/outbox/outbox/service.js +271 -0
- package/dist/esm/outbox/outbox/service.js.map +1 -0
- package/dist/esm/outbox/postgres/index.js +41 -0
- package/dist/esm/outbox/postgres/index.js.map +1 -0
- package/dist/esm/outbox/postgres/registries.js +155 -0
- package/dist/esm/outbox/postgres/registries.js.map +1 -0
- package/dist/esm/outbox/postgres/reportColumns.js +191 -0
- package/dist/esm/outbox/postgres/reportColumns.js.map +1 -0
- package/dist/esm/outbox/postgres/store/enforcement.js +158 -0
- package/dist/esm/outbox/postgres/store/enforcement.js.map +1 -0
- package/dist/esm/outbox/postgres/store/events.js +101 -0
- package/dist/esm/outbox/postgres/store/events.js.map +1 -0
- package/dist/esm/outbox/postgres/store/index.js +59 -0
- package/dist/esm/outbox/postgres/store/index.js.map +1 -0
- package/dist/esm/outbox/postgres/store/outbox.js +207 -0
- package/dist/esm/outbox/postgres/store/outbox.js.map +1 -0
- package/dist/esm/outbox/postgres/store/reports.js +231 -0
- package/dist/esm/outbox/postgres/store/reports.js.map +1 -0
- package/dist/esm/outbox/postgres/store/transaction.js +29 -0
- package/dist/esm/outbox/postgres/store/transaction.js.map +1 -0
- package/dist/esm/outbox/postgres/tables.js +270 -0
- package/dist/esm/outbox/postgres/tables.js.map +1 -0
- package/dist/esm/outbox/reconciliation.js +157 -0
- package/dist/esm/outbox/reconciliation.js.map +1 -0
- package/dist/esm/outbox/reportStatus.js +22 -0
- package/dist/esm/outbox/reportStatus.js.map +1 -0
- package/dist/esm/outbox/retention.js +33 -0
- package/dist/esm/outbox/retention.js.map +1 -0
- package/dist/esm/outbox/store/types.js +30 -0
- package/dist/esm/outbox/store/types.js.map +1 -0
- package/dist/esm/outbox/types.js +23 -0
- package/dist/esm/outbox/types.js.map +1 -0
- package/dist/esm/outbox/webhook.js +145 -0
- package/dist/esm/outbox/webhook.js.map +1 -0
- package/dist/esm/package.json +1 -0
- package/dist/esm/reports.js +74 -0
- package/dist/esm/reports.js.map +1 -0
- package/dist/esm/testing/fixtures.js +132 -0
- package/dist/esm/testing/fixtures.js.map +1 -0
- package/dist/esm/testing/index.js +33 -0
- package/dist/esm/testing/index.js.map +1 -0
- package/dist/esm/testing/sandbox.js +337 -0
- package/dist/esm/testing/sandbox.js.map +1 -0
- package/dist/esm/testing/webhook-simulator.js +93 -0
- package/dist/esm/testing/webhook-simulator.js.map +1 -0
- package/dist/esm/transport.js +177 -0
- package/dist/esm/transport.js.map +1 -0
- package/dist/esm/webhookEndpoints.js +139 -0
- package/dist/esm/webhookEndpoints.js.map +1 -0
- package/dist/express/index.d.ts +32 -0
- package/dist/express/index.d.ts.map +1 -0
- package/dist/express/index.js +40 -0
- package/dist/express/index.js.map +1 -0
- package/dist/express/middleware.d.ts +97 -0
- package/dist/express/middleware.d.ts.map +1 -0
- package/dist/express/middleware.js +298 -0
- package/dist/express/middleware.js.map +1 -0
- package/dist/express/store.d.ts +45 -0
- package/dist/express/store.d.ts.map +1 -0
- package/dist/express/store.js +72 -0
- package/dist/express/store.js.map +1 -0
- package/dist/express/verify.d.ts +86 -0
- package/dist/express/verify.d.ts.map +1 -0
- package/dist/express/verify.js +138 -0
- package/dist/express/verify.js.map +1 -0
- package/dist/index.d.ts +67 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +89 -0
- package/dist/index.js.map +1 -0
- package/dist/outbox/client.d.ts +35 -0
- package/dist/outbox/client.d.ts.map +1 -0
- package/dist/outbox/client.js +46 -0
- package/dist/outbox/client.js.map +1 -0
- package/dist/outbox/decision.d.ts +36 -0
- package/dist/outbox/decision.d.ts.map +1 -0
- package/dist/outbox/decision.js +159 -0
- package/dist/outbox/decision.js.map +1 -0
- package/dist/outbox/delivery.d.ts +50 -0
- package/dist/outbox/delivery.d.ts.map +1 -0
- package/dist/outbox/delivery.js +135 -0
- package/dist/outbox/delivery.js.map +1 -0
- package/dist/outbox/enforcement/executor.d.ts +26 -0
- package/dist/outbox/enforcement/executor.d.ts.map +1 -0
- package/dist/outbox/enforcement/executor.js +222 -0
- package/dist/outbox/enforcement/executor.js.map +1 -0
- package/dist/outbox/enforcement/planner.d.ts +50 -0
- package/dist/outbox/enforcement/planner.d.ts.map +1 -0
- package/dist/outbox/enforcement/planner.js +333 -0
- package/dist/outbox/enforcement/planner.js.map +1 -0
- package/dist/outbox/evidence.d.ts +114 -0
- package/dist/outbox/evidence.d.ts.map +1 -0
- package/dist/outbox/evidence.js +181 -0
- package/dist/outbox/evidence.js.map +1 -0
- package/dist/outbox/inbound.d.ts +78 -0
- package/dist/outbox/inbound.d.ts.map +1 -0
- package/dist/outbox/inbound.js +91 -0
- package/dist/outbox/inbound.js.map +1 -0
- package/dist/outbox/index.d.ts +79 -0
- package/dist/outbox/index.d.ts.map +1 -0
- package/dist/outbox/index.js +98 -0
- package/dist/outbox/index.js.map +1 -0
- package/dist/outbox/intake.d.ts +62 -0
- package/dist/outbox/intake.d.ts.map +1 -0
- package/dist/outbox/intake.js +145 -0
- package/dist/outbox/intake.js.map +1 -0
- package/dist/outbox/integration.d.ts +62 -0
- package/dist/outbox/integration.d.ts.map +1 -0
- package/dist/outbox/integration.js +119 -0
- package/dist/outbox/integration.js.map +1 -0
- package/dist/outbox/outbox/dispatcher.d.ts +26 -0
- package/dist/outbox/outbox/dispatcher.d.ts.map +1 -0
- package/dist/outbox/outbox/dispatcher.js +119 -0
- package/dist/outbox/outbox/dispatcher.js.map +1 -0
- package/dist/outbox/outbox/service.d.ts +124 -0
- package/dist/outbox/outbox/service.d.ts.map +1 -0
- package/dist/outbox/outbox/service.js +279 -0
- package/dist/outbox/outbox/service.js.map +1 -0
- package/dist/outbox/postgres/index.d.ts +44 -0
- package/dist/outbox/postgres/index.d.ts.map +1 -0
- package/dist/outbox/postgres/index.js +51 -0
- package/dist/outbox/postgres/index.js.map +1 -0
- package/dist/outbox/postgres/registries.d.ts +69 -0
- package/dist/outbox/postgres/registries.d.ts.map +1 -0
- package/dist/outbox/postgres/registries.js +159 -0
- package/dist/outbox/postgres/registries.js.map +1 -0
- package/dist/outbox/postgres/reportColumns.d.ts +154 -0
- package/dist/outbox/postgres/reportColumns.d.ts.map +1 -0
- package/dist/outbox/postgres/reportColumns.js +195 -0
- package/dist/outbox/postgres/reportColumns.js.map +1 -0
- package/dist/outbox/postgres/store/enforcement.d.ts +33 -0
- package/dist/outbox/postgres/store/enforcement.d.ts.map +1 -0
- package/dist/outbox/postgres/store/enforcement.js +161 -0
- package/dist/outbox/postgres/store/enforcement.js.map +1 -0
- package/dist/outbox/postgres/store/events.d.ts +46 -0
- package/dist/outbox/postgres/store/events.d.ts.map +1 -0
- package/dist/outbox/postgres/store/events.js +104 -0
- package/dist/outbox/postgres/store/events.js.map +1 -0
- package/dist/outbox/postgres/store/index.d.ts +27 -0
- package/dist/outbox/postgres/store/index.d.ts.map +1 -0
- package/dist/outbox/postgres/store/index.js +62 -0
- package/dist/outbox/postgres/store/index.js.map +1 -0
- package/dist/outbox/postgres/store/outbox.d.ts +8 -0
- package/dist/outbox/postgres/store/outbox.d.ts.map +1 -0
- package/dist/outbox/postgres/store/outbox.js +210 -0
- package/dist/outbox/postgres/store/outbox.js.map +1 -0
- package/dist/outbox/postgres/store/reports.d.ts +69 -0
- package/dist/outbox/postgres/store/reports.d.ts.map +1 -0
- package/dist/outbox/postgres/store/reports.js +234 -0
- package/dist/outbox/postgres/store/reports.js.map +1 -0
- package/dist/outbox/postgres/store/transaction.d.ts +49 -0
- package/dist/outbox/postgres/store/transaction.d.ts.map +1 -0
- package/dist/outbox/postgres/store/transaction.js +32 -0
- package/dist/outbox/postgres/store/transaction.js.map +1 -0
- package/dist/outbox/postgres/tables.d.ts +737 -0
- package/dist/outbox/postgres/tables.d.ts.map +1 -0
- package/dist/outbox/postgres/tables.js +273 -0
- package/dist/outbox/postgres/tables.js.map +1 -0
- package/dist/outbox/reconciliation.d.ts +49 -0
- package/dist/outbox/reconciliation.d.ts.map +1 -0
- package/dist/outbox/reconciliation.js +162 -0
- package/dist/outbox/reconciliation.js.map +1 -0
- package/dist/outbox/reportStatus.d.ts +12 -0
- package/dist/outbox/reportStatus.d.ts.map +1 -0
- package/dist/outbox/reportStatus.js +25 -0
- package/dist/outbox/reportStatus.js.map +1 -0
- package/dist/outbox/retention.d.ts +33 -0
- package/dist/outbox/retention.d.ts.map +1 -0
- package/dist/outbox/retention.js +36 -0
- package/dist/outbox/retention.js.map +1 -0
- package/dist/outbox/store/types.d.ts +466 -0
- package/dist/outbox/store/types.d.ts.map +1 -0
- package/dist/outbox/store/types.js +31 -0
- package/dist/outbox/store/types.js.map +1 -0
- package/dist/outbox/types.d.ts +660 -0
- package/dist/outbox/types.d.ts.map +1 -0
- package/dist/outbox/types.js +24 -0
- package/dist/outbox/types.js.map +1 -0
- package/dist/outbox/webhook.d.ts +15 -0
- package/dist/outbox/webhook.d.ts.map +1 -0
- package/dist/outbox/webhook.js +148 -0
- package/dist/outbox/webhook.js.map +1 -0
- package/dist/reports.d.ts +73 -0
- package/dist/reports.d.ts.map +1 -0
- package/dist/reports.js +78 -0
- package/dist/reports.js.map +1 -0
- package/dist/testing/fixtures.d.ts +62 -0
- package/dist/testing/fixtures.d.ts.map +1 -0
- package/dist/testing/fixtures.js +137 -0
- package/dist/testing/fixtures.js.map +1 -0
- package/dist/testing/index.d.ts +36 -0
- package/dist/testing/index.d.ts.map +1 -0
- package/dist/testing/index.js +43 -0
- package/dist/testing/index.js.map +1 -0
- package/dist/testing/sandbox.d.ts +109 -0
- package/dist/testing/sandbox.d.ts.map +1 -0
- package/dist/testing/sandbox.js +342 -0
- package/dist/testing/sandbox.js.map +1 -0
- package/dist/testing/webhook-simulator.d.ts +78 -0
- package/dist/testing/webhook-simulator.d.ts.map +1 -0
- package/dist/testing/webhook-simulator.js +98 -0
- package/dist/testing/webhook-simulator.js.map +1 -0
- package/dist/transport.d.ts +59 -0
- package/dist/transport.d.ts.map +1 -0
- package/dist/transport.js +181 -0
- package/dist/transport.js.map +1 -0
- package/dist/webhookEndpoints.d.ts +147 -0
- package/dist/webhookEndpoints.d.ts.map +1 -0
- package/dist/webhookEndpoints.js +143 -0
- package/dist/webhookEndpoints.js.map +1 -0
- package/package.json +127 -0
- package/src/cases.ts +119 -0
- package/src/client.ts +202 -0
- package/src/communityNotes.ts +194 -0
- package/src/credential.ts +100 -0
- package/src/defaults.ts +87 -0
- package/src/digest.ts +91 -0
- package/src/envelope.ts +432 -0
- package/src/errors.ts +165 -0
- package/src/express/index.ts +43 -0
- package/src/express/middleware.ts +387 -0
- package/src/express/store.ts +85 -0
- package/src/express/verify.ts +166 -0
- package/src/index.ts +107 -0
- package/src/outbox/client.ts +75 -0
- package/src/outbox/decision.ts +209 -0
- package/src/outbox/delivery.ts +163 -0
- package/src/outbox/enforcement/executor.ts +288 -0
- package/src/outbox/enforcement/planner.ts +377 -0
- package/src/outbox/evidence.ts +231 -0
- package/src/outbox/inbound.ts +140 -0
- package/src/outbox/index.ts +160 -0
- package/src/outbox/intake.ts +175 -0
- package/src/outbox/integration.ts +205 -0
- package/src/outbox/outbox/dispatcher.ts +131 -0
- package/src/outbox/outbox/service.ts +444 -0
- package/src/outbox/postgres/index.ts +59 -0
- package/src/outbox/postgres/registries.ts +177 -0
- package/src/outbox/postgres/reportColumns.ts +281 -0
- package/src/outbox/postgres/store/enforcement.ts +186 -0
- package/src/outbox/postgres/store/events.ts +112 -0
- package/src/outbox/postgres/store/index.ts +72 -0
- package/src/outbox/postgres/store/outbox.ts +273 -0
- package/src/outbox/postgres/store/reports.ts +349 -0
- package/src/outbox/postgres/store/transaction.ts +64 -0
- package/src/outbox/postgres/tables.ts +339 -0
- package/src/outbox/reconciliation.ts +199 -0
- package/src/outbox/reportStatus.ts +24 -0
- package/src/outbox/retention.ts +34 -0
- package/src/outbox/store/types.ts +528 -0
- package/src/outbox/types.ts +753 -0
- package/src/outbox/webhook.ts +165 -0
- package/src/reports.ts +138 -0
- package/src/testing/fixtures.ts +183 -0
- package/src/testing/index.ts +57 -0
- package/src/testing/sandbox.ts +469 -0
- package/src/testing/webhook-simulator.ts +166 -0
- package/src/transport.ts +250 -0
- package/src/webhookEndpoints.ts +256 -0
package/dist/cases.js
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* `GET /v1/cases/{id}` and `GET /v1/decisions/{id}` (§10.2).
|
|
4
|
+
*
|
|
5
|
+
* Both are look-ups by an id CrowdSource gave the application. There is no case
|
|
6
|
+
* search and there will not be one: "nobody chooses the case they review" is an
|
|
7
|
+
* invariant, and a list endpoint on the application API would be the first step
|
|
8
|
+
* towards a queue somebody browses.
|
|
9
|
+
*
|
|
10
|
+
* `decisions.get` is written against the route §10.2 defines. **The backend does
|
|
11
|
+
* not serve it yet** — nothing publishes decisions, because sortition, review
|
|
12
|
+
* and consensus are not built — so it answers 404 today. It is here because a
|
|
13
|
+
* client of a documented endpoint is not a stub, and because the webhook path
|
|
14
|
+
* (`case.decided`) is how an application learns about a decision anyway; this is
|
|
15
|
+
* the read-back for one it already has an id for.
|
|
16
|
+
*/
|
|
17
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
18
|
+
exports.Decisions = exports.Cases = void 0;
|
|
19
|
+
const contracts_1 = require("@crowdsource.you/contracts");
|
|
20
|
+
const zod_1 = require("zod");
|
|
21
|
+
const errors_js_1 = require("./errors.js");
|
|
22
|
+
const CaseViewSchema = zod_1.z.looseObject({
|
|
23
|
+
caseId: zod_1.z.string(),
|
|
24
|
+
status: zod_1.z.string(),
|
|
25
|
+
subject: zod_1.z.looseObject({ externalId: zod_1.z.string(), type: zod_1.z.string() }),
|
|
26
|
+
policy: zod_1.z.looseObject({ policySetId: zod_1.z.string(), version: zod_1.z.string() }),
|
|
27
|
+
taxonomyVersion: zod_1.z.string(),
|
|
28
|
+
allegationCodes: zod_1.z.array(zod_1.z.string()),
|
|
29
|
+
reportCount: zod_1.z.number(),
|
|
30
|
+
sensitivityClass: zod_1.z.string(),
|
|
31
|
+
currentRevision: zod_1.z.number(),
|
|
32
|
+
createdAt: zod_1.z.string(),
|
|
33
|
+
updatedAt: zod_1.z.string(),
|
|
34
|
+
});
|
|
35
|
+
class Cases {
|
|
36
|
+
constructor(transport) {
|
|
37
|
+
this.transport = transport;
|
|
38
|
+
}
|
|
39
|
+
async get(caseId, options = {}) {
|
|
40
|
+
const response = await this.transport.request({
|
|
41
|
+
method: 'GET',
|
|
42
|
+
path: `/v1/cases/${encodeURIComponent(caseId)}`,
|
|
43
|
+
signal: options.signal,
|
|
44
|
+
});
|
|
45
|
+
const parsed = CaseViewSchema.safeParse(response);
|
|
46
|
+
if (!parsed.success) {
|
|
47
|
+
throw new errors_js_1.CrowdSourceTransportError('CrowdSource answered with a case this client does not recognise.', { retryable: false, cause: parsed.error });
|
|
48
|
+
}
|
|
49
|
+
return parsed.data;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
exports.Cases = Cases;
|
|
53
|
+
class Decisions {
|
|
54
|
+
constructor(transport) {
|
|
55
|
+
this.transport = transport;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Reads one immutable revision of a decision.
|
|
59
|
+
*
|
|
60
|
+
* A published revision is never edited (Appendix F); a later revision
|
|
61
|
+
* supersedes it and carries `supersedesDecisionId`. So a decision read twice
|
|
62
|
+
* is byte-identical, and an application that cached one never needs to
|
|
63
|
+
* invalidate it — it needs to notice a `decision.corrected` webhook.
|
|
64
|
+
*/
|
|
65
|
+
async get(decisionId, options = {}) {
|
|
66
|
+
const response = await this.transport.request({
|
|
67
|
+
method: 'GET',
|
|
68
|
+
path: `/v1/decisions/${encodeURIComponent(decisionId)}`,
|
|
69
|
+
signal: options.signal,
|
|
70
|
+
});
|
|
71
|
+
const parsed = contracts_1.DecisionSchema.safeParse(response);
|
|
72
|
+
if (!parsed.success) {
|
|
73
|
+
throw new errors_js_1.CrowdSourceTransportError('CrowdSource answered with a decision this client does not recognise.', { retryable: false, cause: parsed.error });
|
|
74
|
+
}
|
|
75
|
+
return parsed.data;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
exports.Decisions = Decisions;
|
|
79
|
+
//# sourceMappingURL=cases.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cases.js","sourceRoot":"","sources":["../src/cases.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;GAcG;;;AAEH,0DAA8F;AAC9F,6BAAwB;AAExB,2CAAwD;AAyBxD,MAAM,cAAc,GAAG,OAAC,CAAC,WAAW,CAAC;IACnC,MAAM,EAAE,OAAC,CAAC,MAAM,EAAE;IAClB,MAAM,EAAE,OAAC,CAAC,MAAM,EAAE;IAClB,OAAO,EAAE,OAAC,CAAC,WAAW,CAAC,EAAE,UAAU,EAAE,OAAC,CAAC,MAAM,EAAE,EAAE,IAAI,EAAE,OAAC,CAAC,MAAM,EAAE,EAAE,CAAC;IACpE,MAAM,EAAE,OAAC,CAAC,WAAW,CAAC,EAAE,WAAW,EAAE,OAAC,CAAC,MAAM,EAAE,EAAE,OAAO,EAAE,OAAC,CAAC,MAAM,EAAE,EAAE,CAAC;IACvE,eAAe,EAAE,OAAC,CAAC,MAAM,EAAE;IAC3B,eAAe,EAAE,OAAC,CAAC,KAAK,CAAC,OAAC,CAAC,MAAM,EAAE,CAAC;IACpC,WAAW,EAAE,OAAC,CAAC,MAAM,EAAE;IACvB,gBAAgB,EAAE,OAAC,CAAC,MAAM,EAAE;IAC5B,eAAe,EAAE,OAAC,CAAC,MAAM,EAAE;IAC3B,SAAS,EAAE,OAAC,CAAC,MAAM,EAAE;IACrB,SAAS,EAAE,OAAC,CAAC,MAAM,EAAE;CACtB,CAAC,CAAC;AAMH,MAAa,KAAK;IAGhB,YAAY,SAAoB;QAC9B,IAAI,CAAC,SAAS,GAAG,SAAS,CAAC;IAC7B,CAAC;IAED,KAAK,CAAC,GAAG,CAAC,MAAc,EAAE,UAAuB,EAAE;QACjD,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,SAAS,CAAC,OAAO,CAAU;YACrD,MAAM,EAAE,KAAK;YACb,IAAI,EAAE,aAAa,kBAAkB,CAAC,MAAM,CAAC,EAAE;YAC/C,MAAM,EAAE,OAAO,CAAC,MAAM;SACvB,CAAC,CAAC;QAEH,MAAM,MAAM,GAAG,cAAc,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC;QAClD,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;YACpB,MAAM,IAAI,qCAAyB,CACjC,kEAAkE,EAClE,EAAE,SAAS,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,EAAE,CAC1C,CAAC;QACJ,CAAC;QACD,OAAO,MAAM,CAAC,IAAI,CAAC;IACrB,CAAC;CACF;AAvBD,sBAuBC;AAED,MAAa,SAAS;IAGpB,YAAY,SAAoB;QAC9B,IAAI,CAAC,SAAS,GAAG,SAAS,CAAC;IAC7B,CAAC;IAED;;;;;;;OAOG;IACH,KAAK,CAAC,GAAG,CAAC,UAAkB,EAAE,UAAuB,EAAE;QACrD,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,SAAS,CAAC,OAAO,CAAU;YACrD,MAAM,EAAE,KAAK;YACb,IAAI,EAAE,iBAAiB,kBAAkB,CAAC,UAAU,CAAC,EAAE;YACvD,MAAM,EAAE,OAAO,CAAC,MAAM;SACvB,CAAC,CAAC;QAEH,MAAM,MAAM,GAAG,0BAAc,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC;QAClD,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;YACpB,MAAM,IAAI,qCAAyB,CACjC,sEAAsE,EACtE,EAAE,SAAS,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,EAAE,CAC1C,CAAC;QACJ,CAAC;QACD,OAAO,MAAM,CAAC,IAAI,CAAC;IACrB,CAAC;CACF;AA/BD,8BA+BC"}
|
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The client.
|
|
3
|
+
*
|
|
4
|
+
* The whole design target is the length of the smallest integration:
|
|
5
|
+
*
|
|
6
|
+
* const crowdsource = new CrowdSource();
|
|
7
|
+
* await crowdsource.reports.create({ ... });
|
|
8
|
+
*
|
|
9
|
+
* Everything else has a default that is correct for the overwhelming majority of
|
|
10
|
+
* integrators and a way to override it for the rest. The service key comes from
|
|
11
|
+
* the environment, the base URL is the one deployment CrowdSource has, and the
|
|
12
|
+
* envelope, the policy version, the digests, the principal refs and the
|
|
13
|
+
* idempotency key are all composed rather than configured.
|
|
14
|
+
*
|
|
15
|
+
* This client is SERVER-SIDE ONLY. A service credential is the tenant's identity
|
|
16
|
+
* for its whole moderation stream; shipping one to a browser or a mobile bundle
|
|
17
|
+
* hands every user of the application the ability to file reports as the
|
|
18
|
+
* application, read its cases and exhaust its quota. The package depends on
|
|
19
|
+
* `node:crypto` and does not build for a browser, which is the intended
|
|
20
|
+
* outcome rather than a limitation to work around.
|
|
21
|
+
*/
|
|
22
|
+
import { Cases, Decisions } from './cases.js';
|
|
23
|
+
import { CommunityNotes } from './communityNotes.js';
|
|
24
|
+
import { Reports } from './reports.js';
|
|
25
|
+
import { type FetchLike } from './transport.js';
|
|
26
|
+
import { WebhookEndpoints } from './webhookEndpoints.js';
|
|
27
|
+
/** The environment variable a zero-configuration integration reads. */
|
|
28
|
+
export declare const SERVICE_KEY_ENV_VAR = "CROWDSOURCE_SERVICE_KEY";
|
|
29
|
+
/** Overrides the service host. Set only when pointing at a local backend. */
|
|
30
|
+
export declare const BASE_URL_ENV_VAR = "CROWDSOURCE_BASE_URL";
|
|
31
|
+
export interface CrowdSourceOptions {
|
|
32
|
+
/**
|
|
33
|
+
* The service key CrowdSource issued, as one opaque string. Defaults to
|
|
34
|
+
* `process.env.CROWDSOURCE_SERVICE_KEY`.
|
|
35
|
+
*
|
|
36
|
+
* There is no `applicationId` option here or anywhere else. The application a
|
|
37
|
+
* report belongs to is read off this credential — see `credential.ts`.
|
|
38
|
+
*/
|
|
39
|
+
readonly serviceKey?: string;
|
|
40
|
+
/**
|
|
41
|
+
* A first-party Oxy service authenticating with NO CrowdSource credential.
|
|
42
|
+
*
|
|
43
|
+
* Return a current Oxy service token; it is asked for once per request
|
|
44
|
+
* attempt, so returning a cached token and refreshing it when it expires is
|
|
45
|
+
* the expected shape (`oxyServices.getServiceToken()` does exactly that).
|
|
46
|
+
*
|
|
47
|
+
* With this set, `serviceKey` is neither needed nor read. CrowdSource resolves
|
|
48
|
+
* the tenant from the Oxy application the token names, so nothing here has to
|
|
49
|
+
* be issued, stored or rotated by a person. Third parties keep the service
|
|
50
|
+
* key: they run where Oxy cannot vouch for them.
|
|
51
|
+
*/
|
|
52
|
+
readonly oxyToken?: () => string | Promise<string>;
|
|
53
|
+
readonly baseUrl?: string;
|
|
54
|
+
/** Per-attempt deadline. Default 10s. */
|
|
55
|
+
readonly timeoutMs?: number;
|
|
56
|
+
/** Attempts per call including the first, for retryable failures. Default 3. */
|
|
57
|
+
readonly maxAttempts?: number;
|
|
58
|
+
/**
|
|
59
|
+
* Marks reports as coming from the application's own pre-production (§5.1
|
|
60
|
+
* `source.environment`). CrowdSource has one deployment; this is a property of
|
|
61
|
+
* the report, not a different host to talk to.
|
|
62
|
+
*
|
|
63
|
+
* Every report from a sandbox client must carry `submittedAt`, because the
|
|
64
|
+
* environment travels inside `source` and `source` cannot be composed without
|
|
65
|
+
* one — see `ReportInput.submittedAt` for why inventing that timestamp would
|
|
66
|
+
* turn every retry into a 409.
|
|
67
|
+
*/
|
|
68
|
+
readonly sandbox?: boolean;
|
|
69
|
+
/** Injected for tests and for the in-process sandbox. Defaults to global `fetch`. */
|
|
70
|
+
readonly fetch?: FetchLike;
|
|
71
|
+
}
|
|
72
|
+
export declare class CrowdSource {
|
|
73
|
+
/**
|
|
74
|
+
* The application this client acts as.
|
|
75
|
+
*
|
|
76
|
+
* Read off the credential when there is one. With an Oxy token there is
|
|
77
|
+
* nothing to read it off — the mapping lives in CrowdSource — so it resolves
|
|
78
|
+
* on first use from `GET /v1/applications/me` and is remembered.
|
|
79
|
+
*/
|
|
80
|
+
readonly applicationId: string | Promise<string>;
|
|
81
|
+
readonly reports: Reports;
|
|
82
|
+
readonly cases: Cases;
|
|
83
|
+
readonly decisions: Decisions;
|
|
84
|
+
/** Where decisions get delivered, and the secret that signs them (§10.2). */
|
|
85
|
+
readonly webhookEndpoints: WebhookEndpoints;
|
|
86
|
+
/** Community notes: write, withdraw, draw to rate, rate, and the reads. */
|
|
87
|
+
readonly communityNotes: CommunityNotes;
|
|
88
|
+
constructor(options?: CrowdSourceOptions);
|
|
89
|
+
}
|
|
90
|
+
//# sourceMappingURL=client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAC9C,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAIrD,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AACvC,OAAO,EAIL,KAAK,SAAS,EACf,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAEzD,uEAAuE;AACvE,eAAO,MAAM,mBAAmB,4BAA4B,CAAC;AAE7D,6EAA6E;AAC7E,eAAO,MAAM,gBAAgB,yBAAyB,CAAC;AAEvD,MAAM,WAAW,kBAAkB;IACjC;;;;;;OAMG;IACH,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAC7B;;;;;;;;;;;OAWG;IACH,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IACnD,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAC1B,yCAAyC;IACzC,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B,gFAAgF;IAChF,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B;;;;;;;;;OASG;IACH,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC;IAC3B,qFAAqF;IACrF,QAAQ,CAAC,KAAK,CAAC,EAAE,SAAS,CAAC;CAC5B;AAED,qBAAa,WAAW;IACtB;;;;;;OAMG;IACH,QAAQ,CAAC,aAAa,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAEjD,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAC;IACtB,QAAQ,CAAC,SAAS,EAAE,SAAS,CAAC;IAC9B,6EAA6E;IAC7E,QAAQ,CAAC,gBAAgB,EAAE,gBAAgB,CAAC;IAC5C,2EAA2E;IAC3E,QAAQ,CAAC,cAAc,EAAE,cAAc,CAAC;gBAE5B,OAAO,GAAE,kBAAuB;CA4C7C"}
|
package/dist/client.js
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* The client.
|
|
4
|
+
*
|
|
5
|
+
* The whole design target is the length of the smallest integration:
|
|
6
|
+
*
|
|
7
|
+
* const crowdsource = new CrowdSource();
|
|
8
|
+
* await crowdsource.reports.create({ ... });
|
|
9
|
+
*
|
|
10
|
+
* Everything else has a default that is correct for the overwhelming majority of
|
|
11
|
+
* integrators and a way to override it for the rest. The service key comes from
|
|
12
|
+
* the environment, the base URL is the one deployment CrowdSource has, and the
|
|
13
|
+
* envelope, the policy version, the digests, the principal refs and the
|
|
14
|
+
* idempotency key are all composed rather than configured.
|
|
15
|
+
*
|
|
16
|
+
* This client is SERVER-SIDE ONLY. A service credential is the tenant's identity
|
|
17
|
+
* for its whole moderation stream; shipping one to a browser or a mobile bundle
|
|
18
|
+
* hands every user of the application the ability to file reports as the
|
|
19
|
+
* application, read its cases and exhaust its quota. The package depends on
|
|
20
|
+
* `node:crypto` and does not build for a browser, which is the intended
|
|
21
|
+
* outcome rather than a limitation to work around.
|
|
22
|
+
*/
|
|
23
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
24
|
+
exports.CrowdSource = exports.BASE_URL_ENV_VAR = exports.SERVICE_KEY_ENV_VAR = void 0;
|
|
25
|
+
const cases_js_1 = require("./cases.js");
|
|
26
|
+
const communityNotes_js_1 = require("./communityNotes.js");
|
|
27
|
+
const credential_js_1 = require("./credential.js");
|
|
28
|
+
const defaults_js_1 = require("./defaults.js");
|
|
29
|
+
const errors_js_1 = require("./errors.js");
|
|
30
|
+
const reports_js_1 = require("./reports.js");
|
|
31
|
+
const transport_js_1 = require("./transport.js");
|
|
32
|
+
const webhookEndpoints_js_1 = require("./webhookEndpoints.js");
|
|
33
|
+
/** The environment variable a zero-configuration integration reads. */
|
|
34
|
+
exports.SERVICE_KEY_ENV_VAR = 'CROWDSOURCE_SERVICE_KEY';
|
|
35
|
+
/** Overrides the service host. Set only when pointing at a local backend. */
|
|
36
|
+
exports.BASE_URL_ENV_VAR = 'CROWDSOURCE_BASE_URL';
|
|
37
|
+
class CrowdSource {
|
|
38
|
+
constructor(options = {}) {
|
|
39
|
+
/**
|
|
40
|
+
* Two ways to be an application, and exactly one of them is configured.
|
|
41
|
+
*
|
|
42
|
+
* The Oxy path is checked first so that a deployment which has BOTH — during
|
|
43
|
+
* the migration off shared secrets — uses the identity it can prove rather
|
|
44
|
+
* than the secret it still happens to hold. Removing the key is then the
|
|
45
|
+
* cleanup, not the cutover.
|
|
46
|
+
*/
|
|
47
|
+
const credential = options.oxyToken
|
|
48
|
+
? null
|
|
49
|
+
: (0, credential_js_1.parseServiceKey)(options.serviceKey ?? process.env[exports.SERVICE_KEY_ENV_VAR] ?? '');
|
|
50
|
+
const baseUrl = normalisedBaseUrl(options.baseUrl ?? process.env[exports.BASE_URL_ENV_VAR] ?? defaults_js_1.DEFAULT_BASE_URL);
|
|
51
|
+
const timeoutMs = options.timeoutMs ?? transport_js_1.DEFAULT_TIMEOUT_MS;
|
|
52
|
+
const fetchImpl = options.fetch ?? globalThis.fetch;
|
|
53
|
+
if (typeof fetchImpl !== 'function') {
|
|
54
|
+
throw new errors_js_1.CrowdSourceConfigurationError('This runtime has no global fetch. Pass one as the `fetch` option.');
|
|
55
|
+
}
|
|
56
|
+
const oxyToken = options.oxyToken;
|
|
57
|
+
const transport = new transport_js_1.Transport({
|
|
58
|
+
baseUrl,
|
|
59
|
+
bearerToken: credential ? () => credential.bearerToken : () => oxyToken(),
|
|
60
|
+
timeoutMs,
|
|
61
|
+
maxAttempts: options.maxAttempts ?? transport_js_1.DEFAULT_MAX_ATTEMPTS,
|
|
62
|
+
fetch: fetchImpl,
|
|
63
|
+
});
|
|
64
|
+
this.applicationId = credential ? credential.applicationId : lazyApplicationId(transport);
|
|
65
|
+
this.reports = new reports_js_1.Reports({
|
|
66
|
+
transport,
|
|
67
|
+
applicationId: this.applicationId,
|
|
68
|
+
environment: options.sandbox === true ? 'sandbox' : 'production',
|
|
69
|
+
});
|
|
70
|
+
this.cases = new cases_js_1.Cases(transport);
|
|
71
|
+
this.decisions = new cases_js_1.Decisions(transport);
|
|
72
|
+
this.webhookEndpoints = new webhookEndpoints_js_1.WebhookEndpoints(transport);
|
|
73
|
+
this.communityNotes = new communityNotes_js_1.CommunityNotes(transport);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
exports.CrowdSource = CrowdSource;
|
|
77
|
+
/**
|
|
78
|
+
* Asks CrowdSource which application this client is, once.
|
|
79
|
+
*
|
|
80
|
+
* Only the Oxy-token path needs this: a service key carries the id inside it.
|
|
81
|
+
* The promise is created at construction and awaited wherever the id is used,
|
|
82
|
+
* so the lookup happens at most once per client and never blocks a caller that
|
|
83
|
+
* does not need it (community notes never do; a report does, because its
|
|
84
|
+
* envelope names the application and the server refuses one that disagrees).
|
|
85
|
+
*
|
|
86
|
+
* A failure is not swallowed into a placeholder id. An envelope carrying the
|
|
87
|
+
* wrong application is refused by the server anyway, and a client that invented
|
|
88
|
+
* one would turn a clear "we could not identify you" into a confusing 403 on
|
|
89
|
+
* every report.
|
|
90
|
+
*/
|
|
91
|
+
function lazyApplicationId(transport) {
|
|
92
|
+
let pending = null;
|
|
93
|
+
const resolve = () => (pending ?? (pending = askApplicationId(transport)));
|
|
94
|
+
// A thenable rather than a promise: nothing is requested until somebody
|
|
95
|
+
// awaits it, so a client that only reads community notes — which never name
|
|
96
|
+
// an application — makes no identity call at all. Awaiting it twice still
|
|
97
|
+
// makes one.
|
|
98
|
+
return { then: (onFulfilled, onRejected) => resolve().then(onFulfilled, onRejected) };
|
|
99
|
+
}
|
|
100
|
+
function askApplicationId(transport) {
|
|
101
|
+
return transport
|
|
102
|
+
.request({ method: 'GET', path: '/v1/applications/me' })
|
|
103
|
+
.then((body) => {
|
|
104
|
+
const applicationId = body?.applicationId;
|
|
105
|
+
if (typeof applicationId !== 'string' || applicationId.length === 0) {
|
|
106
|
+
throw new errors_js_1.CrowdSourceConfigurationError('CrowdSource did not name the application this token belongs to.');
|
|
107
|
+
}
|
|
108
|
+
return applicationId;
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
function normalisedBaseUrl(value) {
|
|
112
|
+
let parsed;
|
|
113
|
+
try {
|
|
114
|
+
parsed = new URL(value);
|
|
115
|
+
}
|
|
116
|
+
catch {
|
|
117
|
+
throw new errors_js_1.CrowdSourceConfigurationError(`'${value}' is not a usable CrowdSource base URL.`);
|
|
118
|
+
}
|
|
119
|
+
if (parsed.protocol !== 'https:' && parsed.hostname !== 'localhost') {
|
|
120
|
+
throw new errors_js_1.CrowdSourceConfigurationError('A CrowdSource base URL must be https. A service credential sent in clear is a credential you have to rotate.');
|
|
121
|
+
}
|
|
122
|
+
// Trailing slashes are stripped so `${baseUrl}/v1/reports` never doubles up.
|
|
123
|
+
return parsed.origin + parsed.pathname.replace(/\/+$/, '');
|
|
124
|
+
}
|
|
125
|
+
//# sourceMappingURL=client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;;;AAEH,yCAA8C;AAC9C,2DAAqD;AACrD,mDAA0E;AAC1E,+CAAiD;AACjD,2CAA4D;AAC5D,6CAAuC;AACvC,iDAKwB;AACxB,+DAAyD;AAEzD,uEAAuE;AAC1D,QAAA,mBAAmB,GAAG,yBAAyB,CAAC;AAE7D,6EAA6E;AAChE,QAAA,gBAAgB,GAAG,sBAAsB,CAAC;AA4CvD,MAAa,WAAW;IAkBtB,YAAY,UAA8B,EAAE;QAC1C;;;;;;;WAOG;QACH,MAAM,UAAU,GAA6B,OAAO,CAAC,QAAQ;YAC3D,CAAC,CAAC,IAAI;YACN,CAAC,CAAC,IAAA,+BAAe,EAAC,OAAO,CAAC,UAAU,IAAI,OAAO,CAAC,GAAG,CAAC,2BAAmB,CAAC,IAAI,EAAE,CAAC,CAAC;QAElF,MAAM,OAAO,GAAG,iBAAiB,CAC/B,OAAO,CAAC,OAAO,IAAI,OAAO,CAAC,GAAG,CAAC,wBAAgB,CAAC,IAAI,8BAAgB,CACrE,CAAC;QACF,MAAM,SAAS,GAAG,OAAO,CAAC,SAAS,IAAI,iCAAkB,CAAC;QAC1D,MAAM,SAAS,GAAG,OAAO,CAAC,KAAK,IAAI,UAAU,CAAC,KAAK,CAAC;QACpD,IAAI,OAAO,SAAS,KAAK,UAAU,EAAE,CAAC;YACpC,MAAM,IAAI,yCAA6B,CACrC,mEAAmE,CACpE,CAAC;QACJ,CAAC;QAED,MAAM,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC;QAClC,MAAM,SAAS,GAAG,IAAI,wBAAS,CAAC;YAC9B,OAAO;YACP,WAAW,EAAE,UAAU,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC,UAAU,CAAC,WAAW,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC,QAAS,EAAE;YAC1E,SAAS;YACT,WAAW,EAAE,OAAO,CAAC,WAAW,IAAI,mCAAoB;YACxD,KAAK,EAAE,SAAS;SACjB,CAAC,CAAC;QAEH,IAAI,CAAC,aAAa,GAAG,UAAU,CAAC,CAAC,CAAC,UAAU,CAAC,aAAa,CAAC,CAAC,CAAC,iBAAiB,CAAC,SAAS,CAAC,CAAC;QAC1F,IAAI,CAAC,OAAO,GAAG,IAAI,oBAAO,CAAC;YACzB,SAAS;YACT,aAAa,EAAE,IAAI,CAAC,aAAa;YACjC,WAAW,EAAE,OAAO,CAAC,OAAO,KAAK,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,YAAY;SACjE,CAAC,CAAC;QACH,IAAI,CAAC,KAAK,GAAG,IAAI,gBAAK,CAAC,SAAS,CAAC,CAAC;QAClC,IAAI,CAAC,SAAS,GAAG,IAAI,oBAAS,CAAC,SAAS,CAAC,CAAC;QAC1C,IAAI,CAAC,gBAAgB,GAAG,IAAI,sCAAgB,CAAC,SAAS,CAAC,CAAC;QACxD,IAAI,CAAC,cAAc,GAAG,IAAI,kCAAc,CAAC,SAAS,CAAC,CAAC;IACtD,CAAC;CACF;AA9DD,kCA8DC;AAGD;;;;;;;;;;;;;GAaG;AACH,SAAS,iBAAiB,CAAC,SAAoB;IAC7C,IAAI,OAAO,GAA2B,IAAI,CAAC;IAC3C,MAAM,OAAO,GAAG,GAAG,EAAE,CAAC,CAAC,OAAO,KAAP,OAAO,GAAK,gBAAgB,CAAC,SAAS,CAAC,EAAC,CAAC;IAChE,wEAAwE;IACxE,4EAA4E;IAC5E,0EAA0E;IAC1E,aAAa;IACb,OAAO,EAAE,IAAI,EAAE,CAAC,WAAW,EAAE,UAAU,EAAE,EAAE,CAAC,OAAO,EAAE,CAAC,IAAI,CAAC,WAAW,EAAE,UAAU,CAAC,EAAqB,CAAC;AAC3G,CAAC;AAED,SAAS,gBAAgB,CAAC,SAAoB;IAC5C,OAAO,SAAS;SACb,OAAO,CAA8B,EAAE,MAAM,EAAE,KAAK,EAAE,IAAI,EAAE,qBAAqB,EAAE,CAAC;SACpF,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE;QACb,MAAM,aAAa,GAAG,IAAI,EAAE,aAAa,CAAC;QAC1C,IAAI,OAAO,aAAa,KAAK,QAAQ,IAAI,aAAa,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACpE,MAAM,IAAI,yCAA6B,CACrC,iEAAiE,CAClE,CAAC;QACJ,CAAC;QACD,OAAO,aAAa,CAAC;IACvB,CAAC,CAAC,CAAC;AACP,CAAC;AAED,SAAS,iBAAiB,CAAC,KAAa;IACtC,IAAI,MAAW,CAAC;IAChB,IAAI,CAAC;QACH,MAAM,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,CAAC;IAC1B,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,IAAI,yCAA6B,CAAC,IAAI,KAAK,yCAAyC,CAAC,CAAC;IAC9F,CAAC;IACD,IAAI,MAAM,CAAC,QAAQ,KAAK,QAAQ,IAAI,MAAM,CAAC,QAAQ,KAAK,WAAW,EAAE,CAAC;QACpE,MAAM,IAAI,yCAA6B,CACrC,8GAA8G,CAC/G,CAAC;IACJ,CAAC;IACD,6EAA6E;IAC7E,OAAO,MAAM,CAAC,MAAM,GAAG,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;AAC7D,CAAC"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { type CommunityNote, type CommunityNoteAssignment, type CommunityNoteAssignmentRequest, type CommunityNoteRatedList, type CommunityNoteRating, type CommunityNoteRatingSubmission, type CommunityNoteSubmission } from '@crowdsource.you/contracts';
|
|
2
|
+
import type { Transport } from './transport.js';
|
|
3
|
+
/**
|
|
4
|
+
* Community notes (`docs/architecture/community-notes.md`): reader-written context
|
|
5
|
+
* under the application's subjects, shown once raters of different viewpoints
|
|
6
|
+
* agree it is helpful.
|
|
7
|
+
*
|
|
8
|
+
* The application acts for its users, naming each by its own opaque principal id.
|
|
9
|
+
* Every write is retry-safe with no key from the caller where one can be derived
|
|
10
|
+
* from what the write IS — one note per writer per subject, one rating per rater
|
|
11
|
+
* per note — and the derived keys hash the principal ids, so an application's
|
|
12
|
+
* user ids never travel in a header a proxy might log. Drawing notes to rate has
|
|
13
|
+
* no such identity (two draws for one rater are two different requests), so that
|
|
14
|
+
* one method takes the key from the caller.
|
|
15
|
+
*/
|
|
16
|
+
export interface CommunityNoteRequestOptions {
|
|
17
|
+
readonly idempotencyKey?: string;
|
|
18
|
+
readonly signal?: AbortSignal;
|
|
19
|
+
}
|
|
20
|
+
export interface CommunityNoteReadOptions {
|
|
21
|
+
readonly signal?: AbortSignal;
|
|
22
|
+
}
|
|
23
|
+
export declare class CommunityNotes {
|
|
24
|
+
private readonly transport;
|
|
25
|
+
constructor(transport: Transport);
|
|
26
|
+
/** Writes a note. A retry of the same writer's note on the same subject returns it. */
|
|
27
|
+
write(submission: CommunityNoteSubmission, options?: CommunityNoteRequestOptions): Promise<CommunityNote>;
|
|
28
|
+
/** Withdraws a note. Only its writer may; anyone else's note answers 404. */
|
|
29
|
+
withdraw(noteId: string, authorPrincipalId: string, options?: CommunityNoteRequestOptions): Promise<CommunityNote>;
|
|
30
|
+
/**
|
|
31
|
+
* Draws notes for one rater to rate — the ONLY way to be allowed to rate one.
|
|
32
|
+
*
|
|
33
|
+
* `idempotencyKey` is required: a retry with the same key returns the same
|
|
34
|
+
* batch, and a new key is a new draw.
|
|
35
|
+
*/
|
|
36
|
+
drawToRate(request: CommunityNoteAssignmentRequest, options: CommunityNoteRequestOptions & {
|
|
37
|
+
readonly idempotencyKey: string;
|
|
38
|
+
}): Promise<CommunityNoteAssignment[]>;
|
|
39
|
+
/** Rates a note assigned to the rater. Final: a second rating is refused. */
|
|
40
|
+
rate(noteId: string, submission: CommunityNoteRatingSubmission, options?: CommunityNoteRequestOptions): Promise<CommunityNoteRating>;
|
|
41
|
+
/** The shown note, if any, for each of up to 50 subjects. */
|
|
42
|
+
shown(externalSubjectIds: readonly string[], options?: CommunityNoteReadOptions): Promise<CommunityNote[]>;
|
|
43
|
+
/** A writer's own notes, newest first. */
|
|
44
|
+
writtenBy(authorPrincipalId: string, options?: CommunityNoteReadOptions): Promise<CommunityNote[]>;
|
|
45
|
+
/** A rater's own ratings, each with the note it rated, newest first. */
|
|
46
|
+
ratedBy(raterPrincipalId: string, options?: CommunityNoteReadOptions): Promise<CommunityNoteRatedList['ratings']>;
|
|
47
|
+
private parse;
|
|
48
|
+
}
|
|
49
|
+
//# sourceMappingURL=communityNotes.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"communityNotes.d.ts","sourceRoot":"","sources":["../src/communityNotes.ts"],"names":[],"mappings":"AAAA,OAAO,EAOL,KAAK,aAAa,EAClB,KAAK,uBAAuB,EAC5B,KAAK,8BAA8B,EACnC,KAAK,sBAAsB,EAC3B,KAAK,mBAAmB,EACxB,KAAK,6BAA6B,EAClC,KAAK,uBAAuB,EAC7B,MAAM,4BAA4B,CAAC;AAKpC,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAEhD;;;;;;;;;;;;GAYG;AAEH,MAAM,WAAW,2BAA2B;IAC1C,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAC;IACjC,QAAQ,CAAC,MAAM,CAAC,EAAE,WAAW,CAAC;CAC/B;AAED,MAAM,WAAW,wBAAwB;IACvC,QAAQ,CAAC,MAAM,CAAC,EAAE,WAAW,CAAC;CAC/B;AAOD,qBAAa,cAAc;IACzB,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAY;gBAE1B,SAAS,EAAE,SAAS;IAIhC,uFAAuF;IACjF,KAAK,CAAC,UAAU,EAAE,uBAAuB,EAAE,OAAO,GAAE,2BAAgC,GAAG,OAAO,CAAC,aAAa,CAAC;IAgBnH,6EAA6E;IACvE,QAAQ,CACZ,MAAM,EAAE,MAAM,EACd,iBAAiB,EAAE,MAAM,EACzB,OAAO,GAAE,2BAAgC,GACxC,OAAO,CAAC,aAAa,CAAC;IAczB;;;;;OAKG;IACG,UAAU,CACd,OAAO,EAAE,8BAA8B,EACvC,OAAO,EAAE,2BAA2B,GAAG;QAAE,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAA;KAAE,GACzE,OAAO,CAAC,uBAAuB,EAAE,CAAC;IAerC,6EAA6E;IACvE,IAAI,CACR,MAAM,EAAE,MAAM,EACd,UAAU,EAAE,6BAA6B,EACzC,OAAO,GAAE,2BAAgC,GACxC,OAAO,CAAC,mBAAmB,CAAC;IAe/B,6DAA6D;IACvD,KAAK,CAAC,kBAAkB,EAAE,SAAS,MAAM,EAAE,EAAE,OAAO,GAAE,wBAA6B,GAAG,OAAO,CAAC,aAAa,EAAE,CAAC;IAepH,0CAA0C;IACpC,SAAS,CAAC,iBAAiB,EAAE,MAAM,EAAE,OAAO,GAAE,wBAA6B,GAAG,OAAO,CAAC,aAAa,EAAE,CAAC;IAa5G,wEAAwE;IAClE,OAAO,CACX,gBAAgB,EAAE,MAAM,EACxB,OAAO,GAAE,wBAA6B,GACrC,OAAO,CAAC,sBAAsB,CAAC,SAAS,CAAC,CAAC;IAa7C,OAAO,CAAC,KAAK;CAUd"}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.CommunityNotes = void 0;
|
|
4
|
+
const contracts_1 = require("@crowdsource.you/contracts");
|
|
5
|
+
const digest_js_1 = require("./digest.js");
|
|
6
|
+
const errors_js_1 = require("./errors.js");
|
|
7
|
+
/** A stable, header-safe digest of the parts that identify a write. */
|
|
8
|
+
function digestOf(...parts) {
|
|
9
|
+
return (0, digest_js_1.sha256Digest)(JSON.stringify(parts)).replace('sha256:', '');
|
|
10
|
+
}
|
|
11
|
+
class CommunityNotes {
|
|
12
|
+
constructor(transport) {
|
|
13
|
+
this.transport = transport;
|
|
14
|
+
}
|
|
15
|
+
/** Writes a note. A retry of the same writer's note on the same subject returns it. */
|
|
16
|
+
async write(submission, options = {}) {
|
|
17
|
+
return this.parse(contracts_1.CommunityNoteSchema, await this.transport.request({
|
|
18
|
+
method: 'POST',
|
|
19
|
+
path: '/v1/community-notes',
|
|
20
|
+
body: submission,
|
|
21
|
+
idempotencyKey: options.idempotencyKey ??
|
|
22
|
+
`community-note.${digestOf(submission.externalSubjectId, submission.authorPrincipalId)}`,
|
|
23
|
+
signal: options.signal,
|
|
24
|
+
}), 'a community note');
|
|
25
|
+
}
|
|
26
|
+
/** Withdraws a note. Only its writer may; anyone else's note answers 404. */
|
|
27
|
+
async withdraw(noteId, authorPrincipalId, options = {}) {
|
|
28
|
+
return this.parse(contracts_1.CommunityNoteSchema, await this.transport.request({
|
|
29
|
+
method: 'POST',
|
|
30
|
+
path: `/v1/community-notes/${encodeURIComponent(noteId)}/withdraw`,
|
|
31
|
+
body: { authorPrincipalId },
|
|
32
|
+
idempotencyKey: options.idempotencyKey ?? `community-note-withdrawal.${digestOf(noteId)}`,
|
|
33
|
+
signal: options.signal,
|
|
34
|
+
}), 'a community note');
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Draws notes for one rater to rate — the ONLY way to be allowed to rate one.
|
|
38
|
+
*
|
|
39
|
+
* `idempotencyKey` is required: a retry with the same key returns the same
|
|
40
|
+
* batch, and a new key is a new draw.
|
|
41
|
+
*/
|
|
42
|
+
async drawToRate(request, options) {
|
|
43
|
+
const batch = this.parse(contracts_1.CommunityNoteAssignmentBatchSchema, await this.transport.request({
|
|
44
|
+
method: 'POST',
|
|
45
|
+
path: '/v1/community-notes/assignments',
|
|
46
|
+
body: request,
|
|
47
|
+
idempotencyKey: options.idempotencyKey,
|
|
48
|
+
signal: options.signal,
|
|
49
|
+
}), 'a batch of community note assignments');
|
|
50
|
+
return batch.assignments;
|
|
51
|
+
}
|
|
52
|
+
/** Rates a note assigned to the rater. Final: a second rating is refused. */
|
|
53
|
+
async rate(noteId, submission, options = {}) {
|
|
54
|
+
return this.parse(contracts_1.CommunityNoteRatingSchema, await this.transport.request({
|
|
55
|
+
method: 'POST',
|
|
56
|
+
path: `/v1/community-notes/${encodeURIComponent(noteId)}/ratings`,
|
|
57
|
+
body: submission,
|
|
58
|
+
idempotencyKey: options.idempotencyKey ?? `community-note-rating.${digestOf(noteId, submission.raterPrincipalId)}`,
|
|
59
|
+
signal: options.signal,
|
|
60
|
+
}), 'a community note rating');
|
|
61
|
+
}
|
|
62
|
+
/** The shown note, if any, for each of up to 50 subjects. */
|
|
63
|
+
async shown(externalSubjectIds, options = {}) {
|
|
64
|
+
const subjects = [...new Set(externalSubjectIds)];
|
|
65
|
+
if (subjects.length === 0)
|
|
66
|
+
return [];
|
|
67
|
+
const response = this.parse(contracts_1.ShownCommunityNotesSchema, await this.transport.request({
|
|
68
|
+
method: 'GET',
|
|
69
|
+
path: `/v1/community-notes/shown?subjects=${subjects.map(encodeURIComponent).join(',')}`,
|
|
70
|
+
signal: options.signal,
|
|
71
|
+
}), 'a shown community notes lookup');
|
|
72
|
+
return response.notes;
|
|
73
|
+
}
|
|
74
|
+
/** A writer's own notes, newest first. */
|
|
75
|
+
async writtenBy(authorPrincipalId, options = {}) {
|
|
76
|
+
const response = this.parse(contracts_1.CommunityNoteListSchema, await this.transport.request({
|
|
77
|
+
method: 'GET',
|
|
78
|
+
path: `/v1/community-notes/principals/${encodeURIComponent(authorPrincipalId)}/notes`,
|
|
79
|
+
signal: options.signal,
|
|
80
|
+
}), 'a list of community notes');
|
|
81
|
+
return response.notes;
|
|
82
|
+
}
|
|
83
|
+
/** A rater's own ratings, each with the note it rated, newest first. */
|
|
84
|
+
async ratedBy(raterPrincipalId, options = {}) {
|
|
85
|
+
const response = this.parse(contracts_1.CommunityNoteRatedListSchema, await this.transport.request({
|
|
86
|
+
method: 'GET',
|
|
87
|
+
path: `/v1/community-notes/principals/${encodeURIComponent(raterPrincipalId)}/ratings`,
|
|
88
|
+
signal: options.signal,
|
|
89
|
+
}), 'a list of community note ratings');
|
|
90
|
+
return response.ratings;
|
|
91
|
+
}
|
|
92
|
+
parse(schema, response, what) {
|
|
93
|
+
const parsed = schema.safeParse(response);
|
|
94
|
+
if (!parsed.success) {
|
|
95
|
+
throw new errors_js_1.CrowdSourceTransportError(`CrowdSource answered with ${what} this client does not recognise.`, {
|
|
96
|
+
retryable: false,
|
|
97
|
+
cause: parsed.error,
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
return parsed.data;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
exports.CommunityNotes = CommunityNotes;
|
|
104
|
+
//# sourceMappingURL=communityNotes.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"communityNotes.js","sourceRoot":"","sources":["../src/communityNotes.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The service key, and the only place `applicationId` may come from.
|
|
3
|
+
*
|
|
4
|
+
* Appendix F: "`applicationId` comes from the credential, never from the request
|
|
5
|
+
* body." The backend already honours that — it derives the tenant from the
|
|
6
|
+
* presented token and 403s an envelope naming a different application. But the
|
|
7
|
+
* Case Envelope contract still REQUIRES `applicationId` on the wire, so a client
|
|
8
|
+
* has to put one there, and the question this module answers is where it gets it
|
|
9
|
+
* from without ever asking the integrator.
|
|
10
|
+
*
|
|
11
|
+
* The answer is that it comes out of the credential, literally. CrowdSource
|
|
12
|
+
* issues three values together (`provisioning.service.ts`
|
|
13
|
+
* `IssuedCredential`): the application the credential belongs to, the credential
|
|
14
|
+
* id, and the secret. Joined into ONE opaque string, they are a single
|
|
15
|
+
* environment variable an integrator pastes and never reads — and the
|
|
16
|
+
* `applicationId` in an envelope is then a value the client READ OFF THE
|
|
17
|
+
* CREDENTIAL, not a parameter anyone can pass. There is no `applicationId`
|
|
18
|
+
* option on the client, none on any method, and none an envelope input can
|
|
19
|
+
* carry; supplying one is not "discouraged", it is unexpressible.
|
|
20
|
+
*
|
|
21
|
+
* `:` is the separator because the contract's identifier grammar excludes it and
|
|
22
|
+
* credential secrets are base64url, so the split is unambiguous for every value
|
|
23
|
+
* CrowdSource can issue. A `.` separator would not be: `IdentifierSchema` allows
|
|
24
|
+
* dots inside an id.
|
|
25
|
+
*
|
|
26
|
+
* NOTE FOR THE CONSOLE: `issueApplicationCredential` returns the three values
|
|
27
|
+
* separately and the bearer token as `<credentialId>.<secret>`. Whatever surface
|
|
28
|
+
* shows an integrator their key must show `formatServiceKey(issued)` — the
|
|
29
|
+
* composite below — or the integrator has two values to configure instead of
|
|
30
|
+
* one, and the client has no way to know its own application.
|
|
31
|
+
*/
|
|
32
|
+
/** The credential CrowdSource issues, parsed. */
|
|
33
|
+
export interface ServiceCredential {
|
|
34
|
+
/** The application this credential belongs to. The tenant, and its only source. */
|
|
35
|
+
readonly applicationId: string;
|
|
36
|
+
readonly credentialId: string;
|
|
37
|
+
/**
|
|
38
|
+
* The bearer token the API authenticates — `<credentialId>.<secret>`, exactly
|
|
39
|
+
* what `credential.service.ts` parses. The secret is never held on its own,
|
|
40
|
+
* so nothing in this client can log or serialise it by touching a field.
|
|
41
|
+
*/
|
|
42
|
+
readonly bearerToken: string;
|
|
43
|
+
}
|
|
44
|
+
/** The three values `issueApplicationCredential` returns, as one opaque key. */
|
|
45
|
+
export declare function formatServiceKey(issued: {
|
|
46
|
+
applicationId: string;
|
|
47
|
+
credentialId: string;
|
|
48
|
+
secret: string;
|
|
49
|
+
}): string;
|
|
50
|
+
/**
|
|
51
|
+
* Parses a service key.
|
|
52
|
+
*
|
|
53
|
+
* Every rejection names which part is wrong and none of them echoes the secret:
|
|
54
|
+
* a configuration error is read from a log, and a log line is the second most
|
|
55
|
+
* common way a credential leaks after a screenshot.
|
|
56
|
+
*/
|
|
57
|
+
export declare function parseServiceKey(serviceKey: string): ServiceCredential;
|
|
58
|
+
//# sourceMappingURL=credential.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"credential.d.ts","sourceRoot":"","sources":["../src/credential.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAQH,iDAAiD;AACjD,MAAM,WAAW,iBAAiB;IAChC,mFAAmF;IACnF,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B;;;;OAIG;IACH,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;CAC9B;AAED,gFAAgF;AAChF,wBAAgB,gBAAgB,CAAC,MAAM,EAAE;IACvC,aAAa,EAAE,MAAM,CAAC;IACtB,YAAY,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,MAAM,CAAC;CAChB,GAAG,MAAM,CAET;AAED;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,UAAU,EAAE,MAAM,GAAG,iBAAiB,CAgCrE"}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* The service key, and the only place `applicationId` may come from.
|
|
4
|
+
*
|
|
5
|
+
* Appendix F: "`applicationId` comes from the credential, never from the request
|
|
6
|
+
* body." The backend already honours that — it derives the tenant from the
|
|
7
|
+
* presented token and 403s an envelope naming a different application. But the
|
|
8
|
+
* Case Envelope contract still REQUIRES `applicationId` on the wire, so a client
|
|
9
|
+
* has to put one there, and the question this module answers is where it gets it
|
|
10
|
+
* from without ever asking the integrator.
|
|
11
|
+
*
|
|
12
|
+
* The answer is that it comes out of the credential, literally. CrowdSource
|
|
13
|
+
* issues three values together (`provisioning.service.ts`
|
|
14
|
+
* `IssuedCredential`): the application the credential belongs to, the credential
|
|
15
|
+
* id, and the secret. Joined into ONE opaque string, they are a single
|
|
16
|
+
* environment variable an integrator pastes and never reads — and the
|
|
17
|
+
* `applicationId` in an envelope is then a value the client READ OFF THE
|
|
18
|
+
* CREDENTIAL, not a parameter anyone can pass. There is no `applicationId`
|
|
19
|
+
* option on the client, none on any method, and none an envelope input can
|
|
20
|
+
* carry; supplying one is not "discouraged", it is unexpressible.
|
|
21
|
+
*
|
|
22
|
+
* `:` is the separator because the contract's identifier grammar excludes it and
|
|
23
|
+
* credential secrets are base64url, so the split is unambiguous for every value
|
|
24
|
+
* CrowdSource can issue. A `.` separator would not be: `IdentifierSchema` allows
|
|
25
|
+
* dots inside an id.
|
|
26
|
+
*
|
|
27
|
+
* NOTE FOR THE CONSOLE: `issueApplicationCredential` returns the three values
|
|
28
|
+
* separately and the bearer token as `<credentialId>.<secret>`. Whatever surface
|
|
29
|
+
* shows an integrator their key must show `formatServiceKey(issued)` — the
|
|
30
|
+
* composite below — or the integrator has two values to configure instead of
|
|
31
|
+
* one, and the client has no way to know its own application.
|
|
32
|
+
*/
|
|
33
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
34
|
+
exports.formatServiceKey = formatServiceKey;
|
|
35
|
+
exports.parseServiceKey = parseServiceKey;
|
|
36
|
+
const contracts_1 = require("@crowdsource.you/contracts");
|
|
37
|
+
const errors_js_1 = require("./errors.js");
|
|
38
|
+
const SERVICE_KEY_SEPARATOR = ':';
|
|
39
|
+
/** The three values `issueApplicationCredential` returns, as one opaque key. */
|
|
40
|
+
function formatServiceKey(issued) {
|
|
41
|
+
return [issued.applicationId, issued.credentialId, issued.secret].join(SERVICE_KEY_SEPARATOR);
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Parses a service key.
|
|
45
|
+
*
|
|
46
|
+
* Every rejection names which part is wrong and none of them echoes the secret:
|
|
47
|
+
* a configuration error is read from a log, and a log line is the second most
|
|
48
|
+
* common way a credential leaks after a screenshot.
|
|
49
|
+
*/
|
|
50
|
+
function parseServiceKey(serviceKey) {
|
|
51
|
+
const trimmed = serviceKey.trim();
|
|
52
|
+
if (!trimmed) {
|
|
53
|
+
throw new errors_js_1.CrowdSourceConfigurationError('The CrowdSource service key is empty.');
|
|
54
|
+
}
|
|
55
|
+
const parts = trimmed.split(SERVICE_KEY_SEPARATOR);
|
|
56
|
+
if (parts.length !== 3) {
|
|
57
|
+
throw new errors_js_1.CrowdSourceConfigurationError(`A CrowdSource service key is three colon-separated parts (applicationId:credentialId:secret); this one has ${parts.length}.`);
|
|
58
|
+
}
|
|
59
|
+
const [applicationId, credentialId, secret] = parts;
|
|
60
|
+
if (!contracts_1.IdentifierSchema.safeParse(applicationId).success) {
|
|
61
|
+
throw new errors_js_1.CrowdSourceConfigurationError('The first part of a CrowdSource service key must be the applicationId it was issued for.');
|
|
62
|
+
}
|
|
63
|
+
if (!contracts_1.IdentifierSchema.safeParse(credentialId).success) {
|
|
64
|
+
throw new errors_js_1.CrowdSourceConfigurationError('The second part of a CrowdSource service key must be the credentialId it was issued for.');
|
|
65
|
+
}
|
|
66
|
+
if (!secret) {
|
|
67
|
+
throw new errors_js_1.CrowdSourceConfigurationError('The third part of a CrowdSource service key must be the credential secret.');
|
|
68
|
+
}
|
|
69
|
+
return { applicationId, credentialId, bearerToken: `${credentialId}.${secret}` };
|
|
70
|
+
}
|
|
71
|
+
//# sourceMappingURL=credential.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"credential.js","sourceRoot":"","sources":["../src/credential.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;;AAsBH,4CAMC;AASD,0CAgCC;AAnED,0DAA8D;AAE9D,2CAA4D;AAE5D,MAAM,qBAAqB,GAAG,GAAG,CAAC;AAelC,gFAAgF;AAChF,SAAgB,gBAAgB,CAAC,MAIhC;IACC,OAAO,CAAC,MAAM,CAAC,aAAa,EAAE,MAAM,CAAC,YAAY,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,qBAAqB,CAAC,CAAC;AAChG,CAAC;AAED;;;;;;GAMG;AACH,SAAgB,eAAe,CAAC,UAAkB;IAChD,MAAM,OAAO,GAAG,UAAU,CAAC,IAAI,EAAE,CAAC;IAClC,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,MAAM,IAAI,yCAA6B,CAAC,uCAAuC,CAAC,CAAC;IACnF,CAAC;IAED,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,qBAAqB,CAAC,CAAC;IACnD,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACvB,MAAM,IAAI,yCAA6B,CACrC,8GAA8G,KAAK,CAAC,MAAM,GAAG,CAC9H,CAAC;IACJ,CAAC;IAED,MAAM,CAAC,aAAa,EAAE,YAAY,EAAE,MAAM,CAAC,GAAG,KAAK,CAAC;IAEpD,IAAI,CAAC,4BAAgB,CAAC,SAAS,CAAC,aAAa,CAAC,CAAC,OAAO,EAAE,CAAC;QACvD,MAAM,IAAI,yCAA6B,CACrC,0FAA0F,CAC3F,CAAC;IACJ,CAAC;IACD,IAAI,CAAC,4BAAgB,CAAC,SAAS,CAAC,YAAY,CAAC,CAAC,OAAO,EAAE,CAAC;QACtD,MAAM,IAAI,yCAA6B,CACrC,0FAA0F,CAC3F,CAAC;IACJ,CAAC;IACD,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,MAAM,IAAI,yCAA6B,CACrC,4EAA4E,CAC7E,CAAC;IACJ,CAAC;IAED,OAAO,EAAE,aAAa,EAAE,YAAY,EAAE,WAAW,EAAE,GAAG,YAAY,IAAI,MAAM,EAAE,EAAE,CAAC;AACnF,CAAC"}
|