@cookiemunch/sdk 0.5.1 → 0.6.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/dist/index.cjs CHANGED
@@ -20,15 +20,23 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
20
20
  // src/index.ts
21
21
  var src_exports = {};
22
22
  __export(src_exports, {
23
+ CANARY_ISSUER: () => CANARY_ISSUER,
24
+ CONFORMANCE_RULES: () => CONFORMANCE_RULES,
23
25
  CONSENT_COOKIE_NAME: () => CONSENT_COOKIE_NAME,
24
26
  CookieMunchApiError: () => CookieMunchApiError,
27
+ DEFAULT_JWKS_URL: () => DEFAULT_JWKS_URL,
25
28
  SDK_VERSION: () => SDK_VERSION,
29
+ canaryIdentityFor: () => canaryIdentityFor,
30
+ consentSignaturePayload: () => consentSignaturePayload,
26
31
  createCookieMunch: () => createCookieMunch,
27
32
  getConsent: () => getConsent,
28
33
  isAllowed: () => isAllowed,
29
34
  mintConsentSyncToken: () => mintConsentSyncToken,
30
35
  parseConsentValue: () => parseConsentValue,
31
36
  readConsent: () => readConsent,
37
+ runExecutorConformance: () => runExecutorConformance,
38
+ verifyDecisionSignature: () => verifyDecisionSignature,
39
+ verifyToken: () => verifyToken,
32
40
  verifyWebhookSignature: () => verifyWebhookSignature
33
41
  });
34
42
  module.exports = __toCommonJS(src_exports);
@@ -89,6 +97,7 @@ function normalize(o) {
89
97
  for (const [k, v] of Object.entries(r.custom)) if (bool(v)) custom[k] = v;
90
98
  if (Object.keys(custom).length > 0) out.custom = custom;
91
99
  }
100
+ if (typeof r.sig === "string") out.sig = r.sig;
92
101
  return out;
93
102
  }
