@cookiemunch/sdk 0.7.0 → 0.8.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/CHANGELOG.md CHANGED
@@ -2,6 +2,17 @@
2
2
 
3
3
  Every published release, newest first. Dates are the day the version was cut.
4
4
 
5
+ ## 0.8.0 (2026-10-30)
6
+
7
+ - Added: `mintFrom` and `externalId` on the DSAR executor connect method: a partner with an `executorKeyUrl` mints the executor key, so `secretKey` is optional. The dry run's `mint` shows the plan.
8
+ - Changed: a preset's `source` is typed `builtin | manifest | instance`.
9
+ - Added: `integrations.connect(provider, { cbid, providerKey, connector? }, { dryRun? })`: one-call connect for an identity provider's privacy orchestrator (`POST /v1/integrations/{provider}/connect`).
10
+ - Changed: `ProvisionRequestV2` names its block `stack` (same fields); its site tags are `stack_product`, `stack_env`, `stack_ref` and `stack_sandbox`. The trace header is `X-Trace-Id`.
11
+
12
+ ## 0.7.1 (2026-10-24)
13
+
14
+ - Changed: `ConsentInsights.byLanguage` and `timeToDecision` are now filled in — opt-in by the banner's language, and a time-to-decision histogram with its median bucket — or `null` with `notRecorded` saying why.
15
+
5
16
  ## 0.7.0 (2026-10-24)
6
17
 
7
18
  - Added: `sandbox.get` / `create` / `delete` — a sandbox organisation made from yours: the same people and plan, never billed, test keys only, every rights request a test.
@@ -19,7 +30,7 @@ Every published release, newest first. Dates are the day the version was cut.
19
30
  - Added: keys — `keys.self()`, `keys.rollSelf()`, `keys.vend({ scopes, ttlSeconds })` (short-lived, at most 12 h), `keys.listStandard()`, `keys.revokeLeaked(report)`; keys can be minted with `scopes`, `cbids` and `expiresInDays`.
20
31
  - Added: `partnerLinks.list` / `create` / `revoke` (any partner, scoped), and `metadata.get` / `update` with `metadata` and `externalRef` on sites, webhooks, connections, rights requests and orgs; `?metadata[key]=` list filters; `deleteSandbox` on sandbox sites and orgs.
