@intenque/network-sdk 2.2.0-beta.1 → 2.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/CHANGELOG.md CHANGED
@@ -3,7 +3,73 @@
3
3
  `@intenque/network-sdk`. The SDK major is the manifest schema major (spec §6.1). Design decisions are in the hub
4
4
  repository: `docs/Network_CrossPromo_Plan.md` and `docs/spec-addendum-phase0-auth.md` ("# Phase 3: conversions").
5
5
 
6
- ## 2.2.0-beta.1 (in progress)
6
+ ## 2.2.0
7
+
8
+ The first non-beta release of the Phase 3 (conversions) line; the code is 2.2.0-beta.2 plus the production root.
9
+
10
+ - **Production root `root-2026`** (ECDSA P-256, generated offline by the owner; spec §4.5, docs/install-guide.md
11
+ "Adding a production root") is compiled into `rootPublicKeys`. A site that does not set `manifestRootKeys` now
12
+ trusts manifests whose signer is certified under `root-2026`; `init` / `sync-fallback` / `doctor` no longer need
13
+ `--root-keys` against the production hub. `DEV_ROOT_PUBLIC_KEYS` (`root-dev-a01e0d`) stays a separate export and
14
+ is never merged in. Adding a root is a minor; removing one is a major (§4.5 "Root rotation").
15
+ - Sizes (gzip): react 11 987 / 12 000 B, server 8 063 / 8 100 B, capture 1 494 / 1 500 B. The root adds ~90 B to
16
+ `react`; the same bundle gives them back without a behaviour change: the click-id module reuses the verifier's
17
+ base64url encoder and hex helper, `mintClickId` packs its header with a `DataView`, `withClickId` and `buildHref`
18
+ share one fragment splitter, and the signal environment reads `globalThis.document` & co. instead of `typeof`
19
+ guards.
20
+ - `SDK_VERSION` / wire `sdk` is `2.2.0`; the `sdk-publish` workflow publishes a non-prerelease to the `latest`
21
+ dist-tag, so a bare `npm install @intenque/network-sdk` resolves to this version.
22
+
23
+ ## 2.2.0-beta.2
24
+
25
+ Security fix round (hub Phase 3 WP19). No API change; one new drop reason.
26
+
27
+ ### Server entry (`@intenque/network-sdk/server`)
28
+
29
+ - **Misrouted answers are retried, not dropped.** `403`, `404` and any 3xx the classifier sees (`300`, `304`, or a
30
+ `fetch` that hands redirects back) were terminal after one attempt, so a missing route prefix, a WAF or a
31
+ maintenance page in front of the hub lost every report in that window. They are now `misrouted`
32
+ (`classifyResponse` returns the new kind): retried in process after 2, 8 and 30 s, then every 10 min from the
33
+ outbox, and dropped only 24 h after the first 401 / missing-config / misrouted answer with reason `misrouted_24h`
34
+ (the clock is the existing `unauthorizedSince` field, shared with the 401 rule; no outbox migration). A real
35
+ redirect (`301`/`302`/`303`/`307`/`308`, with or without `Location`) is still a transport error under
36
+ `redirect: 'error'` (status 0, retryable), as before. `400`, other `409`s, `402`, `413`, `422` and the rest of 4xx
37
+ stay terminal.
38
+ - **The responder's error code is bounded.** A code is kept (logged in `inr_outbox_dropped_total` /
39
+ `INR_UNAUTHORIZED`, stored in `lastError.code`) only when it matches `^[A-Z][A-Z0-9_]{0,63}$`; anything else
40
+ (a captive portal, a misrouted proxy, a 5000-character string) is `null`.
41
+ - **`referralMiddleware()` strips `inrRef` from any object body** (arrays and class instances too, not only plain
42
+ objects) with `Reflect.deleteProperty`, and never throws: a frozen body or a proxy that refuses keeps the field,
43
+ a `null` or primitive body is left alone. `req.inrRef` is set as before.
44
+ - **`forget()` stops a running send chain.** Before each in-process retry the chain checks that its outbox row (or
45
+ spill entry) still exists; once `forget(clickId)` purged it, no further attempt is sent. An outbox that errors or
46
+ times out on that check keeps the chain sending (as before).
47
+ - **`forget()` during a drain tick's spill move.** The drainer moved a snapshot of the in-memory spill, so a spilled
48
+ report that `forget(clickId)` removed while an earlier entry was being written was inserted into the outbox after
49
+ the purge (and `forget()` had returned `true`); the next tick sent the erased click id again. The move now skips an
50
+ entry no longer spilled, and removes the row again when the entry left the spill while its own write was in flight
51
+ (forget, or a send that finished). A failed write still leaves the entry spilled for the next tick.
52
+
53
+ ### Verifier (`@intenque/network-sdk/react`, the manifest verifier)
54
+
55
+ - **Destination URLs: RFC 3986 characters only after the authority** (unreserved, sub-delims, `:`, `@`, `/`, `?`,
56
+ `%XX`, one `#`). The hub now stores business URLs with `^`, `|`, a backtick, `{`, `}`, `[`, `]` and non-ASCII
57
+ percent-encoded and refuses
58
+ to sign anything else, so the SDK and the hub agree on every URL in every engine (a raw `^` or `|` used to pass
59
+ the hub and fail the SDK in browsers, taking the whole manifest down).
60
+ - **Signer dates must be their own `toISOString()`** (`2027-02-30`, `T24:00`, second 60 are `malformed`), the same
61
+ rule as the hub's verifier: engines disagree on those dates.
62
+ - **A root key that is not exactly `crv: 'P-256'` never verifies** (Firefox's WebCrypto accepts `p-256`).
63
+ - **A root key must have the shape the hub requires of its own roots** (`PublicJwkSchema`: `kty: 'EC'`,
64
+ `crv: 'P-256'`, `x` / `y` canonical base64url of 32 bytes), else `root_sig`; other members are ignored, as
65
+ before. The SDK handed WebCrypto `kty: 'EC'` whatever the site's `manifestRootKeys` said, so `kty: 'ec'`, `'RSA'`
66
+ or none verified in every engine while the hub refuses such a key, and a padded `x` verified in WebKit only.
67
+ - **Hosts with right-to-left text** (hub side, no SDK change): the hub now refuses a business URL whose host breaks
68
+ the RFC 5893 bidi rule (e.g. `24.בדיקה.co.il`, `1shop.عرب.com`, `aא.com`, `٣.com`), which Firefox's URL parser
69
+ refuses, so a signed manifest can no longer be `malformed` in Firefox alone. The SDK schema does not repeat that
70
+ check (bundle size; the hub never signs such a host).
71
+
72
+ ## 2.2.0-beta.1
7
73
 
8
74
  ### Server entry (`@intenque/network-sdk/server`)
9
75
 
package/README.md CHANGED
@@ -4,7 +4,7 @@ Adds the **"Discover Other Businesses"** section, referral capture and conversio
4
4
  network site. Spec: `docs/Network_CrossPromo_Plan.md` §6 in the hub repository; the timed per-site checklist is
5
5
  `docs/install-guide.md`.
6
6
 
7
- Version `2.2.0-beta.1` reads manifest **schema 2** (SDK major = manifest schema major). Zero runtime dependencies;
7
+ Version `2.2.0` reads manifest **schema 2** (SDK major = manifest schema major). Zero runtime dependencies;
8
8
  `react` / `react-dom` ^18 || ^19 are optional peers (only the `react` entry needs them).
9
9
 
10
10
  | Entry | Exports |
@@ -25,9 +25,10 @@ npx network-sdk init --site-key <site-key> # writes src/network.config.js
25
25
  ```
26
26
 
27
27
  `init` fails with `site <key> has no published manifest; Publish in the admin first` when the hub has not published
28
- the site yet (step 1 of the checklist). While no production root key ships in the SDK, pass the hub's root and CDN
29
- explicitly: `--url <cdn-base> --root-keys '<MANIFEST_ROOT_PUBLIC_KEYS json>'` (development only; the flags are
30
- inlined into the generated scripts and removed by re-running `init` once production is live).
28
+ the site yet (step 1 of the checklist). The production root `root-2026` is compiled in since 2.2.0; against the
29
+ development hub pass its root and CDN explicitly: `--url <cdn-base> --root-keys '<MANIFEST_ROOT_PUBLIC_KEYS json>'`
30
+ (development only; the flags are inlined into the generated scripts and removed by re-running `init --force`
31
+ without them against production).
31
32
 
32
33
  Then paste what `init` prints:
33
34
 
@@ -65,7 +66,7 @@ The timed steps 7–8 of `docs/install-guide.md`, as made on JCM. Node ≥ 18, E
65
66
 
66
67
  ```sh
67
68
  cd backend
68
- npm i @intenque/network-sdk@2.2.0-beta.1
69
+ npm i @intenque/network-sdk@2.2.0
69
70
  ```
70
71
 
71
72
  Env, as `SECRET` values on the backend component (names only in `.env.example`): `INR_ENABLED=false` (only the
@@ -1 +1 @@
1
- "use strict";var u=Object.defineProperty;var m=Object.getOwnPropertyDescriptor;var y=Object.getOwnPropertyNames;var k=Object.prototype.hasOwnProperty;var C=(n,e)=>{for(var t in e)u(n,t,{get:e[t],enumerable:!0})},M=(n,e,t,o)=>{if(e&&typeof e=="object"||typeof e=="function")for(let r of y(e))!k.call(n,r)&&r!==t&&u(n,r,{get:()=>e[r],enumerable:!(o=m(e,r))||o.enumerable});return n};var L=n=>M(u({},"__esModule",{value:!0}),n);var w={};C(w,{attachReferral:()=>A,attachReferralHeader:()=>O,captureReferral:()=>b,clearReferral:()=>N,getReferral:()=>I,onConsentChange:()=>T,setConsent:()=>E});module.exports=L(w);var _="2.2.0-beta.1";function S(n,e,t){let o=s=>{try{t?.(s)}catch{}return s};try{if(location.protocol=="https:"&&/^http:/i.test(n))return o(!1)}catch{}try{if(navigator.sendBeacon(n,e))return o(!0)}catch{}let r=s=>fetch(n,{method:"POST",mode:"no-cors",keepalive:s,credentials:"omit",body:e});try{return r(!0).then(()=>o(!0),()=>{try{if(document.visibilityState=="visible")return void r(!1).then(()=>o(!0),()=>o(!1))}catch{}o(!1)}),!0}catch{return o(!1)}}var f="inr_ref",h=2592e6,a={},l=null,c=(n,e)=>{try{let t=n?localStorage:sessionStorage;if(e===void 0)return JSON.parse(t.getItem(f)||"null");e?t.setItem(f,JSON.stringify(e)):t.removeItem(f)}catch{}return null},R=()=>!!navigator.globalPrivacyControl,p=()=>a.consent?.get()=="granted"&&!R(),g="",x=n=>{let e=a.endpoint,t=n.last.c;if(!e||!a.siteKey||g==t)return;g=t;let o=r=>{let s=Date.now();S(e.replace(/\/+$/,"")+"/landing",JSON.stringify({v:2,k:a.siteKey,c:t,scope:"session",ph:r&&[...new Uint8Array(r,0,8)].map(i=>(i+256).toString(16).slice(1)).join(""),sdk:_,t:s,d:Math.min(h,Math.max(0,s-n.last.t))}))};try{crypto.subtle.digest("SHA-256",new TextEncoder().encode(location.pathname)).then(o,()=>o())}catch{o()}};function b(n={}){a=n;try{let e=new URL(location.href),t=e.searchParams.get("inr");if(!t||(e.searchParams.delete("inr"),history.replaceState(history.state,"",e.href),!/^[A-Za-z0-9_-]{32}$/.test(t)))return;let o={c:t,t:Date.now(),from:e.searchParams.get("utm_source")},r=c(0),s={last:o,first:r?.first||o};if(n.consentMode=="require-consent"&&!p()){l=s;return}c(0,s),p()&&c(1,s),x(s)}catch{}}function E(n){try{if(n=="granted"){let e=l;if(l=null,e&&R())return;let t=e||c(0);t&&(c(0,t),p()&&c(1,t),e&&x(e))}else l=null,c(1,null)}catch{}}var T=E;function N(){l=null,c(0,null),c(1,null)}function I(){try{let n=c(0),e=c(1),t=n&&(!e||n.last.t>=e.last.t)?n:e;return!t||Date.now()-t.last.t>h?null:{clickId:t.last.c,firstClickId:t.first.c==t.last.c?null:t.first.c,capturedAt:t.last.t,tokenScope:n&&n.last.c==t.last.c?"session":"persistent",consented:!!p()}}catch{return null}}function A(n,e={}){n.interceptors.request.use(t=>{try{if(/^(get|head|options)?$/i.test(t.method||""))return t;let o=new URL((t.baseURL||n.defaults?.baseURL||"").replace(/\/*$/,"/"),location.origin),r=new URL((t.url||"").replace(/^\/(?!\/)/,""),o),s=r.pathname;if(r.origin!=o.origin&&!e.origins?.includes(r.origin)||e.routes&&!e.routes.some(d=>d.test?~s.search(d):d==s))return t;let i=I();if(!i)return t;e.mode=="header"?(t.headers=t.headers||{},t.headers["X-INR-Ref"]=i.clickId,t.headers["X-INR-Ref-Scope"]=i.tokenScope,i.firstClickId&&(t.headers["X-INR-Ref-First"]=i.firstClickId)):(t.data==null||Object.getPrototypeOf(t.data)==Object.prototype)&&(t.data={...t.data,inrRef:{clickId:i.clickId,firstClickId:i.firstClickId,tokenScope:i.tokenScope}})}catch{}return t})}var O=(n,e={})=>A(n,{...e,mode:"header"});
1
+ "use strict";var u=Object.defineProperty;var m=Object.getOwnPropertyDescriptor;var y=Object.getOwnPropertyNames;var k=Object.prototype.hasOwnProperty;var C=(n,e)=>{for(var t in e)u(n,t,{get:e[t],enumerable:!0})},M=(n,e,t,o)=>{if(e&&typeof e=="object"||typeof e=="function")for(let r of y(e))!k.call(n,r)&&r!==t&&u(n,r,{get:()=>e[r],enumerable:!(o=m(e,r))||o.enumerable});return n};var L=n=>M(u({},"__esModule",{value:!0}),n);var w={};C(w,{attachReferral:()=>A,attachReferralHeader:()=>O,captureReferral:()=>T,clearReferral:()=>N,getReferral:()=>I,onConsentChange:()=>b,setConsent:()=>E});module.exports=L(w);var _="2.2.0";function S(n,e,t){let o=s=>{try{t?.(s)}catch{}return s};try{if(location.protocol=="https:"&&/^http:/i.test(n))return o(!1)}catch{}try{if(navigator.sendBeacon(n,e))return o(!0)}catch{}let r=s=>fetch(n,{method:"POST",mode:"no-cors",keepalive:s,credentials:"omit",body:e});try{return r(!0).then(()=>o(!0),()=>{try{if(document.visibilityState=="visible")return void r(!1).then(()=>o(!0),()=>o(!1))}catch{}o(!1)}),!0}catch{return o(!1)}}var f="inr_ref",h=2592e6,a={},l=null,c=(n,e)=>{try{let t=n?localStorage:sessionStorage;if(e===void 0)return JSON.parse(t.getItem(f)||"null");e?t.setItem(f,JSON.stringify(e)):t.removeItem(f)}catch{}return null},R=()=>!!navigator.globalPrivacyControl,p=()=>a.consent?.get()=="granted"&&!R(),g="",x=n=>{let e=a.endpoint,t=n.last.c;if(!e||!a.siteKey||g==t)return;g=t;let o=r=>{let s=Date.now();S(e.replace(/\/+$/,"")+"/landing",JSON.stringify({v:2,k:a.siteKey,c:t,scope:"session",ph:r&&[...new Uint8Array(r,0,8)].map(i=>(i+256).toString(16).slice(1)).join(""),sdk:_,t:s,d:Math.min(h,Math.max(0,s-n.last.t))}))};try{crypto.subtle.digest("SHA-256",new TextEncoder().encode(location.pathname)).then(o,()=>o())}catch{o()}};function T(n={}){a=n;try{let e=new URL(location.href),t=e.searchParams.get("inr");if(!t||(e.searchParams.delete("inr"),history.replaceState(history.state,"",e.href),!/^[A-Za-z0-9_-]{32}$/.test(t)))return;let o={c:t,t:Date.now(),from:e.searchParams.get("utm_source")},r=c(0),s={last:o,first:r?.first||o};if(n.consentMode=="require-consent"&&!p()){l=s;return}c(0,s),p()&&c(1,s),x(s)}catch{}}function E(n){try{if(n=="granted"){let e=l;if(l=null,e&&R())return;let t=e||c(0);t&&(c(0,t),p()&&c(1,t),e&&x(e))}else l=null,c(1,null)}catch{}}var b=E;function N(){l=null,c(0,null),c(1,null)}function I(){try{let n=c(0),e=c(1),t=n&&(!e||n.last.t>=e.last.t)?n:e;return!t||Date.now()-t.last.t>h?null:{clickId:t.last.c,firstClickId:t.first.c==t.last.c?null:t.first.c,capturedAt:t.last.t,tokenScope:n&&n.last.c==t.last.c?"session":"persistent",consented:!!p()}}catch{return null}}function A(n,e={}){n.interceptors.request.use(t=>{try{if(/^(get|head|options)?$/i.test(t.method||""))return t;let o=new URL((t.baseURL||n.defaults?.baseURL||"").replace(/\/*$/,"/"),location.origin),r=new URL((t.url||"").replace(/^\/(?!\/)/,""),o),s=r.pathname;if(r.origin!=o.origin&&!e.origins?.includes(r.origin)||e.routes&&!e.routes.some(d=>d.test?~s.search(d):d==s))return t;let i=I();if(!i)return t;e.mode=="header"?(t.headers=t.headers||{},t.headers["X-INR-Ref"]=i.clickId,t.headers["X-INR-Ref-Scope"]=i.tokenScope,i.firstClickId&&(t.headers["X-INR-Ref-First"]=i.firstClickId)):(t.data==null||Object.getPrototypeOf(t.data)==Object.prototype)&&(t.data={...t.data,inrRef:{clickId:i.clickId,firstClickId:i.firstClickId,tokenScope:i.tokenScope}})}catch{}return t})}var O=(n,e={})=>A(n,{...e,mode:"header"});
@@ -1 +1 @@
1
- var f="2.2.0-beta.1";function _(n,e,t){let o=r=>{try{t?.(r)}catch{}return r};try{if(location.protocol=="https:"&&/^http:/i.test(n))return o(!1)}catch{}try{if(navigator.sendBeacon(n,e))return o(!0)}catch{}let s=r=>fetch(n,{method:"POST",mode:"no-cors",keepalive:r,credentials:"omit",body:e});try{return s(!0).then(()=>o(!0),()=>{try{if(document.visibilityState=="visible")return void s(!1).then(()=>o(!0),()=>o(!1))}catch{}o(!1)}),!0}catch{return o(!1)}}var u="inr_ref",g=2592e6,a={},l=null,c=(n,e)=>{try{let t=n?localStorage:sessionStorage;if(e===void 0)return JSON.parse(t.getItem(u)||"null");e?t.setItem(u,JSON.stringify(e)):t.removeItem(u)}catch{}return null},h=()=>!!navigator.globalPrivacyControl,p=()=>a.consent?.get()=="granted"&&!h(),S="",R=n=>{let e=a.endpoint,t=n.last.c;if(!e||!a.siteKey||S==t)return;S=t;let o=s=>{let r=Date.now();_(e.replace(/\/+$/,"")+"/landing",JSON.stringify({v:2,k:a.siteKey,c:t,scope:"session",ph:s&&[...new Uint8Array(s,0,8)].map(i=>(i+256).toString(16).slice(1)).join(""),sdk:f,t:r,d:Math.min(g,Math.max(0,r-n.last.t))}))};try{crypto.subtle.digest("SHA-256",new TextEncoder().encode(location.pathname)).then(o,()=>o())}catch{o()}};function C(n={}){a=n;try{let e=new URL(location.href),t=e.searchParams.get("inr");if(!t||(e.searchParams.delete("inr"),history.replaceState(history.state,"",e.href),!/^[A-Za-z0-9_-]{32}$/.test(t)))return;let o={c:t,t:Date.now(),from:e.searchParams.get("utm_source")},s=c(0),r={last:o,first:s?.first||o};if(n.consentMode=="require-consent"&&!p()){l=r;return}c(0,r),p()&&c(1,r),R(r)}catch{}}function x(n){try{if(n=="granted"){let e=l;if(l=null,e&&h())return;let t=e||c(0);t&&(c(0,t),p()&&c(1,t),e&&R(e))}else l=null,c(1,null)}catch{}}var M=x;function L(){l=null,c(0,null),c(1,null)}function E(){try{let n=c(0),e=c(1),t=n&&(!e||n.last.t>=e.last.t)?n:e;return!t||Date.now()-t.last.t>g?null:{clickId:t.last.c,firstClickId:t.first.c==t.last.c?null:t.first.c,capturedAt:t.last.t,tokenScope:n&&n.last.c==t.last.c?"session":"persistent",consented:!!p()}}catch{return null}}function I(n,e={}){n.interceptors.request.use(t=>{try{if(/^(get|head|options)?$/i.test(t.method||""))return t;let o=new URL((t.baseURL||n.defaults?.baseURL||"").replace(/\/*$/,"/"),location.origin),s=new URL((t.url||"").replace(/^\/(?!\/)/,""),o),r=s.pathname;if(s.origin!=o.origin&&!e.origins?.includes(s.origin)||e.routes&&!e.routes.some(d=>d.test?~r.search(d):d==r))return t;let i=E();if(!i)return t;e.mode=="header"?(t.headers=t.headers||{},t.headers["X-INR-Ref"]=i.clickId,t.headers["X-INR-Ref-Scope"]=i.tokenScope,i.firstClickId&&(t.headers["X-INR-Ref-First"]=i.firstClickId)):(t.data==null||Object.getPrototypeOf(t.data)==Object.prototype)&&(t.data={...t.data,inrRef:{clickId:i.clickId,firstClickId:i.firstClickId,tokenScope:i.tokenScope}})}catch{}return t})}var b=(n,e={})=>I(n,{...e,mode:"header"});export{I as attachReferral,b as attachReferralHeader,C as captureReferral,L as clearReferral,E as getReferral,M as onConsentChange,x as setConsent};
1
+ var f="2.2.0";function _(n,e,t){let o=r=>{try{t?.(r)}catch{}return r};try{if(location.protocol=="https:"&&/^http:/i.test(n))return o(!1)}catch{}try{if(navigator.sendBeacon(n,e))return o(!0)}catch{}let s=r=>fetch(n,{method:"POST",mode:"no-cors",keepalive:r,credentials:"omit",body:e});try{return s(!0).then(()=>o(!0),()=>{try{if(document.visibilityState=="visible")return void s(!1).then(()=>o(!0),()=>o(!1))}catch{}o(!1)}),!0}catch{return o(!1)}}var u="inr_ref",g=2592e6,a={},l=null,c=(n,e)=>{try{let t=n?localStorage:sessionStorage;if(e===void 0)return JSON.parse(t.getItem(u)||"null");e?t.setItem(u,JSON.stringify(e)):t.removeItem(u)}catch{}return null},h=()=>!!navigator.globalPrivacyControl,p=()=>a.consent?.get()=="granted"&&!h(),S="",R=n=>{let e=a.endpoint,t=n.last.c;if(!e||!a.siteKey||S==t)return;S=t;let o=s=>{let r=Date.now();_(e.replace(/\/+$/,"")+"/landing",JSON.stringify({v:2,k:a.siteKey,c:t,scope:"session",ph:s&&[...new Uint8Array(s,0,8)].map(i=>(i+256).toString(16).slice(1)).join(""),sdk:f,t:r,d:Math.min(g,Math.max(0,r-n.last.t))}))};try{crypto.subtle.digest("SHA-256",new TextEncoder().encode(location.pathname)).then(o,()=>o())}catch{o()}};function C(n={}){a=n;try{let e=new URL(location.href),t=e.searchParams.get("inr");if(!t||(e.searchParams.delete("inr"),history.replaceState(history.state,"",e.href),!/^[A-Za-z0-9_-]{32}$/.test(t)))return;let o={c:t,t:Date.now(),from:e.searchParams.get("utm_source")},s=c(0),r={last:o,first:s?.first||o};if(n.consentMode=="require-consent"&&!p()){l=r;return}c(0,r),p()&&c(1,r),R(r)}catch{}}function x(n){try{if(n=="granted"){let e=l;if(l=null,e&&h())return;let t=e||c(0);t&&(c(0,t),p()&&c(1,t),e&&R(e))}else l=null,c(1,null)}catch{}}var M=x;function L(){l=null,c(0,null),c(1,null)}function E(){try{let n=c(0),e=c(1),t=n&&(!e||n.last.t>=e.last.t)?n:e;return!t||Date.now()-t.last.t>g?null:{clickId:t.last.c,firstClickId:t.first.c==t.last.c?null:t.first.c,capturedAt:t.last.t,tokenScope:n&&n.last.c==t.last.c?"session":"persistent",consented:!!p()}}catch{return null}}function I(n,e={}){n.interceptors.request.use(t=>{try{if(/^(get|head|options)?$/i.test(t.method||""))return t;let o=new URL((t.baseURL||n.defaults?.baseURL||"").replace(/\/*$/,"/"),location.origin),s=new URL((t.url||"").replace(/^\/(?!\/)/,""),o),r=s.pathname;if(s.origin!=o.origin&&!e.origins?.includes(s.origin)||e.routes&&!e.routes.some(d=>d.test?~r.search(d):d==r))return t;let i=E();if(!i)return t;e.mode=="header"?(t.headers=t.headers||{},t.headers["X-INR-Ref"]=i.clickId,t.headers["X-INR-Ref-Scope"]=i.tokenScope,i.firstClickId&&(t.headers["X-INR-Ref-First"]=i.firstClickId)):(t.data==null||Object.getPrototypeOf(t.data)==Object.prototype)&&(t.data={...t.data,inrRef:{clickId:i.clickId,firstClickId:i.firstClickId,tokenScope:i.tokenScope}})}catch{}return t})}var T=(n,e={})=>I(n,{...e,mode:"header"});export{I as attachReferral,T as attachReferralHeader,C as captureReferral,L as clearReferral,E as getReferral,M as onConsentChange,x as setConsent};
package/dist/cli/index.js CHANGED
@@ -4,7 +4,7 @@
4
4
  import { webcrypto } from "crypto";
5
5
 
6
6
  // src/constants.ts
7
- var SDK_VERSION = "2.2.0-beta.1";
7
+ var SDK_VERSION = "2.2.0";
8
8
  var MANIFEST_SCHEMA_MAJOR = 2;
9
9
  var DEFAULT_CDN_BASE = "https://inr-network-public.nyc3.cdn.digitaloceanspaces.com/network/v1";
10
10
  var MANIFEST_MAX_BYTES = 24 * 1024;
@@ -129,7 +129,17 @@ import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from "
129
129
  import { dirname, join, resolve } from "path";
130
130
 
131
131
  // src/rootPublicKeys.ts
132
- var rootPublicKeys = [];
132
+ var rootPublicKeys = [
133
+ {
134
+ kid: "root-2026",
135
+ jwk: {
136
+ kty: "EC",
137
+ crv: "P-256",
138
+ x: "9Xly8AJB8fMVSBjA1TK7HnAxxYRXwwlPU54zl_AkUcs",
139
+ y: "Tehja7BG1JLpIHfIMJjSRWPpZKXCvRIXS-EtAv1Mc68"
140
+ }
141
+ }
142
+ ];
133
143
 
134
144
  // src/verify/schema.ts
135
145
  var SLUG_RE = /^[a-z0-9]+(-[a-z0-9]+)*$/;
@@ -141,6 +151,7 @@ var COORD_RE = /^[A-Za-z0-9_-]{42}[AEIMQUYcgkosw048]$/;
141
151
  var ISO_DATE_RE = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d{3})?Z$/;
142
152
  var TOKEN_RE = /^[a-z0-9-]+\.\d+\.\d{8}\.[0-9a-f]{12}$/;
143
153
  var HOST_RE = /^[a-z0-9.-]+$/;
154
+ var URL_TAIL_RE = /^([\w.~!$&'()*+,;=:@/?-]|%[\da-f]{2})*(#([\w.~!$&'()*+,;=:@/?-]|%[\da-f]{2})*)?$/i;
144
155
  var Invalid = class extends Error {
145
156
  };
146
157
  var fail = () => {
@@ -220,7 +231,7 @@ function destinationUrlProblem(v) {
220
231
  if (host.startsWith("[") || /^\d+(\.\d+){3}$/.test(host)) return "IP addresses are not allowed";
221
232
  if (!HOST_RE.test(host) || !host.includes(".") || host.startsWith(".") || host.endsWith("."))
222
233
  return "invalid hostname";
223
- if (u.toString() !== v) return "must be normalised";
234
+ if (u.toString() !== v || !URL_TAIL_RE.test(v.slice(u.origin.length))) return "must be normalised";
224
235
  return null;
225
236
  }
226
237
  function destinationUrl(v) {
@@ -470,9 +481,10 @@ var keyCache = /* @__PURE__ */ new WeakMap();
470
481
  async function importKey(jwk, api) {
471
482
  const hit = keyCache.get(jwk);
472
483
  if (hit) return hit;
484
+ const { kty, crv, x, y } = jwk;
473
485
  const p = api.importKey(
474
486
  "jwk",
475
- { kty: "EC", crv: "P-256", x: jwk.x, y: jwk.y, ext: true },
487
+ { ...parsePublicJwk({ kty, crv, x, y }), ext: true },
476
488
  { name: "ECDSA", namedCurve: "P-256" },
477
489
  false,
478
490
  ["verify"]
@@ -514,7 +526,9 @@ function signedPayload(m) {
514
526
  }
515
527
  var isKidList = (v) => Array.isArray(v) && v.every((k) => typeof k === "string");
516
528
  function parseIso(s) {
517
- return Date.parse(s);
529
+ const t = Date.parse(s);
530
+ if (!new Date(t).toISOString().startsWith(s.slice(0, 19))) fail();
531
+ return t;
518
532
  }
519
533
  async function verifyManifest(input, opts) {
520
534
  try {
@@ -541,8 +555,7 @@ async function verifyManifest(input, opts) {
541
555
  return { ok: false, reason: "root_sig" };
542
556
  const notBefore = parseIso(s.notBefore);
543
557
  const notAfter = parseIso(s.notAfter);
544
- if (!Number.isFinite(notBefore) || !Number.isFinite(notAfter) || notAfter < notBefore)
545
- return { ok: false, reason: "malformed" };
558
+ if (notAfter < notBefore) return { ok: false, reason: "malformed" };
546
559
  if (opts.source === "live" && (t < notBefore || t > notAfter + SIGNER_GRACE_MS))
547
560
  return { ok: false, reason: "window" };
548
561
  if (m.sig.kid !== s.kid) return { ok: false, reason: "kid_mismatch" };