94
103
  function parseConsentValue(raw) {
@@ -108,6 +117,12 @@ function parseConsentValue(raw) {
108
117
  }
109
118
  var DAY_MS = 24 * 60 * 60 * 1e3;
110
119
  function readConsent(source, opts = {}) {
120
+ const consent = readUnsigned(source, opts);
121
+ if (opts.requireSigned !== true) return consent;
122
+ if (!consent?.sig) return Promise.resolve(null);
123
+ return verifyDecisionSignature(consent, opts).then((ok) => ok ? consent : null);
124
+ }
125
+ function readUnsigned(source, opts) {
111
126
  const raw = cookieValue(cookieHeaderOf(source), opts.cookieName ?? CONSENT_COOKIE_NAME);
112
127
  const consent = raw ? parseConsentValue(raw) : null;
113
128
  if (!consent) return null;
@@ -128,6 +143,78 @@ function isAllowed(consent, category) {
128
143
  if (category === "marketing") return consent.marketing === true;
129
144
  return consent.custom?.[category] === true;
130
145
  }
146
+ var DEFAULT_JWKS_URL = "https://api.cookiemunch.net/v1/partner/jwks.json";
147
+ function consentSignaturePayload(d) {
148
+ const b = (v) => v ? "1" : "0";
149
+ const custom = d.custom ?? {};
150
+ const named = Object.keys(custom).sort().map((k) => `${k}=${b(custom[k])}`).join(",");
151
+ return `cmc1|${d.stamp}|${d.ver}|${d.utc}|${b(d.preferences)}${b(d.statistics)}${b(d.marketing)}|${named}`;
152
+ }
153
+ var jwksCache = /* @__PURE__ */ new Map();
154
+ var JWKS_TTL_MS = 60 * 60 * 1e3;
155
+ async function keySet(opts, kid) {
156
+ if (opts.jwks) return opts.jwks;
157
+ const url = opts.jwksUrl ?? DEFAULT_JWKS_URL;
158
+ const hit = jwksCache.get(url);
159
+ if (hit && Date.now() - hit.at < JWKS_TTL_MS && hit.set.keys.some((k) => k.kid === kid)) return hit.set;
160
+ const f = opts.fetch ?? globalThis.fetch;
161
+ if (!f) throw new Error("no fetch available to load the key set; pass `jwks`");
162
+ const res = await f(url);
163
+ if (!res.ok) throw new Error(`key set ${url} answered ${res.status}`);
164
+ const set = await res.json();
165
+ if (!set || !Array.isArray(set.keys)) throw new Error(`key set ${url} is not a JWKS`);
166
+ jwksCache.set(url, { at: Date.now(), set });
167
+ return set;
168
+ }
169
+ function b64urlBytes(s) {
170
+ const b64 = s.replace(/-/g, "+").replace(/_/g, "/") + "===".slice((s.length + 3) % 4);
171
+ const bin = atob(b64);
172
+ const out = new Uint8Array(bin.length);
173
+ for (let i = 0; i < bin.length; i++) out[i] = bin.charCodeAt(i);
174
+ return out;
175
+ }
176
+ async function edVerify(jwk, data, signature) {
177
+ const subtle = globalThis.crypto?.subtle;
178
+ if (!subtle) throw new Error("WebCrypto is unavailable in this runtime");
179
+ if (jwk.kty !== "OKP" || jwk.crv !== "Ed25519" || !jwk.x) return false;
180
+ const key = await subtle.importKey("jwk", { kty: "OKP", crv: "Ed25519", x: jwk.x }, { name: "Ed25519" }, false, ["verify"]);
181
+ return subtle.verify({ name: "Ed25519" }, key, b64urlBytes(signature), new TextEncoder().encode(data));
182
+ }
183
+ async function verifyDecisionSignature(consent, opts = {}) {
184
+ const dot = consent.sig?.lastIndexOf(".") ?? -1;
185
+ if (!consent.sig || dot <= 0) return false;
186
+ const kid = consent.sig.slice(0, dot);
187
+ try {
188
+ const jwk = (await keySet(opts, kid)).keys.find((k) => k.kid === kid);
189
+ if (!jwk) return false;
190
+ return await edVerify(jwk, consentSignaturePayload(consent), consent.sig.slice(dot + 1));
191
+ } catch {
192
+ return false;
193
+ }
194
+ }
195
+ async function verifyToken(token, opts = {}) {
196
+ const parts = typeof token === "string" ? token.split(".") : [];
197
+ if (parts.length !== 3) throw new Error("not a JWT");
198
+ const [h, p, s] = parts;
199
+ let header;
200
+ let claims;
201
+ try {
202
+ header = JSON.parse(new TextDecoder().decode(b64urlBytes(h)));
203
+ claims = JSON.parse(new TextDecoder().decode(b64urlBytes(p)));
204
+ } catch {
205
+ throw new Error("malformed token");
206
+ }
207
+ if (header.alg !== "EdDSA" || !header.kid) throw new Error("unexpected token algorithm");
208
+ const jwk = (await keySet(opts, header.kid)).keys.find((k) => k.kid === header.kid);
209
+ if (!jwk || !await edVerify(jwk, `${h}.${p}`, s)) throw new Error("bad signature");
210
+ if (claims.token_use !== "consent") throw new Error("not a consent token");
211
+ const now = (opts.now ?? Date.now()) / 1e3;
212
+ const leeway = opts.leewaySeconds ?? 30;
213
+ if (typeof claims.exp !== "number" || now > claims.exp + leeway) throw new Error("expired");
214
+ if (typeof claims.iat === "number" && claims.iat > now + leeway) throw new Error("issued in the future");
215
+ if (opts.cbid !== void 0 && (claims.cbid !== opts.cbid || claims.aud !== `cookiemunch:${opts.cbid}`)) throw new Error("token is for another site");
216
+ return claims;
217
+ }
131
218
 
132
219
  // src/webhooks.ts
133
220
  var import_node_crypto = require("crypto");
@@ -164,8 +251,183 @@ function mintConsentSyncToken(input) {
164
251
  return `cms1.${payload}.${sig}`;
165
252
  }
166
253
 
254
+ // src/executor-conformance.ts
255
+ var CANARY_ISSUER = "https://canary.cookiemunch.net";
256
+ var CANARY_EMAIL_RE = /^canary-([0-9a-f]{16})@canary\.cookiemunch\.net$/;
257
+ function canaryIdentityFor(canaryEmail) {
258
+ const m = CANARY_EMAIL_RE.exec(canaryEmail.trim().toLowerCase());
259
+ if (!m) throw new Error("canaryEmail must be the canary address canary-<16 hex>@canary.cookiemunch.net");
260
+ return { iss: CANARY_ISSUER, sub: `canary_${m[1]}`, ext: `canary:${m[1]}` };
261
+ }
262
+ var CONFORMANCE_RULES = [
263
+ { id: "auth.required", title: "A request without the credential is refused (401 or 403)." },
264
+ { id: "lookup.email.found", title: "The canary subject is found by its email." },
265
+ { id: "lookup.identity.found", title: "The canary subject is found by its identity alone (iss + sub), and it is the same subject." },
266
+ { id: "lookup.unknown.no_match", title: 'An address nobody has answers { subjectId: null, status: "no_match" }.' },
267
+ { id: "lookup.identity_beats_email", title: "An identity that matches no one is never matched through the email of someone else." },
268
+ { id: "requests.bad_body.400", title: "A request with an unknown type answers 400 { error, field }." },
269
+ { id: "requests.alias.canonical_type", title: 'An alias (erasure) is accepted and answered with canonicalType "deletion".' },
270
+ { id: "requests.execute_after.scheduled", title: 'A deletion with a future executeAfter is "scheduled" with eta equal to executeAfter \u2014 nothing is deleted before it.' },
271
+ { id: "requests.idempotency.replay", title: "The same Idempotency-Key with the same body returns the same request, not a second one." },
272
+ { id: "requests.idempotency.mismatch_409", title: "The same Idempotency-Key with a different body answers 409." },
273
+ { id: "requests.get", title: "GET /privacy/requests/{id} returns the request, still scheduled." },
274
+ { id: "requests.unknown.404", title: "An unknown request id answers 404." },
275
+ { id: "requests.cancel.before_execute_after", title: 'A cancel before executeAfter answers status "cancelled".' },
276
+ { id: "requests.cancel.keeps_data", title: "After a cancel the subject is still there: a lookup finds it." },
277
+ { id: "version.header", title: "Answers carry X-Version (the highest contract version the executor supports)." }
278
+ ];
279
+ function randomTag() {
280
+ const bytes = new Uint8Array(8);
281
+ const c = globalThis.crypto;
282
+ if (c?.getRandomValues) c.getRandomValues(bytes);
283
+ else for (let i = 0; i < bytes.length; i++) bytes[i] = Math.floor(Math.random() * 256);
284
+ return Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
285
+ }
286
+ function field(json, key) {
287
+ return json && typeof json === "object" ? json[key] : void 0;
288
+ }
289
+ async function runExecutorConformance(o) {
290
+ const doFetch = o.fetch ?? fetch;
291
+ const now = o.now ?? (() => Date.now());
292
+ const base = o.baseUrl.replace(/\/+$/, "") + (o.prefix ?? "/privacy");
293
+ let url;
294
+ try {
295
+ url = new URL(base);
296
+ } catch {
297
+ throw new Error("baseUrl must be an absolute URL");
298
+ }
299
+ if (url.protocol !== "https:" && !(url.protocol === "http:" && /^(localhost|127\.0\.0\.1|\[::1\])$/.test(url.hostname))) {
300
+ throw new Error("baseUrl must be https (http only for localhost)");
301
+ }
302
+ const identity = canaryIdentityFor(o.canaryEmail);
303
+ const canaryEmail = o.canaryEmail.trim().toLowerCase();
304
+ const auth = o.auth ?? { header: "Authorization", format: "Bearer {secret}" };
305
+ const run = o.runId ?? randomTag();
306
+ const results = /* @__PURE__ */ new Map();
307
+ let version;
308
+ const set = (id, status, detail) => {
309
+ const rule = CONFORMANCE_RULES.find((r) => r.id === id);
310
+ results.set(id, { id, title: rule.title, status, ...detail ? { detail } : {} });
311
+ };
312
+ const pass = (id) => set(id, "pass");
313
+ const fail = (id, detail) => set(id, "fail", detail);
314
+ const skip = (id, detail) => set(id, "skip", detail);
315
+ async function call(method, path, body, extra = {}, withAuth = true) {
316
+ const headers = {
317
+ accept: "application/json",
318
+ "X-Version": "2",
319
+ ...withAuth ? { [auth.header]: auth.format.replace("{secret}", o.secret) } : {},
320
+ ...body !== void 0 ? { "content-type": "application/json" } : {},
321
+ ...extra
322
+ };
323
+ try {
324
+ const res = await doFetch(base + path, { method, headers, ...body !== void 0 ? { body: JSON.stringify(body) } : {} });
325
+ const text = await res.text().catch(() => "");
326
+ let json;
327
+ try {
328
+ json = text ? JSON.parse(text) : void 0;
329
+ } catch {
330
+ json = void 0;
331
+ }
332
+ const v = res.headers.get("x-version");
333
+ if (v && !version) version = v;
334
+ return { status: res.status, json, version: v };
335
+ } catch (err) {
336
+ return { status: 0, json: void 0, version: null, error: err instanceof Error ? err.message : String(err) };
337
+ }
338
+ }
339
+ const said = (a) => a.status === 0 ? `unreachable: ${a.error}` : `HTTP ${a.status} ${JSON.stringify(a.json ?? null).slice(0, 200)}`;
340
+ const key = (rule) => `conformance:${run}:${rule}`;
341
+ const anon = await call("POST", "/lookup", { emails: [canaryEmail] }, {}, false);
342
+ if (anon.status === 0) {
343
+ for (const r of CONFORMANCE_RULES) skip(r.id, said(anon));
344
+ fail("auth.required", said(anon));
345
+ return report();
346
+ }
347
+ if (anon.status === 401 || anon.status === 403) pass("auth.required");
348
+ else fail("auth.required", `without a credential: ${said(anon)}`);
349
+ const byEmail = await call("POST", "/lookup", { emails: [canaryEmail] }, { "Idempotency-Key": key("lookup-email") });
350
+ const subjectId = field(byEmail.json, "subjectId");
351
+ if (byEmail.status === 200 && field(byEmail.json, "status") === "found" && typeof subjectId === "string" && subjectId) pass("lookup.email.found");
352
+ else fail("lookup.email.found", `the canary address: ${said(byEmail)}`);
353
+ const byIdentity = await call("POST", "/lookup", { emails: [], identity, atlas: identity }, { "Idempotency-Key": key("lookup-identity") });
354
+ if (byIdentity.status === 200 && field(byIdentity.json, "status") === "found" && field(byIdentity.json, "subjectId") === subjectId && typeof subjectId === "string") {
355
+ pass("lookup.identity.found");
356
+ } else {
357
+ fail("lookup.identity.found", `the canary identity ${identity.sub}: ${said(byIdentity)}${typeof subjectId === "string" ? ` (by email it was ${subjectId})` : ""}`);
358
+ }
359
+ const nobody = await call("POST", "/lookup", { emails: [`nobody-${run}@canary.cookiemunch.net`] }, { "Idempotency-Key": key("lookup-nobody") });
360
+ if (nobody.status === 200 && field(nobody.json, "status") === "no_match" && field(nobody.json, "subjectId") === null) pass("lookup.unknown.no_match");
361
+ else fail("lookup.unknown.no_match", said(nobody));
362
+ const stranger = { iss: identity.iss, sub: `canary_other_${run}` };
363
+ const crossed = await call("POST", "/lookup", { emails: [canaryEmail], identity: stranger, atlas: stranger }, { "Idempotency-Key": key("lookup-crossed") });
364
+ if (crossed.status === 200 && field(crossed.json, "status") === "no_match") pass("lookup.identity_beats_email");
365
+ else {
366
+ fail(
367
+ "lookup.identity_beats_email",
368
+ `an identity nobody has (${stranger.sub}) with the canary's email: ${said(crossed)} \u2014 the email belongs to a different identity, so this must be no_match`
369
+ );
370
+ }
371
+ const bad = await call("POST", "/requests", { subjectId: subjectId ?? "x", type: "teleport", caseId: `conformance-${run}` }, { "Idempotency-Key": key("bad-body") });
372
+ if (bad.status === 400 && typeof field(bad.json, "error") === "string" && typeof field(bad.json, "field") === "string") pass("requests.bad_body.400");
373
+ else fail("requests.bad_body.400", said(bad));
374
+ if (typeof subjectId !== "string") {
375
+ for (const id of ["requests.alias.canonical_type", "requests.execute_after.scheduled", "requests.idempotency.replay", "requests.idempotency.mismatch_409", "requests.get", "requests.cancel.before_execute_after", "requests.cancel.keeps_data"]) {
376
+ skip(id, "needs the canary subject, which the lookup did not find");
377
+ }
378
+ } else {
379
+ const executeAfter = new Date(Math.ceil((now() + 36e5) / 1e3) * 1e3).toISOString();
380
+ const body = { subjectId, type: "erasure", executeAfter, caseId: `conformance-${run}`, reason: "Cookie Munch conformance check (canary subject)" };
381
+ const opened = await call("POST", "/requests", body, { "Idempotency-Key": key("open") });
382
+ const id = field(opened.json, "id");
383
+ const st = field(opened.json, "status");
384
+ if ((opened.status === 200 || opened.status === 201) && field(opened.json, "canonicalType") === "deletion") pass("requests.alias.canonical_type");
385
+ else fail("requests.alias.canonical_type", `type "erasure": ${said(opened)}`);
386
+ const eta = field(opened.json, "eta");
387
+ if ((opened.status === 200 || opened.status === 201) && st === "scheduled" && typeof eta === "string" && Date.parse(eta) === Date.parse(executeAfter)) {
388
+ pass("requests.execute_after.scheduled");
389
+ } else if (st === "completed" || st === "completed_with_retention" || st === "processing") {
390
+ fail("requests.execute_after.scheduled", `executeAfter is ${executeAfter}, an hour away, and the executor answered "${String(st)}": it acted before executeAfter`);
391
+ } else {
392
+ fail("requests.execute_after.scheduled", `expected status "scheduled" with eta ${executeAfter}: ${said(opened)}`);
393
+ }
394
+ const replay = await call("POST", "/requests", body, { "Idempotency-Key": key("open") });
395
+ if (typeof id === "string" && (replay.status === 200 || replay.status === 201) && field(replay.json, "id") === id) pass("requests.idempotency.replay");
396
+ else fail("requests.idempotency.replay", `a retry with the same key: ${said(replay)}${typeof id === "string" ? ` (first answer id ${id})` : ""}`);
397
+ const mismatch = await call("POST", "/requests", { ...body, reason: "a different body" }, { "Idempotency-Key": key("open") });
398
+ if (mismatch.status === 409) pass("requests.idempotency.mismatch_409");
399
+ else fail("requests.idempotency.mismatch_409", `the same key with a different body: ${said(mismatch)}`);
400
+ if (typeof id !== "string") {
401
+ skip("requests.get", "no request id came back");
402
+ skip("requests.cancel.before_execute_after", "no request id came back");
403
+ skip("requests.cancel.keeps_data", "no request id came back");
404
+ } else {
405
+ const got = await call("GET", `/requests/${encodeURIComponent(id)}`, void 0, { "Idempotency-Key": key("get") });
406
+ if (got.status === 200 && field(got.json, "id") === id && field(got.json, "status") === "scheduled") pass("requests.get");
407
+ else fail("requests.get", said(got));
408
+ const cancelled = await call("POST", `/requests/${encodeURIComponent(id)}/cancel`, {}, { "Idempotency-Key": key("cancel") });
409
+ if (cancelled.status === 200 && field(cancelled.json, "status") === "cancelled") pass("requests.cancel.before_execute_after");
410
+ else fail("requests.cancel.before_execute_after", said(cancelled));
411
+ const after = await call("POST", "/lookup", { emails: [canaryEmail] }, { "Idempotency-Key": key("lookup-after-cancel") });
412
+ if (after.status === 200 && field(after.json, "status") === "found") pass("requests.cancel.keeps_data");
413
+ else fail("requests.cancel.keeps_data", `a lookup after the cancel: ${said(after)} \u2014 a cancel before executeAfter must leave the data intact`);
414
+ }
415
+ }
416
+ const unknown = await call("GET", `/requests/conformance-missing-${run}`, void 0, { "Idempotency-Key": key("unknown") });
417
+ if (unknown.status === 404) pass("requests.unknown.404");
418
+ else fail("requests.unknown.404", said(unknown));
419
+ if (version) pass("version.header");
420
+ else fail("version.header", "no answer carried an X-Version header");
421
+ return report();
422
+ function report() {
423
+ const rules = CONFORMANCE_RULES.map((r) => results.get(r.id) ?? { id: r.id, title: r.title, status: "skip", detail: "not reached" });
424
+ const count = (s) => rules.filter((r) => r.status === s).length;
425
+ return { ok: count("fail") === 0, passed: count("pass"), failed: count("fail"), skipped: count("skip"), rules, ...version ? { version } : {} };
426
+ }
427
+ }
428
+
167
429
  // src/index.ts
168
- var SDK_VERSION = "0.5.1";
430
+ var SDK_VERSION = "0.6.0";
169
431
  var CookieMunchApiError = class extends Error {
170
432
  constructor(status, message, code, body) {
171
433
  super(message);
@@ -176,6 +438,10 @@ var CookieMunchApiError = class extends Error {
176
438
  }
177
439
  };
178
440
  var withBase = (doc, opts) => opts?.baseUpdatedAt !== void 0 ? { ...doc, baseUpdatedAt: opts.baseUpdatedAt } : doc;
441
+ function metaQs(o) {
442
+ const parts = Object.entries(o?.metadata ?? {}).map(([k, v]) => `metadata%5B${encodeURIComponent(k)}%5D=${encodeURIComponent(v)}`);
443
+ return parts.length ? `?${parts.join("&")}` : "";
444
+ }
179
445
  function qs(query) {
180
446
  if (!query) return "";
181
447
  const parts = [];
@@ -191,9 +457,11 @@ function createCookieMunch(opts) {
191
457
  const base = `${opts.baseUrl.replace(/\/+$/, "")}/v1`;
192
458
  const doFetch = opts.fetch ?? globalThis.fetch;
193
459
  if (!doFetch) throw new Error("No fetch available; pass { fetch } in CookieMunchOptions.");
194
- async function request(method, path, body, raw = false) {
195
- const headers = { Authorization: `Bearer ${opts.apiKey}`, "User-Agent": opts.userAgent ?? `cookiemunch-js/${SDK_VERSION}` };
460
+ async function request(method, path, body, raw = false, extraHeaders = {}) {
461
+ const headers = { Authorization: `Bearer ${opts.apiKey}`, "User-Agent": opts.userAgent ?? `cookiemunch-js/${SDK_VERSION}`, ...extraHeaders };
196
462
  if (opts.strictUnknownFields && method !== "GET") headers["Prefer"] = "strict-unknown-fields";
463
+ if (opts.secretRecipient && method !== "GET") headers["X-Secret-Recipient"] = opts.secretRecipient;
464
+ for (const [k, v] of Object.entries(opts.headers ?? {})) headers[k] = v;
197
465
  const init = { method, headers };
198
466
  if (body !== void 0) {
199
467
  headers["Content-Type"] = "application/json";
@@ -219,22 +487,49 @@ function createCookieMunch(opts) {
219
487
  return contentType.includes("application/json") ? res.json() : res.text();
220
488
  }
221
489
  const get = (p) => request("GET", p);
490
+ async function requestBytes(path) {
491
+ const res = await doFetch(`${base}${path}`, { method: "GET", headers: { Authorization: `Bearer ${opts.apiKey}`, "User-Agent": opts.userAgent ?? `cookiemunch-js/${SDK_VERSION}` } });
492
+ if (!res.ok) {
493
+ let message = `request failed with status ${res.status}`;
494
+ let code;
495
+ try {
496
+ const data = await res.json();
497
+ if (typeof data.error === "string") message = data.error;
498
+ if (typeof data.code === "string") code = data.code;
499
+ } catch {
500
+ }
501
+ throw new CookieMunchApiError(res.status, message, code);
502
+ }
503
+ return res.arrayBuffer();
504
+ }
222
505
  const enc = encodeURIComponent;
223
506
  return {
224
507
  me: () => get("/me"),
225
508
  languages: () => get("/languages"),
226
509
  orgs: {
227
- list: () => get("/orgs"),
228
- create: (name, opts2) => request("POST", "/orgs", { name, ...opts2 ?? {} })
510
+ list: (o) => get(`/orgs${metaQs(o)}`),
511
+ create: (name, opts2) => request("POST", "/orgs", { name, ...opts2 ?? {} }),
512
+ deleteSandbox: (id) => request("DELETE", `/orgs/${enc(id)}?hard=1`)
513
+ },
514
+ metadata: {
515
+ get: (type, id) => get(`/metadata/${enc(type)}/${enc(id)}`),
516
+ update: (type, id, metadata, o) => request("PATCH", `/metadata/${enc(type)}/${enc(id)}${qs({ dryRun: o?.dryRun ? "1" : void 0 })}`, { metadata })
517
+ },
518
+ partnerLinks: {
519
+ list: (o) => get(`/partner_links${metaQs(o)}`),
520
+ create: (input) => request("POST", "/partner_links", input),
521
+ revoke: (id) => request("DELETE", `/partner_links/${enc(id)}`)
229
522
  },
523
+ provisionEnv: (ref) => get(`/provision/env${qs({ ref })}`),
230
524
  sites: {
231
- list: () => get("/sites"),
525
+ list: (o) => get(`/sites${metaQs(o)}`),
232
526
  create: (input) => request("POST", "/sites", input),
233
527
  get: (cbid) => get(`/sites/${enc(cbid)}`),
234
528
  update: (cbid, patch, o) => request("PATCH", `/sites/${enc(cbid)}${qs({ dryRun: o?.dryRun ? "1" : void 0 })}`, patch),
235
529
  delete: async (cbid) => {
236
530
  await request("DELETE", `/sites/${enc(cbid)}`);
237
531
  },
532
+ deleteSandbox: (cbid) => request("DELETE", `/sites/${enc(cbid)}?hard=1`),
238
533
  listPartners: (cbid) => get(`/sites/${enc(cbid)}/partners`),
239
534
  linkPartner: (cbid, input, o) => request("POST", `/sites/${enc(cbid)}/partners${qs({ dryRun: o?.dryRun ? "1" : void 0 })}`, input),
240
535
  unlinkPartner: (cbid, linkId) => request("DELETE", `/sites/${enc(cbid)}/partners/${enc(linkId)}`),
@@ -283,6 +578,9 @@ function createCookieMunch(opts) {
283
578
  }),
284
579
  cookiePresets: (cbid) => get(`/sites/${enc(cbid)}/cookies/presets`),
285
580
  applyCookiePreset: (cbid, vendor, params, o) => request("PUT", `/sites/${enc(cbid)}/cookies/presets/${enc(vendor)}${qs({ dryRun: o?.dryRun ? "1" : void 0 })}`, params ?? {}),
581
+ importCookieManifest: (cbid, input, o) => request("PUT", `/sites/${enc(cbid)}/cookies/presets/custom${qs({ dryRun: o?.dryRun ? "1" : void 0 })}`, input),
582
+ cookieSuggestions: (cbid) => get(`/sites/${enc(cbid)}/cookies/suggestions`),
583
+ declareCookieSuggestions: (cbid, input, o) => request("POST", `/sites/${enc(cbid)}/cookies/suggestions/declare${qs({ dryRun: o?.dryRun ? "1" : void 0 })}`, input ?? {}),
286
584
  removeCookiePreset: (cbid, vendor, o) => request("DELETE", `/sites/${enc(cbid)}/cookies/presets/${enc(vendor)}${qs({ dryRun: o?.dryRun ? "1" : void 0 })}`),
287
585
  deleteObservedCookie: (async (cbid, name, o) => {
288
586
  const out = await request("DELETE", `/sites/${enc(cbid)}/observed-cookies/${enc(name)}${qs({ dryRun: o?.dryRun ? "1" : void 0 })}`);
@@ -300,6 +598,8 @@ function createCookieMunch(opts) {
300
598
  verifyChallenge: (cbid) => get(`/sites/${enc(cbid)}/verify/challenge`),
301
599
  createBulk: (sites) => request("POST", "/sites/bulk", { sites }),
302
600
  brand: (cbid) => request("POST", `/sites/${enc(cbid)}/brand`, {}),
601
+ getBrand: (cbid) => get(`/sites/${enc(cbid)}/brand`),
602
+ setBrand: (cbid, input, o) => request("PUT", `/sites/${enc(cbid)}/brand${qs({ dryRun: o?.dryRun ? "1" : void 0 })}`, input),
303
603
  getFlow: (cbid) => get(`/sites/${enc(cbid)}/flow`),
304
604
  editFlow: (cbid, operations) => request("POST", `/sites/${enc(cbid)}/flow/ops`, { operations }),
305
605
  setFlow: (cbid, config) => request("PUT", `/sites/${enc(cbid)}/flow`, config)
@@ -307,11 +607,13 @@ function createCookieMunch(opts) {
307
607
  consent: {
308
608
  verify: (cbid) => get(`/sites/${enc(cbid)}/consent/verify`),
309
609
  stats: (cbid, query) => get(`/sites/${enc(cbid)}/consent/stats${qs(toQuery(query))}`),
610
+ insights: (cbid, query) => get(`/sites/${enc(cbid)}/consent/insights${qs(toQuery(query))}`),
310
611
  log: (cbid, query) => get(`/sites/${enc(cbid)}/consent/log${qs(toQuery(query))}`),
311
612
  export: (cbid, query) => request("GET", `/sites/${enc(cbid)}/consent/export${qs(toQuery(query))}`, void 0, true),
312
613
  receipt: (cbid, stamp) => get(`/sites/${enc(cbid)}/receipt/${enc(stamp)}`),
313
614
  eraseSubject: (cbid, stamp) => request("POST", `/sites/${enc(cbid)}/erase-consent`, { stamp }),
314
615
  exportSubject: (cbid, stamp) => request("GET", `/sites/${enc(cbid)}/subject-export?stamp=${enc(stamp)}`),
616
+ categoryRegistry: () => get("/categories/registry"),
315
617
  ingest: async (input) => {
316
618
  const res = await doFetch(`${opts.baseUrl.replace(/\/+$/, "")}/api/v1/consent`, {
317
619
  method: "POST",
@@ -342,7 +644,16 @@ function createCookieMunch(opts) {
342
644
  delete: async (id) => {
343
645
  await request("DELETE", `/dsar/${enc(id)}`);
344
646
  },
345
- markTest: (id) => request("POST", `/dsar/${enc(id)}/mark-test`)
647
+ markTest: (id) => request("POST", `/dsar/${enc(id)}/mark-test`),
648
+ listByTrace: (trace) => get(`/dsar${qs({ trace })}`),
649
+ openCase: (input, o) => request("POST", "/dsar/requests", input, false, {
650
+ ...o?.idempotencyKey ? { "Idempotency-Key": o.idempotencyKey } : {},
651
+ ...o?.trace ? { "X-ForgeDeck-Trace": o.trace } : {}
652
+ }),
653
+ getCase: (id) => get(`/dsar/requests/${enc(id)}`),
654
+ cancelCase: (id) => request("POST", `/dsar/requests/${enc(id)}/cancel`),
655
+ caseReceipt: (id) => get(`/dsar/requests/${enc(id)}/receipt`),
656
+ caseReceiptPdf: (id) => requestBytes(`/dsar/requests/${enc(id)}/receipt?format=pdf`)
346
657
  },
347
658
  vendors: {
348
659
  list: () => get("/vendors"),
@@ -445,7 +756,8 @@ function createCookieMunch(opts) {
445
756
  status: (requestId) => get(`/dsar/${enc(requestId)}/fulfillment`),
446
757
  pendingTasks: (limit) => get(`/dsar/agent/tasks${qs({ limit })}`),
447
758
  reportTask: (taskId, ok, error) => request("POST", `/dsar/agent/tasks/${enc(taskId)}/result`, { ok, ...error !== void 0 ? { error } : {} }),
448
- executors: () => get("/dsar/executors"),
759
+ executors: (o) => get(`/dsar/executors${metaQs(o)}`),
760
+ executor: (id) => get(`/dsar/executors/${enc(id)}`),
449
761
  connectExecutor: (input) => request("POST", "/dsar/executors", input),
450
762
  updateExecutor: (id, patch) => request("PATCH", `/dsar/executors/${enc(id)}`, patch),
451
763
  dryRunConnectExecutor: (input) => request("POST", "/dsar/executors?dryRun=1", input),
@@ -460,7 +772,11 @@ function createCookieMunch(opts) {
460
772
  },
461
773
  taskExport: (requestId, taskId) => get(`/dsar/${enc(requestId)}/tasks/${enc(taskId)}/export`),
462
774
  task: (requestId, taskId) => get(`/dsar/${enc(requestId)}/tasks/${enc(taskId)}`),
463
- testExecutor: (id, input) => request("POST", `/dsar/executors/${enc(id)}/test`, input)
775
+ testExecutor: (id, input) => request("POST", `/dsar/executors/${enc(id)}/test`, input),
776
+ recheckExecutor: (id) => request("POST", `/dsar/executors/${enc(id)}/recheck`),
777
+ executorHealthHistory: (id, opts2 = {}) => get(`/dsar/executors/${enc(id)}/health/history${qs({ limit: opts2.limit })}`),
778
+ executorConformance: (id) => request("POST", `/dsar/executors/${enc(id)}/test`, { conformance: true }),
779
+ rollExecutorCompletionSecret: (id) => request("POST", `/dsar/executors/${enc(id)}/completion-secret/roll`)
464
780
  },
465
781
  preferences: {
466
782
  list: () => get("/preferences"),
@@ -484,11 +800,25 @@ function createCookieMunch(opts) {
484
800
  },
485
801
  roll: (prefix) => request("POST", `/keys/${enc(prefix)}/roll`),
486
802
  update: (prefix, patch) => request("PATCH", `/keys/${enc(prefix)}`, patch),
487
- report: (o) => get(`/keys/report${qs({ unusedDays: o?.unusedDays, expiringDays: o?.expiringDays })}`)
803
+ report: (o) => get(`/keys/report${qs({ unusedDays: o?.unusedDays, expiringDays: o?.expiringDays })}`),
804
+ listStandard: () => get("/keys?shape=standard"),
805
+ self: () => get("/keys/self"),
806
+ rollSelf: () => request("POST", "/keys/self/roll"),
807
+ vend: (input) => request("POST", "/keys/vend", input),
808
+ revokeLeaked: (report) => request("POST", "/keys/revoke_leaked", report)
488
809
  },
489
810
  usage: () => get("/usage"),
811
+ entitlements: () => get("/entitlements"),
490
812
  integrationHealth: (cbid) => get(`/integration-health${qs({ cbid })}`),
491
- provision: (input, o) => request("POST", `/provision${qs({ dryRun: o?.dryRun ? "1" : void 0 })}`, input),
813
+ healthWaivers: {
814
+ list: (cbid) => get(`/integration-health/waivers${qs({ cbid })}`),
815
+ set: (input) => request("PUT", "/integration-health/waivers", input),
816
+ remove: async (check, opts2) => {
817
+ await request("DELETE", `/integration-health/waivers${qs({ check, cbid: opts2?.cbid, subject: opts2?.subject })}`);
818
+ }
819
+ },
820
+ embedManifest: (cbid) => get(`/embed${qs({ cbid })}`),
821
+ provision: (input, o) => request("POST", `/provision${qs({ dryRun: o?.dryRun ? "1" : void 0, fromStep: o?.fromStep })}`, input),
492
822
  org: {
493
823
  get: () => get("/org"),
494
824
  update: (patch) => request("PATCH", "/org", patch),
@@ -518,7 +848,14 @@ function createCookieMunch(opts) {
518
848
  remove: () => request("DELETE", "/org/rights-identity")
519
849
  }
520
850
  },
521
- audit: (limit) => get(`/audit${qs({ limit })}`),
851
+ audit: (limit, opts2) => get(`/audit${qs({ limit, actor: opts2?.actor })}`),
852
+ auditSink: {
853
+ get: () => get("/audit/sink"),
854
+ set: (input) => request("PUT", "/audit/sink", input),
855
+ remove: async () => {
856
+ await request("DELETE", "/audit/sink");
857
+ }
858
+ },
522
859
  assets: {
523
860
  upload: (input) => request("POST", "/assets", input),
524
861
  delete: async (urlOrFileName) => {
@@ -541,7 +878,7 @@ function createCookieMunch(opts) {
541
878
  }
542
879
  },
543
880
  webhooks: {
544
- list: () => get("/webhooks"),
881
+ list: (o) => get(`/webhooks${metaQs(o)}`),
545
882
  create: (input) => request("POST", "/webhooks", input),
546
883
  update: (id, patch) => request("PATCH", `/webhooks/${enc(id)}`, patch),
547
884
  delete: async (id) => {
@@ -550,7 +887,8 @@ function createCookieMunch(opts) {
550
887
  rollSecret: (id) => request("POST", `/webhooks/${enc(id)}/roll`),
551
888
  test: (id) => request("POST", `/webhooks/${enc(id)}/test`),
552
889
  deadLetters: () => get("/webhooks/dead-letters"),
553
- replayDeadLetter: (id) => request("POST", `/webhooks/dead-letters/${enc(id)}/replay`)
890
+ replayDeadLetter: (id) => request("POST", `/webhooks/dead-letters/${enc(id)}/replay`),
891
+ replay: (id, since, opts2) => request("POST", `/webhooks/${enc(id)}/replay${qs({ since: String(since), limit: opts2?.limit })}`)
554
892
  },
555
893
  rightsLinks: {
556
894
  create: (cbid, input) => request("POST", `/sites/${enc(cbid)}/rights-links`, input)
@@ -570,6 +908,13 @@ function createCookieMunch(opts) {
570
908
  set: (cbid, input) => request("PUT", `/sites/${enc(cbid)}/rights-identity`, input),
571
909
  remove: (cbid) => request("DELETE", `/sites/${enc(cbid)}/rights-identity`)
572
910
  },
911
+ dsarSettings: {
912
+ get: (cbid) => get(`/sites/${enc(cbid)}/dsar-settings`),
913
+ set: (cbid, input) => request("PUT", `/sites/${enc(cbid)}/dsar-settings`, input)
914
+ },
915
+ integrations: {
916
+ subjectCard: (app, person) => get(`/integrations/${enc(app)}/card${qs(person)}`)
917
+ },
573
918
  subjects: {
574
919
  consent: (subjectId) => get(`/subjects/${enc(subjectId)}/consent`)
575
920
  },
@@ -589,15 +934,23 @@ function createCookieMunch(opts) {
589
934
  }
590
935
  // Annotate the CommonJS export names for ESM import in node:
591
936
  0 && (module.exports = {
937
+ CANARY_ISSUER,
938
+ CONFORMANCE_RULES,
592
939
  CONSENT_COOKIE_NAME,
593
940
  CookieMunchApiError,
941
+ DEFAULT_JWKS_URL,
594
942
  SDK_VERSION,
943
+ canaryIdentityFor,
944
+ consentSignaturePayload,
595
945
  createCookieMunch,
596
946
  getConsent,
597
947
  isAllowed,
598
948
  mintConsentSyncToken,
599
949
  parseConsentValue,
600
950
  readConsent,
951
+ runExecutorConformance,
952
+ verifyDecisionSignature,
953
+ verifyToken,
601
954
  verifyWebhookSignature
602
955
  });
603
956
  //# sourceMappingURL=index.cjs.map