21
32
  - Added: `sites.cookieSuggestions` / `declareCookieSuggestions`, `importCookieManifest` (any vendor's embed manifest) and `removeCookiePreset` for custom presets; `embedManifest(cbid)`; first-class cookie presets for `referalli`, `firstpromoter`, `partnerstack`, `tapfiliate` and `impact` (`referally` is a deprecated alias).
22
- - Added: `sites.getBrand` / `setBrand` (DTCG brand.json import with a read-back), `consent.insights`, `categoryRegistry()`, `entitlements()`, `provisionEnv(ref)`, and `provision(input, { fromStep })` with the forgedeck (v2) body.
33
+ - Added: `sites.getBrand` / `setBrand` (DTCG brand.json import with a read-back), `consent.insights`, `categoryRegistry()`, `entitlements()`, `provisionEnv(ref)`, and `provision(input, { fromStep })` with the version 2 body.
23
34
  - Added: `healthWaivers.list` / `set` / `remove`, `auditSink.get` / `set` / `remove` (CloudEvents), `webhooks.replay(id, since)`, `audit(limit, { actor: 'self' })`, `fulfillment.recheckExecutor` and `executorHealthHistory`.
24
35
  - Added: `readConsent(source, { requireSigned })`, `verifyDecisionSignature`, `verifyToken` (consent tokens, against the partner JWKS) and `consentSignaturePayload`.
25
36
  - Added: the `X-Secret-Recipient` option seals every once-shown secret; `Idempotency-Key` on creates.
@@ -0,0 +1,13 @@
1
+ export interface MintConsentSyncTokenInput {
2
+ /** The site's consent-sync secret (`cmss_…`). */
3
+ secret: string;
4
+ /** The site the token is for. A token for one site is refused by every other. */
5
+ cbid: string;
6
+ /** Who is signed in: 1–128 of `A-Z a-z 0-9 . _ : @ + / = ~ -`. Use an opaque id. */
7
+ subject: string;
8
+ /** Lifetime in seconds. Default 3600; at most 86400 (Cookie Munch refuses longer). */
9
+ ttlSeconds?: number;
10
+ /** Current time in ms (injectable for tests). */
11
+ now?: number;
12
+ }
13
+ export declare function mintConsentSyncToken(input: MintConsentSyncTokenInput): string;
package/dist/consent.d.ts CHANGED
@@ -17,9 +17,9 @@
17
17
  * This module imports nothing — `@cookiemunch/sdk/consent` pulls in no API client.
18
18
  */
19
19
  /** The first-party cookie the embed writes (mirrors @cookiemunch/core COOKIE_NAME). */
20
- declare const CONSENT_COOKIE_NAME = "CookieMunch";
20
+ export declare const CONSENT_COOKIE_NAME = "CookieMunch";
21
21
  /** A visitor's consent decision, read from the request cookie. `necessary` is always true. */
22
- interface ServerConsent {
22
+ export interface ServerConsent {
23
23
  necessary: true;
24
24
  preferences: boolean;
25
25
  statistics: boolean;
@@ -43,7 +43,7 @@ interface ServerConsent {
43
43
  sig?: string;
44
44
  }
45
45
  /** The site's consent policy, so the server applies the same rules the embed does. */
46
- interface ReadConsentOptions {
46
+ export interface ReadConsentOptions {
47
47
  /** Cookie name. Default `CookieMunch`. */
48
48
  cookieName?: string;
49
49
  /**
@@ -58,7 +58,7 @@ interface ReadConsentOptions {
58
58
  now?: number;
59
59
  }
60
60
  /** Where the platform's public signing keys come from, for signatures and consent tokens. */
61
- interface KeySetOptions {
61
+ export interface KeySetOptions {
62
62
  /** A key set you already hold (`{ keys: [...] }`). Skips the fetch. */
63
63
  jwks?: JsonWebKeySet;
64
64
  /** Default `https://api.cookiemunch.net/v1/partner/jwks.json`. */
@@ -67,11 +67,11 @@ interface KeySetOptions {
67
67
  fetch?: typeof fetch;
68
68
  }
69
69
  /** `readConsent` with `requireSigned: true`: only a decision the API signed counts. */
70
- interface ReadSignedConsentOptions extends ReadConsentOptions, KeySetOptions {
70
+ export interface ReadSignedConsentOptions extends ReadConsentOptions, KeySetOptions {
71
71
  requireSigned: true;
72
72
  }
73
73
  /** Anything we can pull a Cookie header off of. */
74
- type ConsentSource = string | null | undefined | {
74
+ export type ConsentSource = string | null | undefined | {
75
75
  headers?: {
76
76
  cookie?: string | null;
77
77
  get?(name: string): string | null;
@@ -82,32 +82,32 @@ type ConsentSource = string | null | undefined | {
82
82
  * the embed reads: URL-encoded (or plain) strict JSON, and Cookiebot's relaxed object
83
83
  * literal — `{stamp:'…',necessary:true,…}` — left behind by a site migrating from Cookiebot.
84
84
  */
85
- declare function parseConsentValue(raw: string): ServerConsent | null;
85
+ export declare function parseConsentValue(raw: string): ServerConsent | null;
86
86
  /**
87
87
  * Read the visitor's consent from a request: a raw `Cookie` header, a Node `req`, or a Fetch
88
88
  * `Request`. Returns null when there is no decision the embed would honour — no cookie, an
89
89
  * unparseable one, or (with the options) one made under an older version or past expiry.
90
90
  */
91
- declare function readConsent(source: ConsentSource, opts: ReadSignedConsentOptions): Promise<ServerConsent | null>;
92
- declare function readConsent(source: ConsentSource, opts?: ReadConsentOptions & {
91
+ export declare function readConsent(source: ConsentSource, opts: ReadSignedConsentOptions): Promise<ServerConsent | null>;
92
+ export declare function readConsent(source: ConsentSource, opts?: ReadConsentOptions & {
93
93
  requireSigned?: false;
94
94
  }): ServerConsent | null;
95
95
  /**
96
96
  * Read the visitor's consent from a request. The original spelling of {@link readConsent},
97
97
  * kept for existing callers; the second argument is the cookie name.
98
98
  */
99
- declare function getConsent(source: ConsentSource, cookieName?: string): ServerConsent | null;
99
+ export declare function getConsent(source: ConsentSource, cookieName?: string): ServerConsent | null;
100
100
  /** Standard + custom consent categories. */
101
- type ConsentCategory = 'necessary' | 'preferences' | 'statistics' | 'marketing' | (string & {});
101
+ export type ConsentCategory = 'necessary' | 'preferences' | 'statistics' | 'marketing' | (string & {});
102
102
  /**
103
103
  * Whether a category is allowed for this visitor. `necessary` is always allowed. A missing
104
104
  * consent (`null` — visitor hasn't decided) is treated as NOT allowed for non-necessary
105
105
  * categories (fail-closed / prior-blocking posture), matching the embed.
106
106
  */
107
- declare function isAllowed(consent: ServerConsent | null, category: ConsentCategory): boolean;
108
- declare const DEFAULT_JWKS_URL = "https://api.cookiemunch.net/v1/partner/jwks.json";
107
+ export declare function isAllowed(consent: ServerConsent | null, category: ConsentCategory): boolean;
108
+ export declare const DEFAULT_JWKS_URL = "https://api.cookiemunch.net/v1/partner/jwks.json";
109
109
  /** The fields of a decision its signature covers. */
110
- interface SignableDecision {
110
+ export interface SignableDecision {
111
111
  stamp: string;
112
112
  ver: number;
113
113
  utc: number;
@@ -120,7 +120,7 @@ interface SignableDecision {
120
120
  * The exact line the API signs for a recorded decision. Identical to @cookiemunch/core's
121
121
  * `consentSignaturePayload` (a server test pins the two together).
122
122
  */
123
- declare function consentSignaturePayload(d: SignableDecision): string;
123
+ export declare function consentSignaturePayload(d: SignableDecision): string;
124
124
  interface Jwk {
125
125
  kty?: string;
126
126
  crv?: string;
@@ -128,13 +128,13 @@ interface Jwk {
128
128
  kid?: string;
129
129
  alg?: string;
130
130
  }
131
- interface JsonWebKeySet {
131
+ export interface JsonWebKeySet {
132
132
  keys: Jwk[];
133
133
  }
134
134
  /** Does the cookie's `sig` verify for this decision? False on anything malformed. */
135
- declare function verifyDecisionSignature(consent: ServerConsent, opts?: KeySetOptions): Promise<boolean>;
135
+ export declare function verifyDecisionSignature(consent: ServerConsent, opts?: KeySetOptions): Promise<boolean>;
136
136
  /** What a consent token (`CookieMunch.getConsentToken()`) asserts. */
137
- interface ConsentTokenClaims {
137
+ export interface ConsentTokenClaims {
138
138
  iss: string;
139
139
  /** The decision's stamp. */
140
140
  sub: string;
@@ -152,7 +152,7 @@ interface ConsentTokenClaims {
152
152
  iat: number;
153
153
  exp: number;
154
154
  }
155
- interface VerifyTokenOptions extends KeySetOptions {
155
+ export interface VerifyTokenOptions extends KeySetOptions {
156
156
  /** Require the token to be for this site. */
157
157
  cbid?: string;
158
158
  /** Clock, epoch ms. Default `Date.now()`. */
@@ -164,6 +164,5 @@ interface VerifyTokenOptions extends KeySetOptions {
164
164
  * Verify a consent token from `CookieMunch.getConsentToken()` (CMU-F06) against the published
165
165
  * key set and return its claims. Throws on a bad signature, a wrong site, or an expired token.
166
166
  */
167
- declare function verifyToken(token: string, opts?: VerifyTokenOptions): Promise<ConsentTokenClaims>;
168
-
169
- export { CONSENT_COOKIE_NAME, type ConsentCategory, type ConsentSource, type ConsentTokenClaims, DEFAULT_JWKS_URL, type JsonWebKeySet, type KeySetOptions, type ReadConsentOptions, type ReadSignedConsentOptions, type ServerConsent, type SignableDecision, type VerifyTokenOptions, consentSignaturePayload, getConsent, isAllowed, parseConsentValue, readConsent, verifyDecisionSignature, verifyToken };
167
+ export declare function verifyToken(token: string, opts?: VerifyTokenOptions): Promise<ConsentTokenClaims>;
168
+ export {};
@@ -0,0 +1,75 @@
1
+ /**
2
+ * The cm-standard-v2 executor conformance kit (CMU-X20).
3
+ *
4
+ * cm-standard-v2 is an open contract: any system that holds personal data can implement it and
5
+ * be driven by Cookie Munch with no code on our side (docs: cookiemunch.net/docs/executor-contract-v2).
6
+ * This kit checks an implementation rule by rule, and names the rule that fails. It is the same
7
+ * suite Cookie Munch runs on `POST /v1/dsar/executors/{id}/test {conformance: true}`, and the
8
+ * `cookiemunch executors conformance <baseUrl>` command runs it from a terminal or CI.
9
+ *
10
+ * It only ever touches the CANARY subject — an address no real person has. The contract asks every
11
+ * implementation to treat the canary as an existing synthetic person: it is found by its email and
12
+ * by its identity, requests on it move through the full state machine, and nothing real is ever
13
+ * erased. So the kit can check that an erasure waits for `executeAfter`, that a cancel leaves the
14
+ * person in place, and that an identity is never matched to someone else through a shared email —
15
+ * without a real person's data being anywhere near it.
16
+ *
17
+ * No dependencies: `fetch` only, so it runs in Node 18+, a browser, or an edge runtime.
18
+ */
19
+ export type ConformanceStatus = 'pass' | 'fail' | 'skip';
20
+ export interface ConformanceRuleResult {
21
+ /** A stable id, e.g. `requests.execute_after.scheduled`. */
22
+ id: string;
23
+ /** What the rule says, in one sentence. */
24
+ title: string;
25
+ status: ConformanceStatus;
26
+ /** What was seen, when it failed or was skipped. */
27
+ detail?: string;
28
+ }
29
+ export interface ConformanceReport {
30
+ /** True when no rule failed. */
31
+ ok: boolean;
32
+ passed: number;
33
+ failed: number;
34
+ skipped: number;
35
+ rules: ConformanceRuleResult[];
36
+ /** The `X-Version` the executor answered with, when it sent one. */
37
+ version?: string;
38
+ }
39
+ export interface ConformanceOptions {
40
+ /** The executor's base: the contract paths are `{baseUrl}/privacy/lookup` and so on. https (http only for localhost). */
41
+ baseUrl: string;
42
+ /** The credential Cookie Munch would send. */
43
+ secret: string;
44
+ /**
45
+ * The canary address: `canary-<16 hex>@canary.cookiemunch.net`, per organisation
46
+ * (`GET /v1/dsar/executors/presets` → `canaryEmail`).
47
+ */
48
+ canaryEmail: string;
49
+ /** How the secret is sent. Default `Authorization: Bearer {secret}`. */
50
+ auth?: {
51
+ header: string;
52
+ format: string;
53
+ };
54
+ /** Path prefix under baseUrl. Default `/privacy`. */
55
+ prefix?: string;
56
+ fetch?: typeof fetch;
57
+ now?: () => number;
58
+ /** A per-run tag for idempotency keys and case ids. Default random. */
59
+ runId?: string;
60
+ }
61
+ /** The issuer the canary identity carries. */
62
+ export declare const CANARY_ISSUER = "https://canary.cookiemunch.net";
63
+ /** The canary subject's identity, derived from its address. */
64
+ export declare function canaryIdentityFor(canaryEmail: string): {
65
+ iss: string;
66
+ sub: string;
67
+ ext: string;
68
+ };
69
+ /** Every rule, in the order they run. */
70
+ export declare const CONFORMANCE_RULES: ReadonlyArray<{
71
+ id: string;
72
+ title: string;
73
+ }>;
74
+ /** Run every rule against one executor. Never throws for an executor's answer; only for bad options. */
75
+ export declare function runExecutorConformance(o: ConformanceOptions): Promise<ConformanceReport>;
@@ -0,0 +1,73 @@
1
+ /**
2
+ * Types for `window.__consent` (also `window.__fdConsent`, the same object under its first name), the shared
3
+ * consent API any embed can code against — a chat
4
+ * widget, a referral snippet, an analytics tag — whatever CMP the page runs. Cookie Munch sets
5
+ * it natively; `fd-consent-shim.js` sets the same object over Cookiebot, OneTrust or no CMP.
6
+ * Type-only: importing this module adds nothing to a bundle.
7
+ *
8
+ * import type { FdConsent, FdConsentQueue } from '@cookiemunch/sdk';
9
+ * const fd = (window.__consent ??= { q: [] });
10
+ * if ('version' in fd) fd.on('change', (s) => …); else fd.q.push(['on', 'change', (s) => …]);
11
+ *
12
+ * `version` changes only with a breaking change; new members do not bump it.
13
+ */
14
+ /** What caused a change. The same causes Cookie Munch's ledger records. */
15
+ export type FdCause = 'replay' | 'accept' | 'decline' | 'selection' | 'withdraw' | 'implied' | 'sync' | 'request' | 'migrated';
16
+ export interface FdSnapshot {
17
+ /** False until the visitor has answered. */
18
+ decided: boolean;
19
+ /** False when this visitor's region and the site's rules need no answer (a default applies). */
20
+ required: boolean;
21
+ /** Every category the site offers (standard, custom, reserved) and its answer. */
22
+ categories: Record<string, boolean>;
23
+ /** The decision's stamp (`CookieMunch.consentId()`), or null. */
24
+ stamp: string | null;
25
+ region: string;
26
+ /** A Global Privacy Control signal was seen. */
27
+ gpc: boolean;
28
+ /** ISO 8601, or null before a decision. */
29
+ decidedAt: string | null;
30
+ }
31
+ export interface FdRequestOptions {
32
+ /** One sentence in the page's language: what will be stored and why. Shown as the prompt's text. */
33
+ reason: string;
34
+ /** Who is asking: your vendor id. Recorded with the answer. */
35
+ vendor: string;
36
+ }
37
+ export interface FdConsent {
38
+ readonly version: 1;
39
+ /** `cookiemunch`, or `shim:cookiebot` / `shim:onetrust` / `shim:none`. */
40
+ readonly provider: string;
41
+ /** Resolves once a decision exists, or at once where the region needs none. */
42
+ ready(): Promise<FdSnapshot>;
43
+ /** true / false, or null while a required decision is pending. Never false for "not decided". */
44
+ has(category: string): boolean | null;
45
+ /** Fires once per decision (a returning visitor's as `replay`); a withdrawal synchronously. Returns unsubscribe. */
46
+ on(event: 'change', cb: (snapshot: FdSnapshot, cause: FdCause) => void): () => void;
47
+ /** Ask for one category at the moment you need it. Never re-asks a refusal; one prompt per category per page view. */
48
+ request(category: string, opts: FdRequestOptions): Promise<boolean>;
49
+ /** Not available yet: warns once and does nothing. */
50
+ identify(token: string): void;
51
+ layout: {
52
+ bottomInset(): number;
53
+ on(cb: (insetPx: number) => void): () => void;
54
+ };
55
+ /** Calls made on the queue before this existed ran in order; later pushes run at once. */
56
+ q: {
57
+ push(...calls: FdQueuedCall[]): number;
58
+ };
59
+ }
60
+ /** A call queued before the real object exists. */
61
+ export type FdQueuedCall = ['on', 'change', (snapshot: FdSnapshot, cause: FdCause) => void] | ['ready', (snapshot: FdSnapshot) => void] | ['request', string, FdRequestOptions, (granted: boolean) => void] | ['identify', string];
62
+ /** What an embed that loads first creates: `window.__consent = window.__consent || { q: [] }` (or the same under `__fdConsent`). */
63
+ export interface FdConsentQueue {
64
+ q: FdQueuedCall[];
65
+ }
66
+ declare global {
67
+ interface Window {
68
+ /** The shared consent API (or, before Cookie Munch loads, a queue). */
69
+ __consent?: FdConsent | FdConsentQueue;
70
+ /** The same object as `__consent`, under its first name. Kept for good: existing embeds read it. */
71
+ __fdConsent?: FdConsent | FdConsentQueue;
72
+ }
73
+ }
package/dist/index.cjs CHANGED
@@ -465,7 +465,7 @@ async function exchangeOperatorToken(input) {
465
465
  }
466
466
 
467
467
  // src/index.ts
468
- var SDK_VERSION = "0.7.0";
468
+ var SDK_VERSION = "0.8.0";
469
469
  var CookieMunchApiError = class extends Error {
470
470
  constructor(status, message, code, body) {
471
471
  super(message);
@@ -687,7 +687,7 @@ function createCookieMunch(opts) {
687
687
  listByTrace: (trace) => get(`/dsar${qs({ trace })}`),
688
688
  openCase: (input, o) => request("POST", "/dsar/requests", input, false, {
689
689
  ...o?.idempotencyKey ? { "Idempotency-Key": o.idempotencyKey } : {},
690
- ...o?.trace ? { "X-ForgeDeck-Trace": o.trace } : {}
690
+ ...o?.trace ? { "X-Trace-Id": o.trace } : {}
691
691
  }),
692
692
  getCase: (id) => get(`/dsar/requests/${enc(id)}`),
693
693
  cancelCase: (id) => request("POST", `/dsar/requests/${enc(id)}/cancel`),
@@ -959,7 +959,8 @@ function createCookieMunch(opts) {
959
959
  set: (cbid, input) => request("PUT", `/sites/${enc(cbid)}/dsar-settings`, input)
960
960
  },
961
961
  integrations: {
962
- subjectCard: (app, person) => get(`/integrations/${enc(app)}/card${qs(person)}`)
962
+ subjectCard: (app, person) => get(`/integrations/${enc(app)}/card${qs(person)}`),
963
+ connect: (provider, input, o) => request("POST", `/integrations/${enc(provider)}/connect${qs({ dryRun: o?.dryRun ? "1" : void 0 })}`, input)
963
964
  },
964
965
  subjects: {
965
966
  consent: (subjectId) => get(`/subjects/${enc(subjectId)}/consent`)