@recomnext/browser 0.3.1-dev.cf35fcbb → 0.3.1-dev.e2cdeffb

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.
@@ -1 +1 @@
1
- {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -2,5 +2,5 @@ export { RecomnextBrowserClient } from './client';
2
2
  export { ImpressionTracker } from './tracker';
3
3
  export type { FireAndForgetPayload } from './client';
4
4
  export type { FacetBucket, SearchFacets, SearchOptions, SearchResponse, SearchV2QueryConstraint, SearchV2ScoreBreakdown, SearchV2ItemScore, SearchV2Options, SearchV2Response, SearchV2AutocompleteResponse, SearchV2SettingsResponse, CatalogItem, CatalogListResponse, ListItemsOptions, } from '@recomnext/shared-search-types';
5
- export type { BrowserClientConfig, RecommendedItem, RecommendationResult, ProjectionOptions, RecommendationOptions, RecommendOptions, PostProcessOverrideOptions, ImpressionTrackerOptions, } from './types';
5
+ export type { BrowserClientConfig, RecommendedItem, RecommendationResult, FilterRelaxation, ProjectionOptions, RecommendationOptions, RecommendOptions, PostProcessOverrideOptions, ImpressionTrackerOptions, } from './types';
6
6
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,sBAAsB,EAAE,MAAM,UAAU,CAAC;AAClD,OAAO,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAC;AAC9C,YAAY,EAAE,oBAAoB,EAAE,MAAM,UAAU,CAAC;AACrD,YAAY,EACV,WAAW,EACX,YAAY,EACZ,aAAa,EACb,cAAc,EACd,uBAAuB,EACvB,sBAAsB,EACtB,iBAAiB,EACjB,eAAe,EACf,gBAAgB,EAChB,4BAA4B,EAC5B,wBAAwB,EACxB,WAAW,EACX,mBAAmB,EACnB,gBAAgB,GACjB,MAAM,gCAAgC,CAAC;AACxC,YAAY,EACV,mBAAmB,EACnB,eAAe,EACf,oBAAoB,EACpB,iBAAiB,EACjB,qBAAqB,EACrB,gBAAgB,EAChB,0BAA0B,EAC1B,wBAAwB,GACzB,MAAM,SAAS,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,sBAAsB,EAAE,MAAM,UAAU,CAAC;AAClD,OAAO,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAC;AAC9C,YAAY,EAAE,oBAAoB,EAAE,MAAM,UAAU,CAAC;AACrD,YAAY,EACV,WAAW,EACX,YAAY,EACZ,aAAa,EACb,cAAc,EACd,uBAAuB,EACvB,sBAAsB,EACtB,iBAAiB,EACjB,eAAe,EACf,gBAAgB,EAChB,4BAA4B,EAC5B,wBAAwB,EACxB,WAAW,EACX,mBAAmB,EACnB,gBAAgB,GACjB,MAAM,gCAAgC,CAAC;AACxC,YAAY,EACV,mBAAmB,EACnB,eAAe,EACf,oBAAoB,EACpB,gBAAgB,EAChB,iBAAiB,EACjB,qBAAqB,EACrB,gBAAgB,EAChB,0BAA0B,EAC1B,wBAAwB,GACzB,MAAM,SAAS,CAAC"}
@@ -1,4 +1,4 @@
1
- function S(n){return I(n)}function I(n){if(n===null)return"null";switch(typeof n){case"boolean":return n?"true":"false";case"number":if(!Number.isFinite(n))throw new TypeError(`Cannot canonicalize non-finite number: ${String(n)}`);return JSON.stringify(n);case"string":return JSON.stringify(n);case"object":{if(Array.isArray(n))return"["+n.map(I).join(",")+"]";let e=Object.keys(n).sort(),t=[];for(let r of e){let s=n[r];typeof s>"u"||t.push(JSON.stringify(r)+":"+I(s))}return"{"+t.join(",")+"}"}case"undefined":throw new TypeError("Cannot canonicalize undefined at the root");default:throw new TypeError(`Cannot canonicalize value of type ${typeof n}`)}}var P=new Map;async function R(n){let e=n.method.toUpperCase(),t=$(n.path),r=n.now??Math.floor(Date.now()/1e3),s=e==="GET"||e==="DELETE"||e==="HEAD"||e==="OPTIONS"?"":await x(S(n.body??null)),i=new Map;for(let[f,c]of Object.entries(n.query??{}))c!=null&&i.set(f,String(c));i.delete("hmac_timestamp"),i.delete("frontend_timestamp"),i.delete("body_hash"),i.delete("hmac_sign"),i.delete("frontend_sign");let o=Array.from(i.entries()).sort(([f],[c])=>f<c?-1:f>c?1:0).map(([f,c])=>`${encodeURIComponent(f)}=${encodeURIComponent(c)}`),u=n.mode==="public"?"frontend_timestamp":"hmac_timestamp",l=n.mode==="public"?"frontend_sign":"hmac_sign",m=`${u}=${r}&body_hash=${s}`,a=o.length?`${o.join("&")}&${m}`:m,d=`${e}
1
+ function S(n){return I(n)}function I(n){if(n===null)return"null";switch(typeof n){case"boolean":return n?"true":"false";case"number":if(!Number.isFinite(n))throw new TypeError(`Cannot canonicalize non-finite number: ${String(n)}`);return JSON.stringify(n);case"string":return JSON.stringify(n);case"object":{if(Array.isArray(n))return"["+n.map(I).join(",")+"]";let e=Object.keys(n).sort(),t=[];for(let r of e){let i=n[r];typeof i>"u"||t.push(JSON.stringify(r)+":"+I(i))}return"{"+t.join(",")+"}"}case"undefined":throw new TypeError("Cannot canonicalize undefined at the root");default:throw new TypeError(`Cannot canonicalize value of type ${typeof n}`)}}var P=new Map;async function R(n){let e=n.method.toUpperCase(),t=A(n.path),r=n.now??Math.floor(Date.now()/1e3),i=e==="GET"||e==="DELETE"||e==="HEAD"||e==="OPTIONS"?"":await T(S(n.body??null)),s=new Map;for(let[f,c]of Object.entries(n.query??{}))c!=null&&s.set(f,String(c));s.delete("hmac_timestamp"),s.delete("frontend_timestamp"),s.delete("body_hash"),s.delete("hmac_sign"),s.delete("frontend_sign");let o=Array.from(s.entries()).sort(([f],[c])=>f<c?-1:f>c?1:0).map(([f,c])=>`${encodeURIComponent(f)}=${encodeURIComponent(c)}`),u=n.mode==="public"?"frontend_timestamp":"hmac_timestamp",l=n.mode==="public"?"frontend_sign":"hmac_sign",m=`${u}=${r}&body_hash=${i}`,a=o.length?`${o.join("&")}&${m}`:m,d=`${e}
2
2
  ${t}?${a}
3
- X-Tenant-Id:${n.tenantId}`,h=await O(n.token,d);return{signedPath:`${t}?${a}&${l}=${h}`,timestamp:r,bodyHash:s,signature:h,idempotencyKey:q()}}async function x(n){let e=await crypto.subtle.digest("SHA-256",new TextEncoder().encode(n));return b(new Uint8Array(e))}async function O(n,e){let t=await T(n),r=await crypto.subtle.sign("HMAC",t,new TextEncoder().encode(e));return b(new Uint8Array(r))}async function T(n){let e=P.get(n);return e||(e=crypto.subtle.importKey("raw",new TextEncoder().encode(n),{name:"HMAC",hash:"SHA-256"},!1,["sign"]),P.set(n,e)),e}function w(n){return T(n)}function b(n){let e="";for(let t=0;t<n.length;t++)e+=n[t].toString(16).padStart(2,"0");return e}function $(n){let e=n.replace(/\/$/,"");return e.length>0?e:"/"}function q(){let n=globalThis.crypto;if(n?.randomUUID)return n.randomUUID();let e=new Uint8Array(16);n?.getRandomValues(e),e[6]=e[6]&15|64,e[8]=e[8]&63|128;let t=b(e);return`${t.slice(0,8)}-${t.slice(8,12)}-${t.slice(12,16)}-${t.slice(16,20)}-${t.slice(20)}`}var y={maxAttempts:8,initialBackoffMs:1e3,maxBackoffMs:6e5,flushIntervalMs:2e3},g=class{constructor(e){if(this.items=[],this.timer=null,this.draining=!1,this.cfg={persist:e.persist??!1,persistKey:e.persistKey,maxAttempts:e.maxAttempts??y.maxAttempts,initialBackoffMs:e.initialBackoffMs??y.initialBackoffMs,maxBackoffMs:e.maxBackoffMs??y.maxBackoffMs,flushIntervalMs:e.flushIntervalMs??y.flushIntervalMs,onRetryDrop:e.onRetryDrop,signRequest:e.signRequest},this.cfg.persist){if(!this.cfg.persistKey)throw new Error("RetryQueue: persistKey required when persist=true");this.idb=E(),this.loadPersisted()}typeof window<"u"&&(this.timer=setInterval(()=>void this.drain(),this.cfg.flushIntervalMs),window.addEventListener("online",()=>void this.drain()),window.addEventListener("pagehide",()=>void this.drain()))}enqueue(e){let t={...e,id:e.id??k(),attempts:e.attempts??0,nextAttemptAt:e.nextAttemptAt??Date.now()};this.items.push(t),this.persist(),this.drain()}flush(){return this.drain()}destroy(){this.timer&&clearInterval(this.timer),this.timer=null}async drain(){if(!this.draining&&!(typeof navigator<"u"&&navigator.onLine===!1)){this.draining=!0;try{let e=Date.now(),t=this.items.filter(r=>(r.nextAttemptAt??0)<=e);for(let r of t)await this.attempt(r);await this.persist()}finally{this.draining=!1}}}async attempt(e){e.attempts=(e.attempts??0)+1;try{let{url:t,headers:r}=await this.cfg.signRequest(e),s=await fetch(t,{method:e.method,headers:{"Content-Type":"application/json",...r},body:JSON.stringify(e.payload),keepalive:!0});if(s.ok){this.removeById(e.id);return}if(s.status>=400&&s.status<500&&s.status!==429){this.removeById(e.id),this.cfg.onRetryDrop?.(e,new Error(`HTTP ${s.status}`));return}this.scheduleRetry(e,new Error(`HTTP ${s.status}`))}catch(t){this.scheduleRetry(e,t)}}scheduleRetry(e,t){if((e.attempts??0)>=this.cfg.maxAttempts){this.removeById(e.id),this.cfg.onRetryDrop?.(e,t);return}let r=Math.min(this.cfg.maxBackoffMs,this.cfg.initialBackoffMs*Math.pow(2,(e.attempts??1)-1)),s=.5+Math.random()*.5;e.nextAttemptAt=Date.now()+Math.floor(r*s)}removeById(e){this.items=this.items.filter(t=>t.id!==e)}async persist(){if(!(!this.cfg.persist||!this.idb||!this.cfg.persistKey))try{let e=await this.idb;if(!e)return;let t=e.transaction("queue","readwrite");t.objectStore("queue").put(JSON.parse(JSON.stringify(this.items)),this.cfg.persistKey),await C(t)}catch{}}async loadPersisted(){if(!(!this.cfg.persist||!this.idb||!this.cfg.persistKey))try{let e=await this.idb;if(!e)return;let s=e.transaction("queue","readonly").objectStore("queue").get(this.cfg.persistKey),i=await new Promise((o,u)=>{s.onsuccess=()=>o(s.result),s.onerror=()=>u(s.error)});Array.isArray(i)&&(this.items=i,this.drain())}catch{}}};function k(){let n=globalThis.crypto;return n?.randomUUID?n.randomUUID():`rnx-${Date.now()}-${Math.random().toString(36).slice(2,11)}`}function E(){return typeof indexedDB>"u"?Promise.resolve(null):new Promise(n=>{let e=indexedDB.open("recomnext-retry-queue",1);e.onupgradeneeded=()=>{let t=e.result;t.objectStoreNames.contains("queue")||t.createObjectStore("queue")},e.onsuccess=()=>n(e.result),e.onerror=()=>n(null),e.onblocked=()=>n(null)})}function C(n){return new Promise((e,t)=>{n.oncomplete=()=>e(),n.onerror=()=>t(n.error),n.onabort=()=>t(n.error)})}var p=class{constructor(e,t,r,s,i){this.queue=[];this.flushing=!1;this.observer=null;this.seen=new Set;this.timer=null;this.getUserId=r,this.getSessionId=s,this.retryQueue=i,typeof window<"u"&&(this.timer=setInterval(()=>this.flush(),2e3),window.addEventListener("pagehide",()=>this.flush(!0)))}observe(e,t={}){let r=t.itemSelector??"[data-item-id]",s=t.threshold??.5,i=t.once!==!1,o=t.scenario??"";return this.observer=new IntersectionObserver(l=>{for(let m of l){if(!m.isIntersecting)continue;let a=m.target,d=a.dataset.itemId;if(!d)continue;let h=`${o}:${d}`;i&&this.seen.has(h)||(this.seen.add(h),this.queue.push({itemId:d,scenario:o}),i&&this.observer?.unobserve(a))}},{root:null,threshold:s}),e.querySelectorAll(r).forEach(l=>this.observer.observe(l)),()=>{this.observer?.disconnect(),this.observer=null}}flush(e=!1){if(this.flushing||this.queue.length===0)return;this.flushing=!0;let t=this.queue.splice(0,this.queue.length),r=new Map;for(let{itemId:i,scenario:o}of t)r.has(o)||r.set(o,[]),r.get(o).push(i);let s=Array.from(r.entries()).map(([i,o])=>({userId:this.getUserId()||void 0,sessionId:this.getSessionId()||void 0,itemIds:o,scenarioSlug:i||void 0,timestamp:new Date().toISOString()}));this.retryQueue.enqueue({path:"/ingestion/impressions",method:"POST",payload:{impressions:s}}),e&&this.retryQueue.flush(),this.flushing=!1}destroy(){this.flush(),this.observer?.disconnect(),this.timer&&clearInterval(this.timer)}};var A="recomnext_session_id",U=3,F=200,_=2e3,M=new Set([429,500,502,503,504]),Q="auth_replay_cache_unavailable",B=new Set(["/identity/merge"]),v=class{constructor(e){this.baseUrl=e.baseUrl.replace(/\/+$/,""),this.tenantId=e.tenantId,this.publicToken=e.publicToken,this.userId=e.userId??"",this.sessionId=e.sessionId??this.resolveSessionId(),this.publicToken&&w(this.publicToken),this.fireAndForgetQueue=new g({persist:e.persistRetries??!1,persistKey:`rnx:${this.tenantId}:fnf`,onRetryDrop:e.onRetryDrop,signRequest:t=>this.signFireAndForget(t)}),this.tracker=new p(this.baseUrl,this.tenantId,()=>this.userId,()=>this.sessionId,this.fireAndForgetQueue)}resolveSessionId(){if(typeof sessionStorage>"u")return"";let e=sessionStorage.getItem(A);return e||(e=`sess_${Date.now()}_${Math.random().toString(36).slice(2,9)}`,sessionStorage.setItem(A,e)),e}effectiveUserId(){return this.userId||this.sessionId}async request(e,t,r,s){let i=B.has(t)?0:U,o;for(let u=0;u<=i;u++){if(u>0){let d=Math.min(_,F*Math.pow(2,u-1)),h=.5+Math.random()*.5;await K(Math.floor(d*h))}let{url:l,headers:m}=await this.buildSignedRequest(e,t,s,r),a;try{a=await fetch(l,{method:e,headers:{"Content-Type":"application/json",...m},body:r!==void 0?JSON.stringify(r):void 0})}catch(d){if(o=d,u>=i)throw d;continue}if(a.status===503){let d=await V(a.clone());if(d===Q&&u<i){o=new Error(`recomnext: ${e} ${t} ${a.status} ${d}`);continue}}if(M.has(a.status)&&u<i){o=new Error(`recomnext: ${e} ${t} returned ${a.status}`);continue}if(!a.ok){let d=await a.text().catch(()=>"");throw new Error(`recomnext: ${e} ${t} returned ${a.status}: ${d}`)}return a.status===204?void 0:a.json()}throw o??new Error(`recomnext: ${e} ${t} retries exhausted`)}async signFireAndForget(e){let{url:t,headers:r}=await this.buildSignedRequest(e.method,e.path,void 0,e.payload);return{url:t,headers:r}}async buildSignedRequest(e,t,r,s){let i={"X-Tenant-Id":this.tenantId};if(!this.publicToken){let u=D(r);return{url:`${this.baseUrl}${t}${u}`,headers:i}}let o=await R({method:e,path:t,query:r,body:s??null,tenantId:this.tenantId,token:this.publicToken,mode:"public"});return{url:`${this.baseUrl}${o.signedPath}`,headers:{...i,"X-Idempotency-Key":o.idempotencyKey}}}qs(e){return e}includeQuery(e){return{returnProperties:e?.returnProperties,includedProperties:e?.includedProperties&&e.includedProperties.length>0?e.includedProperties.join(","):void 0}}overrideQuery(e){if(!e)return{};let t=r=>{if(r!==void 0)return r===null||r.length===0?"":r.join(",")};return{filter:e.filter===void 0?void 0:e.filter===null?"":e.filter,boosters:t(e.boosters),rulesSlugs:t(e.rulesSlugs),segmentationId:e.segmentationId===void 0?void 0:e.segmentationId===null?"":e.segmentationId,itemsPerSegment:e.itemsPerSegment===void 0?void 0:e.itemsPerSegment===null?"":e.itemsPerSegment}}overrideBody(e){if(!e)return{};let t={};return e.filter!==void 0&&(t.filter=e.filter),e.boosters!==void 0&&(t.boosters=e.boosters),e.rulesSlugs!==void 0&&(t.rulesSlugs=e.rulesSlugs),e.segmentationId!==void 0&&(t.segmentationId=e.segmentationId),e.itemsPerSegment!==void 0&&(t.itemsPerSegment=e.itemsPerSegment),t}getRecommendations(e){return this.request("GET",`/recommendations/user-to-item/${this.effectiveUserId()}`,void 0,this.qs({count:e?.count,scenario:e?.scenario,...this.includeQuery(e),...this.overrideQuery(e)}))}getRelatedItems(e,t){return this.request("GET",`/recommendations/item-to-item/${e}`,void 0,this.qs({count:t?.count,scenario:t?.scenario,...this.includeQuery(t),...this.overrideQuery(t)}))}getSimilarProducts(e,t){return this.request("GET",`/recommendations/similar-products/${e}`,void 0,this.qs({count:t?.count,scenario:t?.scenario,...this.includeQuery(t),...this.overrideQuery(t)}))}getRecentlyViewed(e){return this.request("GET",`/recommendations/recently-viewed/${this.effectiveUserId()}`,void 0,this.qs({count:e?.count,scenario:e?.scenario,...this.includeQuery(e),...this.overrideQuery(e)}))}getCartRecommendations(e,t){return this.request("POST","/recommendations/items-to-items",{itemIds:e,count:t?.count??10,returnProperties:t?.returnProperties,includedProperties:t?.includedProperties,...this.overrideBody(t)},this.qs({scenario:t?.scenario}))}recommend(e){return this.request("POST","/recommendations",{...e,userId:this.effectiveUserId()})}search(e,t){return this.request("GET","/recommendations/search",void 0,this.qs({query:e,count:t?.count,category:t?.category,priceRange:t?.priceRange,userId:t?.userId??(this.userId||void 0)}))}searchV2(e,t){return this.request("GET","/recommendations/search/v2",void 0,this.qs({query:e,count:t?.count,category:t?.category,priceRange:t?.priceRange,userId:t?.userId??(this.userId||void 0),locale:t?.locale,debugScores:t?.debugScores===!0?!0:void 0,algorithm:t?.algorithm}))}autocomplete(e,t){return this.request("GET","/recommendations/search/v2/autocomplete",void 0,this.qs({query:e,limit:t?.limit}))}getSearchV2Settings(){return this.request("GET","/recommendations/search/v2/settings")}listItems(e){return this.request("GET","/catalog/items",void 0,this.qs({page:e?.page,limit:e?.limit,category:e?.category,search:e?.search,availability:e?.availability}))}getCategories(){return this.request("GET","/catalog/items/categories")}getItem(e){return this.request("GET",`/catalog/items/${encodeURIComponent(e)}`)}trackView(e,t){this.fireAndForgetQueue.enqueue({path:"/ingestion/interactions",method:"POST",payload:{interactions:[{userId:this.effectiveUserId(),itemId:e,type:"view",scenarioSlug:t||void 0,timestamp:new Date().toISOString()}]}})}trackCart(e,t){this.fireAndForgetQueue.enqueue({path:"/ingestion/interactions",method:"POST",payload:{interactions:[{userId:this.effectiveUserId(),itemId:e,type:"cart",scenarioSlug:t||void 0,timestamp:new Date().toISOString()}]}})}trackPurchase(e,t){this.fireAndForgetQueue.enqueue({path:"/ingestion/interactions",method:"POST",payload:{interactions:[{userId:this.effectiveUserId(),itemId:e,type:"purchase",scenarioSlug:t||void 0,timestamp:new Date().toISOString()}]}})}trackImpressions(e,t){return this.tracker.observe(e,t)}setUserId(e){this.userId=e}mergeIdentity(e){let t=this.sessionId;return this.userId=e,this.request("POST","/identity/merge",{userId:e,sessionIds:[t]})}destroy(){this.tracker.destroy(),this.fireAndForgetQueue.destroy()}};function D(n){if(!n)return"";let e=[];for(let[t,r]of Object.entries(n))r!=null&&e.push(`${encodeURIComponent(t)}=${encodeURIComponent(String(r))}`);return e.length?`?${e.join("&")}`:""}async function V(n){try{let e=await n.json();return typeof e.errorCode=="string"?e.errorCode:void 0}catch{return}}function K(n){return new Promise(e=>setTimeout(e,n))}export{p as ImpressionTracker,v as RecomnextBrowserClient};
3
+ X-Tenant-Id:${n.tenantId}`,h=await O(n.token,d);return{signedPath:`${t}?${a}&${l}=${h}`,timestamp:r,bodyHash:i,signature:h,idempotencyKey:$()}}async function T(n){let e=await crypto.subtle.digest("SHA-256",new TextEncoder().encode(n));return w(new Uint8Array(e))}async function O(n,e){let t=await x(n),r=await crypto.subtle.sign("HMAC",t,new TextEncoder().encode(e));return w(new Uint8Array(r))}async function x(n){let e=P.get(n);return e||(e=crypto.subtle.importKey("raw",new TextEncoder().encode(n),{name:"HMAC",hash:"SHA-256"},!1,["sign"]),P.set(n,e)),e}function b(n){return x(n)}function w(n){let e="";for(let t=0;t<n.length;t++)e+=n[t].toString(16).padStart(2,"0");return e}function A(n){let e=n.replace(/\/$/,"");return e.length>0?e:"/"}function $(){let n=globalThis.crypto;if(n?.randomUUID)return n.randomUUID();let e=new Uint8Array(16);n?.getRandomValues(e),e[6]=e[6]&15|64,e[8]=e[8]&63|128;let t=w(e);return`${t.slice(0,8)}-${t.slice(8,12)}-${t.slice(12,16)}-${t.slice(16,20)}-${t.slice(20)}`}var y={maxAttempts:8,initialBackoffMs:1e3,maxBackoffMs:6e5,flushIntervalMs:2e3},g=class{constructor(e){if(this.items=[],this.timer=null,this.draining=!1,this.cfg={persist:e.persist??!1,persistKey:e.persistKey,maxAttempts:e.maxAttempts??y.maxAttempts,initialBackoffMs:e.initialBackoffMs??y.initialBackoffMs,maxBackoffMs:e.maxBackoffMs??y.maxBackoffMs,flushIntervalMs:e.flushIntervalMs??y.flushIntervalMs,onRetryDrop:e.onRetryDrop,signRequest:e.signRequest},this.cfg.persist){if(!this.cfg.persistKey)throw new Error("RetryQueue: persistKey required when persist=true");this.idb=k(),this.loadPersisted()}typeof window<"u"&&(this.timer=setInterval(()=>void this.drain(),this.cfg.flushIntervalMs),window.addEventListener("online",()=>void this.drain()),window.addEventListener("pagehide",()=>void this.drain()))}enqueue(e){let t={...e,id:e.id??q(),attempts:e.attempts??0,nextAttemptAt:e.nextAttemptAt??Date.now()};this.items.push(t),this.persist(),this.drain()}flush(){return this.drain()}destroy(){this.timer&&clearInterval(this.timer),this.timer=null}async drain(){if(!this.draining&&!(typeof navigator<"u"&&navigator.onLine===!1)){this.draining=!0;try{let e=Date.now(),t=this.items.filter(r=>(r.nextAttemptAt??0)<=e);for(let r of t)await this.attempt(r);await this.persist()}finally{this.draining=!1}}}async attempt(e){e.attempts=(e.attempts??0)+1;try{let{url:t,headers:r}=await this.cfg.signRequest(e),i=await fetch(t,{method:e.method,headers:{"Content-Type":"application/json",...r},body:JSON.stringify(e.payload),keepalive:!0});if(i.ok){this.removeById(e.id);return}if(i.status>=400&&i.status<500&&i.status!==429){this.removeById(e.id),this.cfg.onRetryDrop?.(e,new Error(`HTTP ${i.status}`));return}this.scheduleRetry(e,new Error(`HTTP ${i.status}`))}catch(t){this.scheduleRetry(e,t)}}scheduleRetry(e,t){if((e.attempts??0)>=this.cfg.maxAttempts){this.removeById(e.id),this.cfg.onRetryDrop?.(e,t);return}let r=Math.min(this.cfg.maxBackoffMs,this.cfg.initialBackoffMs*Math.pow(2,(e.attempts??1)-1)),i=.5+Math.random()*.5;e.nextAttemptAt=Date.now()+Math.floor(r*i)}removeById(e){this.items=this.items.filter(t=>t.id!==e)}async persist(){if(!(!this.cfg.persist||!this.idb||!this.cfg.persistKey))try{let e=await this.idb;if(!e)return;let t=e.transaction("queue","readwrite");t.objectStore("queue").put(JSON.parse(JSON.stringify(this.items)),this.cfg.persistKey),await F(t)}catch{}}async loadPersisted(){if(!(!this.cfg.persist||!this.idb||!this.cfg.persistKey))try{let e=await this.idb;if(!e)return;let i=e.transaction("queue","readonly").objectStore("queue").get(this.cfg.persistKey),s=await new Promise((o,u)=>{i.onsuccess=()=>o(i.result),i.onerror=()=>u(i.error)});Array.isArray(s)&&(this.items=s,this.drain())}catch{}}};function q(){let n=globalThis.crypto;return n?.randomUUID?n.randomUUID():`rnx-${Date.now()}-${Math.random().toString(36).slice(2,11)}`}function k(){return typeof indexedDB>"u"?Promise.resolve(null):new Promise(n=>{let e=indexedDB.open("recomnext-retry-queue",1);e.onupgradeneeded=()=>{let t=e.result;t.objectStoreNames.contains("queue")||t.createObjectStore("queue")},e.onsuccess=()=>n(e.result),e.onerror=()=>n(null),e.onblocked=()=>n(null)})}function F(n){return new Promise((e,t)=>{n.oncomplete=()=>e(),n.onerror=()=>t(n.error),n.onabort=()=>t(n.error)})}var p=class{constructor(e,t,r,i,s){this.queue=[];this.flushing=!1;this.observer=null;this.seen=new Set;this.timer=null;this.getUserId=r,this.getSessionId=i,this.retryQueue=s,typeof window<"u"&&(this.timer=setInterval(()=>this.flush(),2e3),window.addEventListener("pagehide",()=>this.flush(!0)))}observe(e,t={}){let r=t.itemSelector??"[data-item-id]",i=t.threshold??.5,s=t.once!==!1,o=t.scenario??"";return this.observer=new IntersectionObserver(l=>{for(let m of l){if(!m.isIntersecting)continue;let a=m.target,d=a.dataset.itemId;if(!d)continue;let h=`${o}:${d}`;s&&this.seen.has(h)||(this.seen.add(h),this.queue.push({itemId:d,scenario:o}),s&&this.observer?.unobserve(a))}},{root:null,threshold:i}),e.querySelectorAll(r).forEach(l=>this.observer.observe(l)),()=>{this.observer?.disconnect(),this.observer=null}}flush(e=!1){if(this.flushing||this.queue.length===0)return;this.flushing=!0;let t=this.queue.splice(0,this.queue.length),r=new Map;for(let{itemId:s,scenario:o}of t)r.has(o)||r.set(o,[]),r.get(o).push(s);let i=Array.from(r.entries()).map(([s,o])=>({userId:this.getUserId()||void 0,sessionId:this.getSessionId()||void 0,itemIds:o,scenarioSlug:s||void 0,timestamp:new Date().toISOString()}));this.retryQueue.enqueue({path:"/ingestion/impressions",method:"POST",payload:{impressions:i}}),e&&this.retryQueue.flush(),this.flushing=!1}destroy(){this.flush(),this.observer?.disconnect(),this.timer&&clearInterval(this.timer)}};var E="recomnext_session_id",C=3,U=200,_=2e3,M=new Set([429,500,502,503,504]),Q="auth_replay_cache_unavailable",B=new Set(["/identity/merge"]),v=class{constructor(e){this.baseUrl=e.baseUrl.replace(/\/+$/,""),this.tenantId=e.tenantId,this.publicToken=e.publicToken,this.userId=e.userId??"",this.sessionId=e.sessionId??this.resolveSessionId(),this.publicToken&&b(this.publicToken),this.fireAndForgetQueue=new g({persist:e.persistRetries??!1,persistKey:`rnx:${this.tenantId}:fnf`,onRetryDrop:e.onRetryDrop,signRequest:t=>this.signFireAndForget(t)}),this.tracker=new p(this.baseUrl,this.tenantId,()=>this.userId,()=>this.sessionId,this.fireAndForgetQueue)}resolveSessionId(){if(typeof sessionStorage>"u")return"";let e=sessionStorage.getItem(E);return e||(e=`sess_${Date.now()}_${Math.random().toString(36).slice(2,9)}`,sessionStorage.setItem(E,e)),e}effectiveUserId(){return this.userId||this.sessionId}async request(e,t,r,i){let s=B.has(t)?0:C,o;for(let u=0;u<=s;u++){if(u>0){let d=Math.min(_,U*Math.pow(2,u-1)),h=.5+Math.random()*.5;await K(Math.floor(d*h))}let{url:l,headers:m}=await this.buildSignedRequest(e,t,i,r),a;try{a=await fetch(l,{method:e,headers:{"Content-Type":"application/json",...m},body:r!==void 0?JSON.stringify(r):void 0})}catch(d){if(o=d,u>=s)throw d;continue}if(a.status===503){let d=await V(a.clone());if(d===Q&&u<s){o=new Error(`recomnext: ${e} ${t} ${a.status} ${d}`);continue}}if(M.has(a.status)&&u<s){o=new Error(`recomnext: ${e} ${t} returned ${a.status}`);continue}if(!a.ok){let d=await a.text().catch(()=>"");throw new Error(`recomnext: ${e} ${t} returned ${a.status}: ${d}`)}return a.status===204?void 0:a.json()}throw o??new Error(`recomnext: ${e} ${t} retries exhausted`)}async signFireAndForget(e){let{url:t,headers:r}=await this.buildSignedRequest(e.method,e.path,void 0,e.payload);return{url:t,headers:r}}async buildSignedRequest(e,t,r,i){let s={"X-Tenant-Id":this.tenantId};if(!this.publicToken){let u=D(r);return{url:`${this.baseUrl}${t}${u}`,headers:s}}let o=await R({method:e,path:t,query:r,body:i??null,tenantId:this.tenantId,token:this.publicToken,mode:"public"});return{url:`${this.baseUrl}${o.signedPath}`,headers:{...s,"X-Idempotency-Key":o.idempotencyKey}}}qs(e){return e}includeQuery(e){return{returnProperties:e?.returnProperties,includedProperties:e?.includedProperties&&e.includedProperties.length>0?e.includedProperties.join(","):void 0}}overrideQuery(e){if(!e)return{};let t=r=>{if(r!==void 0)return r===null||r.length===0?"":r.join(",")};return{filter:e.filter===void 0?void 0:e.filter===null?"":e.filter,boosters:t(e.boosters),rulesSlugs:t(e.rulesSlugs),segmentationId:e.segmentationId===void 0?void 0:e.segmentationId===null?"":e.segmentationId,itemsPerSegment:e.itemsPerSegment===void 0?void 0:e.itemsPerSegment===null?"":e.itemsPerSegment,autoRelaxFilters:e.autoRelaxFilters,relaxEntireFilterOnEmpty:e.relaxEntireFilterOnEmpty}}overrideBody(e){if(!e)return{};let t={};return e.filter!==void 0&&(t.filter=e.filter),e.boosters!==void 0&&(t.boosters=e.boosters),e.rulesSlugs!==void 0&&(t.rulesSlugs=e.rulesSlugs),e.segmentationId!==void 0&&(t.segmentationId=e.segmentationId),e.itemsPerSegment!==void 0&&(t.itemsPerSegment=e.itemsPerSegment),e.autoRelaxFilters!==void 0&&(t.autoRelaxFilters=e.autoRelaxFilters),e.relaxEntireFilterOnEmpty!==void 0&&(t.relaxEntireFilterOnEmpty=e.relaxEntireFilterOnEmpty),t}getRecommendations(e){return this.request("GET",`/recommendations/user-to-item/${this.effectiveUserId()}`,void 0,this.qs({count:e?.count,scenario:e?.scenario,...this.includeQuery(e),...this.overrideQuery(e)}))}getRelatedItems(e,t){return this.request("GET",`/recommendations/item-to-item/${e}`,void 0,this.qs({count:t?.count,scenario:t?.scenario,...this.includeQuery(t),...this.overrideQuery(t)}))}getSimilarProducts(e,t){return this.request("GET",`/recommendations/similar-products/${e}`,void 0,this.qs({count:t?.count,scenario:t?.scenario,...this.includeQuery(t),...this.overrideQuery(t)}))}getRecentlyViewed(e){return this.request("GET",`/recommendations/recently-viewed/${this.effectiveUserId()}`,void 0,this.qs({count:e?.count,scenario:e?.scenario,...this.includeQuery(e),...this.overrideQuery(e)}))}getCartRecommendations(e,t){return this.request("POST","/recommendations/items-to-items",{itemIds:e,count:t?.count??10,returnProperties:t?.returnProperties,includedProperties:t?.includedProperties,...this.overrideBody(t)},this.qs({scenario:t?.scenario}))}recommend(e){return this.request("POST","/recommendations",{...e,userId:this.effectiveUserId()})}search(e,t){return this.request("GET","/recommendations/search",void 0,this.qs({query:e,count:t?.count,category:t?.category,priceRange:t?.priceRange,userId:t?.userId??(this.userId||void 0)}))}searchV2(e,t){return this.request("GET","/recommendations/search/v2",void 0,this.qs({query:e,count:t?.count,category:t?.category,priceRange:t?.priceRange,userId:t?.userId??(this.userId||void 0),locale:t?.locale,debugScores:t?.debugScores===!0?!0:void 0,algorithm:t?.algorithm}))}autocomplete(e,t){return this.request("GET","/recommendations/search/v2/autocomplete",void 0,this.qs({query:e,limit:t?.limit}))}getSearchV2Settings(){return this.request("GET","/recommendations/search/v2/settings")}listItems(e){return this.request("GET","/catalog/items",void 0,this.qs({page:e?.page,limit:e?.limit,category:e?.category,search:e?.search,availability:e?.availability}))}getCategories(){return this.request("GET","/catalog/items/categories")}getItem(e){return this.request("GET",`/catalog/items/${encodeURIComponent(e)}`)}trackView(e,t){this.fireAndForgetQueue.enqueue({path:"/ingestion/interactions",method:"POST",payload:{interactions:[{userId:this.effectiveUserId(),itemId:e,type:"view",scenarioSlug:t||void 0,timestamp:new Date().toISOString()}]}})}trackCart(e,t){this.fireAndForgetQueue.enqueue({path:"/ingestion/interactions",method:"POST",payload:{interactions:[{userId:this.effectiveUserId(),itemId:e,type:"cart",scenarioSlug:t||void 0,timestamp:new Date().toISOString()}]}})}trackPurchase(e,t){this.fireAndForgetQueue.enqueue({path:"/ingestion/interactions",method:"POST",payload:{interactions:[{userId:this.effectiveUserId(),itemId:e,type:"purchase",scenarioSlug:t||void 0,timestamp:new Date().toISOString()}]}})}trackImpressions(e,t){return this.tracker.observe(e,t)}setUserId(e){this.userId=e}mergeIdentity(e){let t=this.sessionId;return this.userId=e,this.request("POST","/identity/merge",{userId:e,sessionIds:[t]})}destroy(){this.tracker.destroy(),this.fireAndForgetQueue.destroy()}};function D(n){if(!n)return"";let e=[];for(let[t,r]of Object.entries(n))r!=null&&e.push(`${encodeURIComponent(t)}=${encodeURIComponent(String(r))}`);return e.length?`?${e.join("&")}`:""}async function V(n){try{let e=await n.json();return typeof e.errorCode=="string"?e.errorCode:void 0}catch{return}}function K(n){return new Promise(e=>setTimeout(e,n))}export{p as ImpressionTracker,v as RecomnextBrowserClient};
4
4
  //# sourceMappingURL=recomnext-browser.esm.js.map
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../shared-browser-auth/src/canonical-json.ts", "../../shared-browser-auth/src/sign.ts", "../../shared-browser-auth/src/retry-queue.ts", "../src/tracker.ts", "../src/client.ts"],
4
- "sourcesContent": ["// Canonical JSON serializer \u2014 byte-identical with the engine, the Node\n// server SDK, and the Python SDK. See repo-root\n// `tests/auth/canonical-vectors.json` for the conformance gate.\n\nexport function canonicalJson(value: unknown): string {\n return stringify(value);\n}\n\nfunction stringify(value: unknown): string {\n if (value === null) return 'null';\n switch (typeof value) {\n case 'boolean':\n return value ? 'true' : 'false';\n case 'number':\n if (!Number.isFinite(value)) {\n throw new TypeError(\n `Cannot canonicalize non-finite number: ${String(value)}`,\n );\n }\n return JSON.stringify(value);\n case 'string':\n return JSON.stringify(value);\n case 'object': {\n if (Array.isArray(value)) {\n return '[' + value.map(stringify).join(',') + ']';\n }\n const keys = Object.keys(value as Record<string, unknown>).sort();\n const parts: string[] = [];\n for (const k of keys) {\n const v = (value as Record<string, unknown>)[k];\n if (typeof v === 'undefined') continue;\n parts.push(JSON.stringify(k) + ':' + stringify(v));\n }\n return '{' + parts.join(',') + '}';\n }\n case 'undefined':\n throw new TypeError('Cannot canonicalize undefined at the root');\n default:\n throw new TypeError(\n `Cannot canonicalize value of type ${typeof value}`,\n );\n }\n}\n", "// WebCrypto-based HMAC-SHA256 signing for browser-side recomnext callers.\n// Same wire format as the server SDK (sdks/node/src/signing.ts) and the\n// engine guard (engine/src/auth/guards/hmac.guard.ts).\n//\n// Public vs private token contract:\n// - PUBLIC token: shipped in JS bundles. Wire uses `frontend_sign` +\n// `frontend_timestamp` query params. Engine rejects unless the request\n// origin is on the tenant's allow-list AND the route is on the\n// public-write whitelist (or is a GET).\n// - PRIVATE token: NEVER ship to a browser. WebCrypto signing of private\n// tokens is provided here for completeness (e.g. for Node-via-bundler\n// consumers), but the recomnext browser SDK refuses to accept a\n// privateToken in its config.\n\nimport { canonicalJson } from './canonical-json.js';\n\nexport type SignerMode = 'public' | 'private';\n\nexport interface SignInput {\n method: string;\n // Path WITHOUT querystring or trailing slash.\n path: string;\n // Caller-supplied query params; values coerced to strings.\n query?: Record<string, string | number | boolean | undefined>;\n // Parsed body \u2014 undefined for GET/DELETE/HEAD/OPTIONS.\n body?: unknown;\n tenantId: string;\n token: string;\n mode: SignerMode;\n // Test-only clock override.\n now?: number;\n}\n\nexport interface SignedRequest {\n signedPath: string;\n timestamp: number;\n bodyHash: string;\n signature: string;\n idempotencyKey: string;\n}\n\n// Cached CryptoKey objects keyed by raw token bytes. importKey() is async\n// and non-trivial; pages calling sign() in a hot loop (e.g. mouse-move\n// telemetry) would otherwise pay the cost on every event.\nconst KEY_CACHE = new Map<string, Promise<CryptoKey>>();\n\nexport async function sign(input: SignInput): Promise<SignedRequest> {\n const verb = input.method.toUpperCase();\n const path = effectivePath(input.path);\n const timestamp = input.now ?? Math.floor(Date.now() / 1000);\n\n const bodyHash =\n verb === 'GET' || verb === 'DELETE' || verb === 'HEAD' || verb === 'OPTIONS'\n ? ''\n : await sha256Hex(canonicalJson(input.body ?? null));\n\n // Preserve empty-string values (e.g. filter= / itemsPerSegment= clear\n // sentinels for request-time overrides). Only drop undefined/null omit.\n // Must stay lock-step with engine HmacGuard + node/python signers.\n const others = new Map<string, string>();\n for (const [k, v] of Object.entries(input.query ?? {})) {\n if (v === undefined || v === null) continue;\n others.set(k, String(v));\n }\n others.delete('hmac_timestamp');\n others.delete('frontend_timestamp');\n others.delete('body_hash');\n others.delete('hmac_sign');\n others.delete('frontend_sign');\n const sortedOthers = Array.from(others.entries())\n .sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0))\n .map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(v)}`);\n\n const tsParam = input.mode === 'public' ? 'frontend_timestamp' : 'hmac_timestamp';\n const sigParam = input.mode === 'public' ? 'frontend_sign' : 'hmac_sign';\n\n const trailing = `${tsParam}=${timestamp}&body_hash=${bodyHash}`;\n const queryForSigning = sortedOthers.length\n ? `${sortedOthers.join('&')}&${trailing}`\n : trailing;\n\n const signingString = `${verb}\\n${path}?${queryForSigning}\\nX-Tenant-Id:${input.tenantId}`;\n const signature = await hmacSha256Hex(input.token, signingString);\n const signedPath = `${path}?${queryForSigning}&${sigParam}=${signature}`;\n\n return {\n signedPath,\n timestamp,\n bodyHash,\n signature,\n idempotencyKey: makeIdempotencyKey(),\n };\n}\n\nasync function sha256Hex(input: string): Promise<string> {\n const bytes = await crypto.subtle.digest(\n 'SHA-256',\n new TextEncoder().encode(input),\n );\n return toHex(new Uint8Array(bytes));\n}\n\nasync function hmacSha256Hex(token: string, payload: string): Promise<string> {\n const key = await getKey(token);\n const sig = await crypto.subtle.sign(\n 'HMAC',\n key,\n new TextEncoder().encode(payload),\n );\n return toHex(new Uint8Array(sig));\n}\n\nasync function getKey(token: string): Promise<CryptoKey> {\n let cached = KEY_CACHE.get(token);\n if (!cached) {\n cached = crypto.subtle.importKey(\n 'raw',\n new TextEncoder().encode(token),\n { name: 'HMAC', hash: 'SHA-256' },\n false,\n ['sign'],\n );\n KEY_CACHE.set(token, cached);\n }\n return cached;\n}\n\n// Preload the CryptoKey ahead of any sign() calls. Useful at SDK init time\n// so the first user interaction doesn't pay the importKey cost on the hot\n// path. Returns immediately on a second call for the same token.\nexport function preloadSignerKey(token: string): Promise<CryptoKey> {\n return getKey(token);\n}\n\nfunction toHex(bytes: Uint8Array): string {\n let out = '';\n for (let i = 0; i < bytes.length; i++) {\n out += bytes[i].toString(16).padStart(2, '0');\n }\n return out;\n}\n\nfunction effectivePath(path: string): string {\n const stripped = path.replace(/\\/$/, '');\n return stripped.length > 0 ? stripped : '/';\n}\n\nfunction makeIdempotencyKey(): string {\n const c = (globalThis as unknown as { crypto?: Crypto & { randomUUID?: () => string } }).crypto;\n if (c?.randomUUID) return c.randomUUID();\n // Fallback for older WebView shells without randomUUID.\n const rand = new Uint8Array(16);\n c?.getRandomValues(rand);\n rand[6] = (rand[6] & 0x0f) | 0x40;\n rand[8] = (rand[8] & 0x3f) | 0x80;\n const hex = toHex(rand);\n return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;\n}\n", "// Durable retry queue for browser fire-and-forget endpoints (impressions,\n// interactions). Per the auth-rollout plan:\n// - Synchronous retries (3 attempts, ~5s) for request-response calls\n// happen inline in the SDK; THIS file is exclusively for the\n// fire-and-forget endpoints (/ingestion/impressions,\n// /ingestion/interactions).\n// - `/identity/merge` is NEVER auto-retried (non-idempotent) \u2014 callers\n// must NOT enqueue merge requests via this class.\n// - Exponential backoff, capped at 10 minutes / 8 attempts. After the\n// cap, items are dropped and `onRetryDrop` is invoked for observability.\n// - Optional IndexedDB persistence via `persistRetries: true` so queued\n// items survive page reloads / device-sleep / network outages.\n//\n// Re-signing happens on EVERY attempt \u2014 the engine's replay cache rejects\n// the same signature twice, and the 30s signing window means a stale sig\n// is worse than no retry. The `signRequest` callback is invoked with the\n// item payload and must return a freshly-signed URL + idempotency key.\n\nexport interface QueuedItem<TPayload = unknown> {\n // Unique id used for dedupe / persistence. Auto-generated when not set.\n id?: string;\n // Logical path the item targets (used for routing on flush).\n path: string;\n // HTTP method. Currently every fire-and-forget endpoint is POST; the\n // field exists so the queue can also handle PUTs if needed later.\n method: string;\n // Payload sent as the JSON body. Must be canonical-JSON serializable.\n payload: TPayload;\n // Number of attempts so far (0 on first enqueue). The queue increments.\n attempts?: number;\n // Earliest wall-clock ms at which the next attempt should run. Updated\n // each time we back off.\n nextAttemptAt?: number;\n}\n\nexport interface RetryQueueConfig<TPayload = unknown> {\n // Persist queued items across page reloads. Disabled by default \u2014 the\n // demo / read-mostly integrations don't need it. Enabling pulls in an\n // IndexedDB-backed `idb-keyval`-style adapter (see indexed-db.ts).\n persist?: boolean;\n // Storage key for IndexedDB persistence (so multi-tenant pages don't\n // share queue state). Required when `persist: true`.\n persistKey?: string;\n // Maximum attempts before the item is dropped. Default 8.\n maxAttempts?: number;\n // Initial backoff in ms. Default 1000.\n initialBackoffMs?: number;\n // Backoff cap in ms. Default 600_000 (10m).\n maxBackoffMs?: number;\n // Periodic drain interval in ms. Default 2000.\n flushIntervalMs?: number;\n // Called for every item dropped after exhausting attempts. Used for\n // observability \u2014 log to Sentry / surface as a tracker event.\n onRetryDrop?: (item: QueuedItem<TPayload>, error: unknown) => void;\n // Signs each attempt's request fresh. Returns the absolute URL to POST\n // to plus any auth headers (X-Idempotency-Key, etc.). Must NOT cache\n // its return value \u2014 the queue calls this once per attempt.\n signRequest: (item: QueuedItem<TPayload>) => Promise<{\n url: string;\n headers: Record<string, string>;\n }>;\n}\n\nconst DEFAULTS = {\n maxAttempts: 8,\n initialBackoffMs: 1000,\n maxBackoffMs: 600_000,\n flushIntervalMs: 2000,\n};\n\nexport class RetryQueue<TPayload = unknown> {\n private readonly cfg: Required<\n Omit<RetryQueueConfig<TPayload>, 'persist' | 'persistKey' | 'onRetryDrop'>\n > & {\n persist: boolean;\n persistKey?: string;\n onRetryDrop?: RetryQueueConfig<TPayload>['onRetryDrop'];\n };\n private items: QueuedItem<TPayload>[] = [];\n private timer: ReturnType<typeof setInterval> | null = null;\n private draining = false;\n private idb?: Promise<IDBDatabase | null>;\n\n constructor(cfg: RetryQueueConfig<TPayload>) {\n this.cfg = {\n persist: cfg.persist ?? false,\n persistKey: cfg.persistKey,\n maxAttempts: cfg.maxAttempts ?? DEFAULTS.maxAttempts,\n initialBackoffMs: cfg.initialBackoffMs ?? DEFAULTS.initialBackoffMs,\n maxBackoffMs: cfg.maxBackoffMs ?? DEFAULTS.maxBackoffMs,\n flushIntervalMs: cfg.flushIntervalMs ?? DEFAULTS.flushIntervalMs,\n onRetryDrop: cfg.onRetryDrop,\n signRequest: cfg.signRequest,\n };\n\n if (this.cfg.persist) {\n if (!this.cfg.persistKey) {\n throw new Error('RetryQueue: persistKey required when persist=true');\n }\n this.idb = openIndexedDb();\n void this.loadPersisted();\n }\n\n if (typeof window !== 'undefined') {\n this.timer = setInterval(() => void this.drain(), this.cfg.flushIntervalMs);\n window.addEventListener('online', () => void this.drain());\n // Best-effort flush on page hide \u2014 sendBeacon is the documented\n // companion for items we never want to drop.\n window.addEventListener('pagehide', () => void this.drain());\n }\n }\n\n enqueue(item: QueuedItem<TPayload>): void {\n const enriched: QueuedItem<TPayload> = {\n ...item,\n id: item.id ?? makeId(),\n attempts: item.attempts ?? 0,\n nextAttemptAt: item.nextAttemptAt ?? Date.now(),\n };\n this.items.push(enriched);\n void this.persist();\n // Eager drain attempt \u2014 useful when the page is interactive and the\n // queued event should leave the page within a hundred ms or two.\n void this.drain();\n }\n\n // Manually trigger a drain cycle. Used by callers on critical pageviews\n // (e.g. checkout) where dropping the next backoff window matters.\n flush(): Promise<void> {\n return this.drain();\n }\n\n destroy(): void {\n if (this.timer) clearInterval(this.timer);\n this.timer = null;\n }\n\n private async drain(): Promise<void> {\n if (this.draining) return;\n if (typeof navigator !== 'undefined' && navigator.onLine === false) return;\n this.draining = true;\n try {\n const now = Date.now();\n const ready = this.items.filter((i) => (i.nextAttemptAt ?? 0) <= now);\n for (const item of ready) {\n await this.attempt(item);\n }\n await this.persist();\n } finally {\n this.draining = false;\n }\n }\n\n private async attempt(item: QueuedItem<TPayload>): Promise<void> {\n item.attempts = (item.attempts ?? 0) + 1;\n try {\n const { url, headers } = await this.cfg.signRequest(item);\n const res = await fetch(url, {\n method: item.method,\n headers: {\n 'Content-Type': 'application/json',\n ...headers,\n },\n body: JSON.stringify(item.payload),\n keepalive: true,\n });\n if (res.ok) {\n this.removeById(item.id!);\n return;\n }\n // 4xx other than 429 is permanent \u2014 no amount of retry fixes a bad\n // payload. Drop immediately so we don't keep poisoning the queue.\n if (res.status >= 400 && res.status < 500 && res.status !== 429) {\n this.removeById(item.id!);\n this.cfg.onRetryDrop?.(item, new Error(`HTTP ${res.status}`));\n return;\n }\n this.scheduleRetry(item, new Error(`HTTP ${res.status}`));\n } catch (err) {\n this.scheduleRetry(item, err);\n }\n }\n\n private scheduleRetry(item: QueuedItem<TPayload>, err: unknown): void {\n if ((item.attempts ?? 0) >= this.cfg.maxAttempts) {\n this.removeById(item.id!);\n this.cfg.onRetryDrop?.(item, err);\n return;\n }\n const exp = Math.min(\n this.cfg.maxBackoffMs,\n this.cfg.initialBackoffMs * Math.pow(2, (item.attempts ?? 1) - 1),\n );\n // Decorrelated jitter \u2014 full backoff with a uniformly-distributed\n // multiplier in [0.5, 1.0) so multiple replicas don't sync into a\n // thundering herd when the backend recovers.\n const jitter = 0.5 + Math.random() * 0.5;\n item.nextAttemptAt = Date.now() + Math.floor(exp * jitter);\n }\n\n private removeById(id: string): void {\n this.items = this.items.filter((i) => i.id !== id);\n }\n\n // \u2500\u2500 IndexedDB persistence \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n private async persist(): Promise<void> {\n if (!this.cfg.persist || !this.idb || !this.cfg.persistKey) return;\n try {\n const db = await this.idb;\n if (!db) return;\n const tx = db.transaction('queue', 'readwrite');\n const store = tx.objectStore('queue');\n store.put(JSON.parse(JSON.stringify(this.items)), this.cfg.persistKey);\n await txDone(tx);\n } catch {\n // Persistence is best-effort \u2014 never throw out of an enqueue path.\n }\n }\n\n private async loadPersisted(): Promise<void> {\n if (!this.cfg.persist || !this.idb || !this.cfg.persistKey) return;\n try {\n const db = await this.idb;\n if (!db) return;\n const tx = db.transaction('queue', 'readonly');\n const store = tx.objectStore('queue');\n const req = store.get(this.cfg.persistKey);\n const data: unknown = await new Promise((resolve, reject) => {\n req.onsuccess = () => resolve(req.result);\n req.onerror = () => reject(req.error);\n });\n if (Array.isArray(data)) {\n this.items = data as QueuedItem<TPayload>[];\n void this.drain();\n }\n } catch {\n // Lost-persistence is silent by design \u2014 don't break enqueue on\n // private-browsing / disabled-storage.\n }\n }\n}\n\nfunction makeId(): string {\n const c = (globalThis as unknown as { crypto?: Crypto & { randomUUID?: () => string } }).crypto;\n if (c?.randomUUID) return c.randomUUID();\n return `rnx-${Date.now()}-${Math.random().toString(36).slice(2, 11)}`;\n}\n\nfunction openIndexedDb(): Promise<IDBDatabase | null> {\n if (typeof indexedDB === 'undefined') return Promise.resolve(null);\n return new Promise((resolve) => {\n const req = indexedDB.open('recomnext-retry-queue', 1);\n req.onupgradeneeded = () => {\n const db = req.result;\n if (!db.objectStoreNames.contains('queue')) db.createObjectStore('queue');\n };\n req.onsuccess = () => resolve(req.result);\n req.onerror = () => resolve(null);\n req.onblocked = () => resolve(null);\n });\n}\n\nfunction txDone(tx: IDBTransaction): Promise<void> {\n return new Promise((resolve, reject) => {\n tx.oncomplete = () => resolve();\n tx.onerror = () => reject(tx.error);\n tx.onabort = () => reject(tx.error);\n });\n}\n", "import type { RetryQueue } from '@recomnext/shared-browser-auth';\nimport type { FireAndForgetPayload } from './client';\nimport type { ImpressionTrackerOptions } from './types';\n\nconst FLUSH_INTERVAL = 2000;\n\nexport class ImpressionTracker {\n private queue: { itemId: string; scenario: string }[] = [];\n private flushing = false;\n private observer: IntersectionObserver | null = null;\n private seen = new Set<string>();\n private timer: ReturnType<typeof setInterval> | null = null;\n private readonly getUserId: () => string;\n private readonly getSessionId: () => string;\n // The RetryQueue handles all signed POSTs to /ingestion/impressions\n // (single source of truth for fire-and-forget on this page). We still\n // batch impressions every 2s before enqueuing so we don't fan out N\n // signed requests for one viewport scan.\n private readonly retryQueue: RetryQueue<FireAndForgetPayload>;\n\n constructor(\n _baseUrl: string,\n _tenantId: string,\n getUserId: () => string,\n getSessionId: () => string,\n retryQueue: RetryQueue<FireAndForgetPayload>,\n ) {\n // baseUrl + tenantId are captured by the client during signRequest()\n // and no longer needed at the tracker level. Kept in the signature\n // for backwards compatibility with anyone constructing the tracker\n // directly (rare \u2014 almost everyone uses RecomnextBrowserClient).\n this.getUserId = getUserId;\n this.getSessionId = getSessionId;\n this.retryQueue = retryQueue;\n if (typeof window !== 'undefined') {\n this.timer = setInterval(() => this.flush(), FLUSH_INTERVAL);\n // sendBeacon is best-effort and CANNOT carry the X-Idempotency-Key\n // header (it's a Blob POST). On pagehide we therefore force the\n // queue's normal signed-fetch path to drain via `keepalive: true`,\n // which is now widely supported in all evergreen browsers.\n window.addEventListener('pagehide', () => this.flush(true));\n }\n }\n\n observe(container: Element, opts: ImpressionTrackerOptions = {}): () => void {\n const selector = opts.itemSelector ?? '[data-item-id]';\n const threshold = opts.threshold ?? 0.5;\n const once = opts.once !== false;\n const scenario = opts.scenario ?? '';\n\n this.observer = new IntersectionObserver(\n (entries) => {\n for (const entry of entries) {\n if (!entry.isIntersecting) continue;\n const el = entry.target as HTMLElement;\n const itemId = el.dataset.itemId;\n if (!itemId) continue;\n const key = `${scenario}:${itemId}`;\n if (once && this.seen.has(key)) continue;\n this.seen.add(key);\n this.queue.push({ itemId, scenario });\n if (once) this.observer?.unobserve(el);\n }\n },\n { root: null, threshold },\n );\n\n const elements = container.querySelectorAll(selector);\n elements.forEach((el) => this.observer!.observe(el));\n\n return () => {\n this.observer?.disconnect();\n this.observer = null;\n };\n }\n\n // `urgent` is set during pagehide. When true we kick the RetryQueue\n // immediately so it has a chance to land items inside the unload window\n // (keepalive: true on the inner fetch keeps the connection alive past\n // the navigation).\n private flush(urgent = false): void {\n if (this.flushing || this.queue.length === 0) return;\n this.flushing = true;\n const batch = this.queue.splice(0, this.queue.length);\n\n const grouped = new Map<string, string[]>();\n for (const { itemId, scenario } of batch) {\n if (!grouped.has(scenario)) grouped.set(scenario, []);\n grouped.get(scenario)!.push(itemId);\n }\n\n const impressions = Array.from(grouped.entries()).map(([scenarioSlug, itemIds]) => ({\n userId: this.getUserId() || undefined,\n sessionId: this.getSessionId() || undefined,\n itemIds,\n scenarioSlug: scenarioSlug || undefined,\n timestamp: new Date().toISOString(),\n }));\n\n this.retryQueue.enqueue({\n path: '/ingestion/impressions',\n method: 'POST',\n payload: { impressions },\n });\n\n if (urgent) void this.retryQueue.flush();\n this.flushing = false;\n }\n\n destroy(): void {\n this.flush();\n this.observer?.disconnect();\n if (this.timer) clearInterval(this.timer);\n }\n}\n", "import {\n preloadSignerKey,\n RetryQueue,\n sign as signRequest,\n type QueuedItem,\n} from '@recomnext/shared-browser-auth';\n\nimport type {\n SearchOptions,\n SearchResponse,\n SearchV2Options,\n SearchV2Response,\n SearchV2AutocompleteResponse,\n SearchV2SettingsResponse,\n CatalogItem,\n CatalogListResponse,\n ListItemsOptions,\n} from '@recomnext/shared-search-types';\n\nimport { ImpressionTracker } from './tracker';\nimport type {\n BrowserClientConfig,\n RecommendationResult,\n RecommendationOptions,\n RecommendOptions,\n ImpressionTrackerOptions,\n} from './types';\n\nconst SESSION_KEY = 'recomnext_session_id';\n\n// Synchronous retry budget for request-response calls (recommendations,\n// catalog reads, identity/merge). Mirrors the Node + Python SDK behavior:\n// 3 attempts total, ~5s aggregate, re-signs on every attempt.\nconst SYNC_RETRIES = 3;\nconst SYNC_INITIAL_BACKOFF_MS = 200;\nconst SYNC_MAX_BACKOFF_MS = 2_000;\n\n// 5xx + 429 are auto-retried. We special-case 503 +\n// errorCode=auth_replay_cache_unavailable so the SDK retries even if an\n// operator tunes 503 out of the retry set.\nconst RETRYABLE_STATUS = new Set([429, 500, 502, 503, 504]);\nconst AUTH_REPLAY_CACHE_UNAVAILABLE = 'auth_replay_cache_unavailable';\n\n// `/identity/merge` mutates the identity graph on every call. The plan\n// locks this to single-shot until the engine grows server-side\n// idempotency \u2014 never auto-retry, even on a transient 503.\nconst NEVER_RETRY_PATHS = new Set(['/identity/merge']);\n\nexport class RecomnextBrowserClient {\n private readonly baseUrl: string;\n private readonly tenantId: string;\n private readonly publicToken: string | undefined;\n private userId: string;\n private sessionId: string;\n private readonly tracker: ImpressionTracker;\n // Lazy \u2014 only constructed once for fire-and-forget queues that the\n // tracker also reuses.\n readonly fireAndForgetQueue: RetryQueue<FireAndForgetPayload>;\n\n constructor(config: BrowserClientConfig) {\n this.baseUrl = config.baseUrl.replace(/\\/+$/, '');\n this.tenantId = config.tenantId;\n this.publicToken = config.publicToken;\n this.userId = config.userId ?? '';\n this.sessionId = config.sessionId ?? this.resolveSessionId();\n\n // Eagerly import the CryptoKey so the first signed event after page\n // load doesn't pay the importKey latency on the hot path.\n if (this.publicToken) void preloadSignerKey(this.publicToken);\n\n this.fireAndForgetQueue = new RetryQueue<FireAndForgetPayload>({\n persist: config.persistRetries ?? false,\n persistKey: `rnx:${this.tenantId}:fnf`,\n onRetryDrop: config.onRetryDrop,\n signRequest: (item) => this.signFireAndForget(item),\n });\n\n this.tracker = new ImpressionTracker(\n this.baseUrl,\n this.tenantId,\n () => this.userId,\n () => this.sessionId,\n this.fireAndForgetQueue,\n );\n }\n\n private resolveSessionId(): string {\n if (typeof sessionStorage === 'undefined') return '';\n let sid = sessionStorage.getItem(SESSION_KEY);\n if (!sid) {\n sid = `sess_${Date.now()}_${Math.random().toString(36).slice(2, 9)}`;\n sessionStorage.setItem(SESSION_KEY, sid);\n }\n return sid;\n }\n\n private effectiveUserId(): string {\n return this.userId || this.sessionId;\n }\n\n private async request<T>(\n method: string,\n path: string,\n body?: unknown,\n query?: Record<string, string | number | boolean | undefined>,\n ): Promise<T> {\n const maxRetries = NEVER_RETRY_PATHS.has(path) ? 0 : SYNC_RETRIES;\n let lastErr: unknown;\n\n for (let attempt = 0; attempt <= maxRetries; attempt++) {\n if (attempt > 0) {\n const exp = Math.min(\n SYNC_MAX_BACKOFF_MS,\n SYNC_INITIAL_BACKOFF_MS * Math.pow(2, attempt - 1),\n );\n const jitter = 0.5 + Math.random() * 0.5;\n await sleep(Math.floor(exp * jitter));\n }\n\n // Always re-sign \u2014 the engine's replay cache rejects the same\n // signature twice, and the 30s signing window means a stale sig\n // would also fail.\n const { url, headers } = await this.buildSignedRequest(\n method,\n path,\n query,\n body,\n );\n\n let res: Response;\n try {\n res = await fetch(url, {\n method,\n headers: {\n 'Content-Type': 'application/json',\n ...headers,\n },\n body: body !== undefined ? JSON.stringify(body) : undefined,\n });\n } catch (err) {\n lastErr = err;\n if (attempt >= maxRetries) throw err;\n continue;\n }\n\n if (res.status === 503) {\n const code = await extractErrorCode(res.clone());\n if (code === AUTH_REPLAY_CACHE_UNAVAILABLE && attempt < maxRetries) {\n lastErr = new Error(`recomnext: ${method} ${path} ${res.status} ${code}`);\n continue;\n }\n }\n\n if (RETRYABLE_STATUS.has(res.status) && attempt < maxRetries) {\n lastErr = new Error(`recomnext: ${method} ${path} returned ${res.status}`);\n continue;\n }\n\n if (!res.ok) {\n const text = await res.text().catch(() => '');\n throw new Error(`recomnext: ${method} ${path} returned ${res.status}: ${text}`);\n }\n\n if (res.status === 204) return undefined as unknown as T;\n return res.json() as Promise<T>;\n }\n\n throw lastErr ?? new Error(`recomnext: ${method} ${path} retries exhausted`);\n }\n\n // Same shape as the RetryQueue's signRequest contract \u2014 used by the\n // queue *and* the impression tracker so both share a single signing\n // path.\n async signFireAndForget(item: QueuedItem<FireAndForgetPayload>) {\n const { url, headers } = await this.buildSignedRequest(\n item.method,\n item.path,\n undefined,\n item.payload,\n );\n return { url, headers };\n }\n\n private async buildSignedRequest(\n method: string,\n path: string,\n query: Record<string, string | number | boolean | undefined> | undefined,\n body: unknown,\n ): Promise<{ url: string; headers: Record<string, string> }> {\n const baseHeaders: Record<string, string> = {\n 'X-Tenant-Id': this.tenantId,\n };\n\n if (!this.publicToken) {\n // Legacy / no-auth mode. Engine accepts because\n // LEGACY_TENANT_AUTH=true. After Phase 7 cutover this path will be\n // rejected with 401.\n const qs = renderQs(query);\n return { url: `${this.baseUrl}${path}${qs}`, headers: baseHeaders };\n }\n\n const signed = await signRequest({\n method,\n path,\n query,\n body: body ?? null,\n tenantId: this.tenantId,\n token: this.publicToken,\n mode: 'public',\n });\n return {\n url: `${this.baseUrl}${signed.signedPath}`,\n headers: {\n ...baseHeaders,\n 'X-Idempotency-Key': signed.idempotencyKey,\n },\n };\n }\n\n private qs(\n params: Record<string, string | number | boolean | undefined>,\n ): Record<string, string | number | boolean | undefined> {\n return params;\n }\n\n private includeQuery(opts?: RecommendationOptions): {\n returnProperties?: boolean;\n includedProperties?: string;\n } {\n return {\n returnProperties: opts?.returnProperties,\n includedProperties:\n opts?.includedProperties && opts.includedProperties.length > 0\n ? opts.includedProperties.join(',')\n : undefined,\n };\n }\n\n /** Serialize override fields for GET query (comma-separated lists; '' clears). */\n private overrideQuery(opts?: RecommendationOptions): {\n filter?: string;\n boosters?: string;\n rulesSlugs?: string;\n segmentationId?: string;\n itemsPerSegment?: number | string;\n } {\n if (!opts) return {};\n const join = (v: string[] | null | undefined): string | undefined => {\n if (v === undefined) return undefined;\n if (v === null || v.length === 0) return '';\n return v.join(',');\n };\n return {\n filter:\n opts.filter === undefined\n ? undefined\n : opts.filter === null\n ? ''\n : opts.filter,\n boosters: join(opts.boosters),\n rulesSlugs: join(opts.rulesSlugs),\n segmentationId:\n opts.segmentationId === undefined\n ? undefined\n : opts.segmentationId === null\n ? ''\n : opts.segmentationId,\n // Present null \u2192 '' so the query param is sent; engine transform maps\n // '' \u2192 null \u2192 resolve to itemsPerSegment=1 (omit would keep scenario).\n itemsPerSegment:\n opts.itemsPerSegment === undefined\n ? undefined\n : opts.itemsPerSegment === null\n ? ''\n : opts.itemsPerSegment,\n };\n }\n\n private overrideBody(opts?: RecommendationOptions): {\n filter?: string | null;\n boosters?: string[] | null;\n rulesSlugs?: string[] | null;\n segmentationId?: string | null;\n itemsPerSegment?: number | null;\n } {\n if (!opts) return {};\n const body: {\n filter?: string | null;\n boosters?: string[] | null;\n rulesSlugs?: string[] | null;\n segmentationId?: string | null;\n itemsPerSegment?: number | null;\n } = {};\n if (opts.filter !== undefined) body.filter = opts.filter;\n if (opts.boosters !== undefined) body.boosters = opts.boosters;\n if (opts.rulesSlugs !== undefined) body.rulesSlugs = opts.rulesSlugs;\n if (opts.segmentationId !== undefined) {\n body.segmentationId = opts.segmentationId;\n }\n if (opts.itemsPerSegment !== undefined) {\n body.itemsPerSegment = opts.itemsPerSegment;\n }\n return body;\n }\n\n /* \u2500\u2500 Recommendations \u2500\u2500 */\n\n getRecommendations(opts?: RecommendationOptions): Promise<RecommendationResult> {\n return this.request(\n 'GET',\n `/recommendations/user-to-item/${this.effectiveUserId()}`,\n undefined,\n this.qs({\n count: opts?.count,\n scenario: opts?.scenario,\n ...this.includeQuery(opts),\n ...this.overrideQuery(opts),\n }),\n );\n }\n\n getRelatedItems(itemId: string, opts?: RecommendationOptions): Promise<RecommendationResult> {\n return this.request(\n 'GET',\n `/recommendations/item-to-item/${itemId}`,\n undefined,\n this.qs({\n count: opts?.count,\n scenario: opts?.scenario,\n ...this.includeQuery(opts),\n ...this.overrideQuery(opts),\n }),\n );\n }\n\n getSimilarProducts(itemId: string, opts?: RecommendationOptions): Promise<RecommendationResult> {\n return this.request(\n 'GET',\n `/recommendations/similar-products/${itemId}`,\n undefined,\n this.qs({\n count: opts?.count,\n scenario: opts?.scenario,\n ...this.includeQuery(opts),\n ...this.overrideQuery(opts),\n }),\n );\n }\n\n getRecentlyViewed(opts?: RecommendationOptions): Promise<RecommendationResult> {\n return this.request(\n 'GET',\n `/recommendations/recently-viewed/${this.effectiveUserId()}`,\n undefined,\n this.qs({\n count: opts?.count,\n scenario: opts?.scenario,\n ...this.includeQuery(opts),\n ...this.overrideQuery(opts),\n }),\n );\n }\n\n getCartRecommendations(itemIds: string[], opts?: RecommendationOptions): Promise<RecommendationResult> {\n return this.request(\n 'POST',\n '/recommendations/items-to-items',\n {\n itemIds,\n count: opts?.count ?? 10,\n returnProperties: opts?.returnProperties,\n includedProperties: opts?.includedProperties,\n ...this.overrideBody(opts),\n },\n this.qs({ scenario: opts?.scenario }),\n );\n }\n\n recommend(opts: RecommendOptions): Promise<RecommendationResult> {\n return this.request('POST', '/recommendations', {\n ...opts,\n userId: this.effectiveUserId(),\n });\n }\n\n /* \u2500\u2500 Search \u2500\u2500 */\n\n search(query: string, opts?: SearchOptions): Promise<SearchResponse> {\n return this.request(\n 'GET',\n '/recommendations/search',\n undefined,\n this.qs({\n query,\n count: opts?.count,\n category: opts?.category,\n priceRange: opts?.priceRange,\n userId: opts?.userId ?? (this.userId || undefined),\n }),\n );\n }\n\n searchV2(query: string, opts?: SearchV2Options): Promise<SearchV2Response> {\n return this.request(\n 'GET',\n '/recommendations/search/v2',\n undefined,\n this.qs({\n query,\n count: opts?.count,\n category: opts?.category,\n priceRange: opts?.priceRange,\n userId: opts?.userId ?? (this.userId || undefined),\n locale: opts?.locale,\n debugScores: opts?.debugScores === true ? true : undefined,\n algorithm: opts?.algorithm,\n }),\n );\n }\n\n autocomplete(\n query: string,\n opts?: { limit?: number },\n ): Promise<SearchV2AutocompleteResponse> {\n return this.request(\n 'GET',\n '/recommendations/search/v2/autocomplete',\n undefined,\n this.qs({\n query,\n limit: opts?.limit,\n }),\n );\n }\n\n getSearchV2Settings(): Promise<SearchV2SettingsResponse> {\n return this.request('GET', '/recommendations/search/v2/settings');\n }\n\n /* \u2500\u2500 Catalog browse (public HMAC eligible) \u2500\u2500 */\n\n listItems(opts?: ListItemsOptions): Promise<CatalogListResponse> {\n return this.request(\n 'GET',\n '/catalog/items',\n undefined,\n this.qs({\n page: opts?.page,\n limit: opts?.limit,\n category: opts?.category,\n search: opts?.search,\n availability: opts?.availability,\n }),\n );\n }\n\n getCategories(): Promise<string[]> {\n return this.request('GET', '/catalog/items/categories');\n }\n\n getItem(externalId: string): Promise<CatalogItem> {\n return this.request('GET', `/catalog/items/${encodeURIComponent(externalId)}`);\n }\n\n /* \u2500\u2500 Event tracking \u2500\u2500 */\n\n trackView(itemId: string, scenarioSlug?: string): void {\n this.fireAndForgetQueue.enqueue({\n path: '/ingestion/interactions',\n method: 'POST',\n payload: {\n interactions: [{ userId: this.effectiveUserId(), itemId, type: 'view', scenarioSlug: scenarioSlug || undefined, timestamp: new Date().toISOString() }],\n },\n });\n }\n\n trackCart(itemId: string, scenarioSlug?: string): void {\n this.fireAndForgetQueue.enqueue({\n path: '/ingestion/interactions',\n method: 'POST',\n payload: {\n interactions: [{ userId: this.effectiveUserId(), itemId, type: 'cart', scenarioSlug: scenarioSlug || undefined, timestamp: new Date().toISOString() }],\n },\n });\n }\n\n trackPurchase(itemId: string, scenarioSlug?: string): void {\n this.fireAndForgetQueue.enqueue({\n path: '/ingestion/interactions',\n method: 'POST',\n payload: {\n interactions: [{ userId: this.effectiveUserId(), itemId, type: 'purchase', scenarioSlug: scenarioSlug || undefined, timestamp: new Date().toISOString() }],\n },\n });\n }\n\n /* \u2500\u2500 Impression tracking \u2500\u2500 */\n\n trackImpressions(container: Element, opts?: ImpressionTrackerOptions): () => void {\n return this.tracker.observe(container, opts);\n }\n\n /* \u2500\u2500 Identity \u2500\u2500 */\n\n setUserId(userId: string): void {\n this.userId = userId;\n }\n\n mergeIdentity(authenticatedUserId: string): Promise<unknown> {\n const anonId = this.sessionId;\n this.userId = authenticatedUserId;\n // Note: never auto-retried (see NEVER_RETRY_PATHS) \u2014 callers should\n // handle 5xx/network failures themselves to avoid duplicate merges.\n return this.request('POST', '/identity/merge', {\n userId: authenticatedUserId,\n sessionIds: [anonId],\n });\n }\n\n /* \u2500\u2500 Lifecycle \u2500\u2500 */\n\n destroy(): void {\n this.tracker.destroy();\n this.fireAndForgetQueue.destroy();\n }\n}\n\n// Shape used by both the SDK's trackView/trackCart/trackPurchase calls\n// and the ImpressionTracker \u2014 they all funnel through the same queue.\nexport type FireAndForgetPayload = {\n interactions?: unknown[];\n impressions?: unknown[];\n};\n\nfunction renderQs(params?: Record<string, string | number | boolean | undefined>): string {\n if (!params) return '';\n const parts: string[] = [];\n for (const [k, v] of Object.entries(params)) {\n // Preserve empty string (GET clear sentinels). Drop only omit/null.\n if (v === undefined || v === null) continue;\n parts.push(`${encodeURIComponent(k)}=${encodeURIComponent(String(v))}`);\n }\n return parts.length ? `?${parts.join('&')}` : '';\n}\n\nasync function extractErrorCode(res: Response): Promise<string | undefined> {\n try {\n const body = (await res.json()) as { errorCode?: unknown };\n return typeof body.errorCode === 'string' ? body.errorCode : undefined;\n } catch {\n return undefined;\n }\n}\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((r) => setTimeout(r, ms));\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["// Canonical JSON serializer \u2014 byte-identical with the engine, the Node\n// server SDK, and the Python SDK. See repo-root\n// `tests/auth/canonical-vectors.json` for the conformance gate.\n\nexport function canonicalJson(value: unknown): string {\n return stringify(value);\n}\n\nfunction stringify(value: unknown): string {\n if (value === null) return 'null';\n switch (typeof value) {\n case 'boolean':\n return value ? 'true' : 'false';\n case 'number':\n if (!Number.isFinite(value)) {\n throw new TypeError(\n `Cannot canonicalize non-finite number: ${String(value)}`,\n );\n }\n return JSON.stringify(value);\n case 'string':\n return JSON.stringify(value);\n case 'object': {\n if (Array.isArray(value)) {\n return '[' + value.map(stringify).join(',') + ']';\n }\n const keys = Object.keys(value as Record<string, unknown>).sort();\n const parts: string[] = [];\n for (const k of keys) {\n const v = (value as Record<string, unknown>)[k];\n if (typeof v === 'undefined') continue;\n parts.push(JSON.stringify(k) + ':' + stringify(v));\n }\n return '{' + parts.join(',') + '}';\n }\n case 'undefined':\n throw new TypeError('Cannot canonicalize undefined at the root');\n default:\n throw new TypeError(\n `Cannot canonicalize value of type ${typeof value}`,\n );\n }\n}\n", "// WebCrypto-based HMAC-SHA256 signing for browser-side recomnext callers.\n// Same wire format as the server SDK (sdks/node/src/signing.ts) and the\n// engine guard (engine/src/auth/guards/hmac.guard.ts).\n//\n// Public vs private token contract:\n// - PUBLIC token: shipped in JS bundles. Wire uses `frontend_sign` +\n// `frontend_timestamp` query params. Engine rejects unless the request\n// origin is on the tenant's allow-list AND the route is on the\n// public-write whitelist (or is a GET).\n// - PRIVATE token: NEVER ship to a browser. WebCrypto signing of private\n// tokens is provided here for completeness (e.g. for Node-via-bundler\n// consumers), but the recomnext browser SDK refuses to accept a\n// privateToken in its config.\n\nimport { canonicalJson } from './canonical-json.js';\n\nexport type SignerMode = 'public' | 'private';\n\nexport interface SignInput {\n method: string;\n // Path WITHOUT querystring or trailing slash.\n path: string;\n // Caller-supplied query params; values coerced to strings.\n query?: Record<string, string | number | boolean | undefined>;\n // Parsed body \u2014 undefined for GET/DELETE/HEAD/OPTIONS.\n body?: unknown;\n tenantId: string;\n token: string;\n mode: SignerMode;\n // Test-only clock override.\n now?: number;\n}\n\nexport interface SignedRequest {\n signedPath: string;\n timestamp: number;\n bodyHash: string;\n signature: string;\n idempotencyKey: string;\n}\n\n// Cached CryptoKey objects keyed by raw token bytes. importKey() is async\n// and non-trivial; pages calling sign() in a hot loop (e.g. mouse-move\n// telemetry) would otherwise pay the cost on every event.\nconst KEY_CACHE = new Map<string, Promise<CryptoKey>>();\n\nexport async function sign(input: SignInput): Promise<SignedRequest> {\n const verb = input.method.toUpperCase();\n const path = effectivePath(input.path);\n const timestamp = input.now ?? Math.floor(Date.now() / 1000);\n\n const bodyHash =\n verb === 'GET' || verb === 'DELETE' || verb === 'HEAD' || verb === 'OPTIONS'\n ? ''\n : await sha256Hex(canonicalJson(input.body ?? null));\n\n // Preserve empty-string values (e.g. filter= / itemsPerSegment= clear\n // sentinels for request-time overrides). Only drop undefined/null omit.\n // Must stay lock-step with engine HmacGuard + node/python signers.\n const others = new Map<string, string>();\n for (const [k, v] of Object.entries(input.query ?? {})) {\n if (v === undefined || v === null) continue;\n others.set(k, String(v));\n }\n others.delete('hmac_timestamp');\n others.delete('frontend_timestamp');\n others.delete('body_hash');\n others.delete('hmac_sign');\n others.delete('frontend_sign');\n const sortedOthers = Array.from(others.entries())\n .sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0))\n .map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(v)}`);\n\n const tsParam = input.mode === 'public' ? 'frontend_timestamp' : 'hmac_timestamp';\n const sigParam = input.mode === 'public' ? 'frontend_sign' : 'hmac_sign';\n\n const trailing = `${tsParam}=${timestamp}&body_hash=${bodyHash}`;\n const queryForSigning = sortedOthers.length\n ? `${sortedOthers.join('&')}&${trailing}`\n : trailing;\n\n const signingString = `${verb}\\n${path}?${queryForSigning}\\nX-Tenant-Id:${input.tenantId}`;\n const signature = await hmacSha256Hex(input.token, signingString);\n const signedPath = `${path}?${queryForSigning}&${sigParam}=${signature}`;\n\n return {\n signedPath,\n timestamp,\n bodyHash,\n signature,\n idempotencyKey: makeIdempotencyKey(),\n };\n}\n\nasync function sha256Hex(input: string): Promise<string> {\n const bytes = await crypto.subtle.digest(\n 'SHA-256',\n new TextEncoder().encode(input),\n );\n return toHex(new Uint8Array(bytes));\n}\n\nasync function hmacSha256Hex(token: string, payload: string): Promise<string> {\n const key = await getKey(token);\n const sig = await crypto.subtle.sign(\n 'HMAC',\n key,\n new TextEncoder().encode(payload),\n );\n return toHex(new Uint8Array(sig));\n}\n\nasync function getKey(token: string): Promise<CryptoKey> {\n let cached = KEY_CACHE.get(token);\n if (!cached) {\n cached = crypto.subtle.importKey(\n 'raw',\n new TextEncoder().encode(token),\n { name: 'HMAC', hash: 'SHA-256' },\n false,\n ['sign'],\n );\n KEY_CACHE.set(token, cached);\n }\n return cached;\n}\n\n// Preload the CryptoKey ahead of any sign() calls. Useful at SDK init time\n// so the first user interaction doesn't pay the importKey cost on the hot\n// path. Returns immediately on a second call for the same token.\nexport function preloadSignerKey(token: string): Promise<CryptoKey> {\n return getKey(token);\n}\n\nfunction toHex(bytes: Uint8Array): string {\n let out = '';\n for (let i = 0; i < bytes.length; i++) {\n out += bytes[i].toString(16).padStart(2, '0');\n }\n return out;\n}\n\nfunction effectivePath(path: string): string {\n const stripped = path.replace(/\\/$/, '');\n return stripped.length > 0 ? stripped : '/';\n}\n\nfunction makeIdempotencyKey(): string {\n const c = (globalThis as unknown as { crypto?: Crypto & { randomUUID?: () => string } }).crypto;\n if (c?.randomUUID) return c.randomUUID();\n // Fallback for older WebView shells without randomUUID.\n const rand = new Uint8Array(16);\n c?.getRandomValues(rand);\n rand[6] = (rand[6] & 0x0f) | 0x40;\n rand[8] = (rand[8] & 0x3f) | 0x80;\n const hex = toHex(rand);\n return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;\n}\n", "// Durable retry queue for browser fire-and-forget endpoints (impressions,\n// interactions). Per the auth-rollout plan:\n// - Synchronous retries (3 attempts, ~5s) for request-response calls\n// happen inline in the SDK; THIS file is exclusively for the\n// fire-and-forget endpoints (/ingestion/impressions,\n// /ingestion/interactions).\n// - `/identity/merge` is NEVER auto-retried (non-idempotent) \u2014 callers\n// must NOT enqueue merge requests via this class.\n// - Exponential backoff, capped at 10 minutes / 8 attempts. After the\n// cap, items are dropped and `onRetryDrop` is invoked for observability.\n// - Optional IndexedDB persistence via `persistRetries: true` so queued\n// items survive page reloads / device-sleep / network outages.\n//\n// Re-signing happens on EVERY attempt \u2014 the engine's replay cache rejects\n// the same signature twice, and the 30s signing window means a stale sig\n// is worse than no retry. The `signRequest` callback is invoked with the\n// item payload and must return a freshly-signed URL + idempotency key.\n\nexport interface QueuedItem<TPayload = unknown> {\n // Unique id used for dedupe / persistence. Auto-generated when not set.\n id?: string;\n // Logical path the item targets (used for routing on flush).\n path: string;\n // HTTP method. Currently every fire-and-forget endpoint is POST; the\n // field exists so the queue can also handle PUTs if needed later.\n method: string;\n // Payload sent as the JSON body. Must be canonical-JSON serializable.\n payload: TPayload;\n // Number of attempts so far (0 on first enqueue). The queue increments.\n attempts?: number;\n // Earliest wall-clock ms at which the next attempt should run. Updated\n // each time we back off.\n nextAttemptAt?: number;\n}\n\nexport interface RetryQueueConfig<TPayload = unknown> {\n // Persist queued items across page reloads. Disabled by default \u2014 the\n // demo / read-mostly integrations don't need it. Enabling pulls in an\n // IndexedDB-backed `idb-keyval`-style adapter (see indexed-db.ts).\n persist?: boolean;\n // Storage key for IndexedDB persistence (so multi-tenant pages don't\n // share queue state). Required when `persist: true`.\n persistKey?: string;\n // Maximum attempts before the item is dropped. Default 8.\n maxAttempts?: number;\n // Initial backoff in ms. Default 1000.\n initialBackoffMs?: number;\n // Backoff cap in ms. Default 600_000 (10m).\n maxBackoffMs?: number;\n // Periodic drain interval in ms. Default 2000.\n flushIntervalMs?: number;\n // Called for every item dropped after exhausting attempts. Used for\n // observability \u2014 log to Sentry / surface as a tracker event.\n onRetryDrop?: (item: QueuedItem<TPayload>, error: unknown) => void;\n // Signs each attempt's request fresh. Returns the absolute URL to POST\n // to plus any auth headers (X-Idempotency-Key, etc.). Must NOT cache\n // its return value \u2014 the queue calls this once per attempt.\n signRequest: (item: QueuedItem<TPayload>) => Promise<{\n url: string;\n headers: Record<string, string>;\n }>;\n}\n\nconst DEFAULTS = {\n maxAttempts: 8,\n initialBackoffMs: 1000,\n maxBackoffMs: 600_000,\n flushIntervalMs: 2000,\n};\n\nexport class RetryQueue<TPayload = unknown> {\n private readonly cfg: Required<\n Omit<RetryQueueConfig<TPayload>, 'persist' | 'persistKey' | 'onRetryDrop'>\n > & {\n persist: boolean;\n persistKey?: string;\n onRetryDrop?: RetryQueueConfig<TPayload>['onRetryDrop'];\n };\n private items: QueuedItem<TPayload>[] = [];\n private timer: ReturnType<typeof setInterval> | null = null;\n private draining = false;\n private idb?: Promise<IDBDatabase | null>;\n\n constructor(cfg: RetryQueueConfig<TPayload>) {\n this.cfg = {\n persist: cfg.persist ?? false,\n persistKey: cfg.persistKey,\n maxAttempts: cfg.maxAttempts ?? DEFAULTS.maxAttempts,\n initialBackoffMs: cfg.initialBackoffMs ?? DEFAULTS.initialBackoffMs,\n maxBackoffMs: cfg.maxBackoffMs ?? DEFAULTS.maxBackoffMs,\n flushIntervalMs: cfg.flushIntervalMs ?? DEFAULTS.flushIntervalMs,\n onRetryDrop: cfg.onRetryDrop,\n signRequest: cfg.signRequest,\n };\n\n if (this.cfg.persist) {\n if (!this.cfg.persistKey) {\n throw new Error('RetryQueue: persistKey required when persist=true');\n }\n this.idb = openIndexedDb();\n void this.loadPersisted();\n }\n\n if (typeof window !== 'undefined') {\n this.timer = setInterval(() => void this.drain(), this.cfg.flushIntervalMs);\n window.addEventListener('online', () => void this.drain());\n // Best-effort flush on page hide \u2014 sendBeacon is the documented\n // companion for items we never want to drop.\n window.addEventListener('pagehide', () => void this.drain());\n }\n }\n\n enqueue(item: QueuedItem<TPayload>): void {\n const enriched: QueuedItem<TPayload> = {\n ...item,\n id: item.id ?? makeId(),\n attempts: item.attempts ?? 0,\n nextAttemptAt: item.nextAttemptAt ?? Date.now(),\n };\n this.items.push(enriched);\n void this.persist();\n // Eager drain attempt \u2014 useful when the page is interactive and the\n // queued event should leave the page within a hundred ms or two.\n void this.drain();\n }\n\n // Manually trigger a drain cycle. Used by callers on critical pageviews\n // (e.g. checkout) where dropping the next backoff window matters.\n flush(): Promise<void> {\n return this.drain();\n }\n\n destroy(): void {\n if (this.timer) clearInterval(this.timer);\n this.timer = null;\n }\n\n private async drain(): Promise<void> {\n if (this.draining) return;\n if (typeof navigator !== 'undefined' && navigator.onLine === false) return;\n this.draining = true;\n try {\n const now = Date.now();\n const ready = this.items.filter((i) => (i.nextAttemptAt ?? 0) <= now);\n for (const item of ready) {\n await this.attempt(item);\n }\n await this.persist();\n } finally {\n this.draining = false;\n }\n }\n\n private async attempt(item: QueuedItem<TPayload>): Promise<void> {\n item.attempts = (item.attempts ?? 0) + 1;\n try {\n const { url, headers } = await this.cfg.signRequest(item);\n const res = await fetch(url, {\n method: item.method,\n headers: {\n 'Content-Type': 'application/json',\n ...headers,\n },\n body: JSON.stringify(item.payload),\n keepalive: true,\n });\n if (res.ok) {\n this.removeById(item.id!);\n return;\n }\n // 4xx other than 429 is permanent \u2014 no amount of retry fixes a bad\n // payload. Drop immediately so we don't keep poisoning the queue.\n if (res.status >= 400 && res.status < 500 && res.status !== 429) {\n this.removeById(item.id!);\n this.cfg.onRetryDrop?.(item, new Error(`HTTP ${res.status}`));\n return;\n }\n this.scheduleRetry(item, new Error(`HTTP ${res.status}`));\n } catch (err) {\n this.scheduleRetry(item, err);\n }\n }\n\n private scheduleRetry(item: QueuedItem<TPayload>, err: unknown): void {\n if ((item.attempts ?? 0) >= this.cfg.maxAttempts) {\n this.removeById(item.id!);\n this.cfg.onRetryDrop?.(item, err);\n return;\n }\n const exp = Math.min(\n this.cfg.maxBackoffMs,\n this.cfg.initialBackoffMs * Math.pow(2, (item.attempts ?? 1) - 1),\n );\n // Decorrelated jitter \u2014 full backoff with a uniformly-distributed\n // multiplier in [0.5, 1.0) so multiple replicas don't sync into a\n // thundering herd when the backend recovers.\n const jitter = 0.5 + Math.random() * 0.5;\n item.nextAttemptAt = Date.now() + Math.floor(exp * jitter);\n }\n\n private removeById(id: string): void {\n this.items = this.items.filter((i) => i.id !== id);\n }\n\n // \u2500\u2500 IndexedDB persistence \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n private async persist(): Promise<void> {\n if (!this.cfg.persist || !this.idb || !this.cfg.persistKey) return;\n try {\n const db = await this.idb;\n if (!db) return;\n const tx = db.transaction('queue', 'readwrite');\n const store = tx.objectStore('queue');\n store.put(JSON.parse(JSON.stringify(this.items)), this.cfg.persistKey);\n await txDone(tx);\n } catch {\n // Persistence is best-effort \u2014 never throw out of an enqueue path.\n }\n }\n\n private async loadPersisted(): Promise<void> {\n if (!this.cfg.persist || !this.idb || !this.cfg.persistKey) return;\n try {\n const db = await this.idb;\n if (!db) return;\n const tx = db.transaction('queue', 'readonly');\n const store = tx.objectStore('queue');\n const req = store.get(this.cfg.persistKey);\n const data: unknown = await new Promise((resolve, reject) => {\n req.onsuccess = () => resolve(req.result);\n req.onerror = () => reject(req.error);\n });\n if (Array.isArray(data)) {\n this.items = data as QueuedItem<TPayload>[];\n void this.drain();\n }\n } catch {\n // Lost-persistence is silent by design \u2014 don't break enqueue on\n // private-browsing / disabled-storage.\n }\n }\n}\n\nfunction makeId(): string {\n const c = (globalThis as unknown as { crypto?: Crypto & { randomUUID?: () => string } }).crypto;\n if (c?.randomUUID) return c.randomUUID();\n return `rnx-${Date.now()}-${Math.random().toString(36).slice(2, 11)}`;\n}\n\nfunction openIndexedDb(): Promise<IDBDatabase | null> {\n if (typeof indexedDB === 'undefined') return Promise.resolve(null);\n return new Promise((resolve) => {\n const req = indexedDB.open('recomnext-retry-queue', 1);\n req.onupgradeneeded = () => {\n const db = req.result;\n if (!db.objectStoreNames.contains('queue')) db.createObjectStore('queue');\n };\n req.onsuccess = () => resolve(req.result);\n req.onerror = () => resolve(null);\n req.onblocked = () => resolve(null);\n });\n}\n\nfunction txDone(tx: IDBTransaction): Promise<void> {\n return new Promise((resolve, reject) => {\n tx.oncomplete = () => resolve();\n tx.onerror = () => reject(tx.error);\n tx.onabort = () => reject(tx.error);\n });\n}\n", "import type { RetryQueue } from '@recomnext/shared-browser-auth';\nimport type { FireAndForgetPayload } from './client';\nimport type { ImpressionTrackerOptions } from './types';\n\nconst FLUSH_INTERVAL = 2000;\n\nexport class ImpressionTracker {\n private queue: { itemId: string; scenario: string }[] = [];\n private flushing = false;\n private observer: IntersectionObserver | null = null;\n private seen = new Set<string>();\n private timer: ReturnType<typeof setInterval> | null = null;\n private readonly getUserId: () => string;\n private readonly getSessionId: () => string;\n // The RetryQueue handles all signed POSTs to /ingestion/impressions\n // (single source of truth for fire-and-forget on this page). We still\n // batch impressions every 2s before enqueuing so we don't fan out N\n // signed requests for one viewport scan.\n private readonly retryQueue: RetryQueue<FireAndForgetPayload>;\n\n constructor(\n _baseUrl: string,\n _tenantId: string,\n getUserId: () => string,\n getSessionId: () => string,\n retryQueue: RetryQueue<FireAndForgetPayload>,\n ) {\n // baseUrl + tenantId are captured by the client during signRequest()\n // and no longer needed at the tracker level. Kept in the signature\n // for backwards compatibility with anyone constructing the tracker\n // directly (rare \u2014 almost everyone uses RecomnextBrowserClient).\n this.getUserId = getUserId;\n this.getSessionId = getSessionId;\n this.retryQueue = retryQueue;\n if (typeof window !== 'undefined') {\n this.timer = setInterval(() => this.flush(), FLUSH_INTERVAL);\n // sendBeacon is best-effort and CANNOT carry the X-Idempotency-Key\n // header (it's a Blob POST). On pagehide we therefore force the\n // queue's normal signed-fetch path to drain via `keepalive: true`,\n // which is now widely supported in all evergreen browsers.\n window.addEventListener('pagehide', () => this.flush(true));\n }\n }\n\n observe(container: Element, opts: ImpressionTrackerOptions = {}): () => void {\n const selector = opts.itemSelector ?? '[data-item-id]';\n const threshold = opts.threshold ?? 0.5;\n const once = opts.once !== false;\n const scenario = opts.scenario ?? '';\n\n this.observer = new IntersectionObserver(\n (entries) => {\n for (const entry of entries) {\n if (!entry.isIntersecting) continue;\n const el = entry.target as HTMLElement;\n const itemId = el.dataset.itemId;\n if (!itemId) continue;\n const key = `${scenario}:${itemId}`;\n if (once && this.seen.has(key)) continue;\n this.seen.add(key);\n this.queue.push({ itemId, scenario });\n if (once) this.observer?.unobserve(el);\n }\n },\n { root: null, threshold },\n );\n\n const elements = container.querySelectorAll(selector);\n elements.forEach((el) => this.observer!.observe(el));\n\n return () => {\n this.observer?.disconnect();\n this.observer = null;\n };\n }\n\n // `urgent` is set during pagehide. When true we kick the RetryQueue\n // immediately so it has a chance to land items inside the unload window\n // (keepalive: true on the inner fetch keeps the connection alive past\n // the navigation).\n private flush(urgent = false): void {\n if (this.flushing || this.queue.length === 0) return;\n this.flushing = true;\n const batch = this.queue.splice(0, this.queue.length);\n\n const grouped = new Map<string, string[]>();\n for (const { itemId, scenario } of batch) {\n if (!grouped.has(scenario)) grouped.set(scenario, []);\n grouped.get(scenario)!.push(itemId);\n }\n\n const impressions = Array.from(grouped.entries()).map(([scenarioSlug, itemIds]) => ({\n userId: this.getUserId() || undefined,\n sessionId: this.getSessionId() || undefined,\n itemIds,\n scenarioSlug: scenarioSlug || undefined,\n timestamp: new Date().toISOString(),\n }));\n\n this.retryQueue.enqueue({\n path: '/ingestion/impressions',\n method: 'POST',\n payload: { impressions },\n });\n\n if (urgent) void this.retryQueue.flush();\n this.flushing = false;\n }\n\n destroy(): void {\n this.flush();\n this.observer?.disconnect();\n if (this.timer) clearInterval(this.timer);\n }\n}\n", "import {\n preloadSignerKey,\n RetryQueue,\n sign as signRequest,\n type QueuedItem,\n} from '@recomnext/shared-browser-auth';\n\nimport type {\n SearchOptions,\n SearchResponse,\n SearchV2Options,\n SearchV2Response,\n SearchV2AutocompleteResponse,\n SearchV2SettingsResponse,\n CatalogItem,\n CatalogListResponse,\n ListItemsOptions,\n} from '@recomnext/shared-search-types';\n\nimport { ImpressionTracker } from './tracker';\nimport type {\n BrowserClientConfig,\n RecommendationResult,\n RecommendationOptions,\n RecommendOptions,\n ImpressionTrackerOptions,\n} from './types';\n\nconst SESSION_KEY = 'recomnext_session_id';\n\n// Synchronous retry budget for request-response calls (recommendations,\n// catalog reads, identity/merge). Mirrors the Node + Python SDK behavior:\n// 3 attempts total, ~5s aggregate, re-signs on every attempt.\nconst SYNC_RETRIES = 3;\nconst SYNC_INITIAL_BACKOFF_MS = 200;\nconst SYNC_MAX_BACKOFF_MS = 2_000;\n\n// 5xx + 429 are auto-retried. We special-case 503 +\n// errorCode=auth_replay_cache_unavailable so the SDK retries even if an\n// operator tunes 503 out of the retry set.\nconst RETRYABLE_STATUS = new Set([429, 500, 502, 503, 504]);\nconst AUTH_REPLAY_CACHE_UNAVAILABLE = 'auth_replay_cache_unavailable';\n\n// `/identity/merge` mutates the identity graph on every call. The plan\n// locks this to single-shot until the engine grows server-side\n// idempotency \u2014 never auto-retry, even on a transient 503.\nconst NEVER_RETRY_PATHS = new Set(['/identity/merge']);\n\nexport class RecomnextBrowserClient {\n private readonly baseUrl: string;\n private readonly tenantId: string;\n private readonly publicToken: string | undefined;\n private userId: string;\n private sessionId: string;\n private readonly tracker: ImpressionTracker;\n // Lazy \u2014 only constructed once for fire-and-forget queues that the\n // tracker also reuses.\n readonly fireAndForgetQueue: RetryQueue<FireAndForgetPayload>;\n\n constructor(config: BrowserClientConfig) {\n this.baseUrl = config.baseUrl.replace(/\\/+$/, '');\n this.tenantId = config.tenantId;\n this.publicToken = config.publicToken;\n this.userId = config.userId ?? '';\n this.sessionId = config.sessionId ?? this.resolveSessionId();\n\n // Eagerly import the CryptoKey so the first signed event after page\n // load doesn't pay the importKey latency on the hot path.\n if (this.publicToken) void preloadSignerKey(this.publicToken);\n\n this.fireAndForgetQueue = new RetryQueue<FireAndForgetPayload>({\n persist: config.persistRetries ?? false,\n persistKey: `rnx:${this.tenantId}:fnf`,\n onRetryDrop: config.onRetryDrop,\n signRequest: (item) => this.signFireAndForget(item),\n });\n\n this.tracker = new ImpressionTracker(\n this.baseUrl,\n this.tenantId,\n () => this.userId,\n () => this.sessionId,\n this.fireAndForgetQueue,\n );\n }\n\n private resolveSessionId(): string {\n if (typeof sessionStorage === 'undefined') return '';\n let sid = sessionStorage.getItem(SESSION_KEY);\n if (!sid) {\n sid = `sess_${Date.now()}_${Math.random().toString(36).slice(2, 9)}`;\n sessionStorage.setItem(SESSION_KEY, sid);\n }\n return sid;\n }\n\n private effectiveUserId(): string {\n return this.userId || this.sessionId;\n }\n\n private async request<T>(\n method: string,\n path: string,\n body?: unknown,\n query?: Record<string, string | number | boolean | undefined>,\n ): Promise<T> {\n const maxRetries = NEVER_RETRY_PATHS.has(path) ? 0 : SYNC_RETRIES;\n let lastErr: unknown;\n\n for (let attempt = 0; attempt <= maxRetries; attempt++) {\n if (attempt > 0) {\n const exp = Math.min(\n SYNC_MAX_BACKOFF_MS,\n SYNC_INITIAL_BACKOFF_MS * Math.pow(2, attempt - 1),\n );\n const jitter = 0.5 + Math.random() * 0.5;\n await sleep(Math.floor(exp * jitter));\n }\n\n // Always re-sign \u2014 the engine's replay cache rejects the same\n // signature twice, and the 30s signing window means a stale sig\n // would also fail.\n const { url, headers } = await this.buildSignedRequest(\n method,\n path,\n query,\n body,\n );\n\n let res: Response;\n try {\n res = await fetch(url, {\n method,\n headers: {\n 'Content-Type': 'application/json',\n ...headers,\n },\n body: body !== undefined ? JSON.stringify(body) : undefined,\n });\n } catch (err) {\n lastErr = err;\n if (attempt >= maxRetries) throw err;\n continue;\n }\n\n if (res.status === 503) {\n const code = await extractErrorCode(res.clone());\n if (code === AUTH_REPLAY_CACHE_UNAVAILABLE && attempt < maxRetries) {\n lastErr = new Error(`recomnext: ${method} ${path} ${res.status} ${code}`);\n continue;\n }\n }\n\n if (RETRYABLE_STATUS.has(res.status) && attempt < maxRetries) {\n lastErr = new Error(`recomnext: ${method} ${path} returned ${res.status}`);\n continue;\n }\n\n if (!res.ok) {\n const text = await res.text().catch(() => '');\n throw new Error(`recomnext: ${method} ${path} returned ${res.status}: ${text}`);\n }\n\n if (res.status === 204) return undefined as unknown as T;\n return res.json() as Promise<T>;\n }\n\n throw lastErr ?? new Error(`recomnext: ${method} ${path} retries exhausted`);\n }\n\n // Same shape as the RetryQueue's signRequest contract \u2014 used by the\n // queue *and* the impression tracker so both share a single signing\n // path.\n async signFireAndForget(item: QueuedItem<FireAndForgetPayload>) {\n const { url, headers } = await this.buildSignedRequest(\n item.method,\n item.path,\n undefined,\n item.payload,\n );\n return { url, headers };\n }\n\n private async buildSignedRequest(\n method: string,\n path: string,\n query: Record<string, string | number | boolean | undefined> | undefined,\n body: unknown,\n ): Promise<{ url: string; headers: Record<string, string> }> {\n const baseHeaders: Record<string, string> = {\n 'X-Tenant-Id': this.tenantId,\n };\n\n if (!this.publicToken) {\n // Legacy / no-auth mode. Engine accepts because\n // LEGACY_TENANT_AUTH=true. After Phase 7 cutover this path will be\n // rejected with 401.\n const qs = renderQs(query);\n return { url: `${this.baseUrl}${path}${qs}`, headers: baseHeaders };\n }\n\n const signed = await signRequest({\n method,\n path,\n query,\n body: body ?? null,\n tenantId: this.tenantId,\n token: this.publicToken,\n mode: 'public',\n });\n return {\n url: `${this.baseUrl}${signed.signedPath}`,\n headers: {\n ...baseHeaders,\n 'X-Idempotency-Key': signed.idempotencyKey,\n },\n };\n }\n\n private qs(\n params: Record<string, string | number | boolean | undefined>,\n ): Record<string, string | number | boolean | undefined> {\n return params;\n }\n\n private includeQuery(opts?: RecommendationOptions): {\n returnProperties?: boolean;\n includedProperties?: string;\n } {\n return {\n returnProperties: opts?.returnProperties,\n includedProperties:\n opts?.includedProperties && opts.includedProperties.length > 0\n ? opts.includedProperties.join(',')\n : undefined,\n };\n }\n\n /** Serialize override fields for GET query (comma-separated lists; '' clears). */\n private overrideQuery(opts?: RecommendationOptions): {\n filter?: string;\n boosters?: string;\n rulesSlugs?: string;\n segmentationId?: string;\n itemsPerSegment?: number | string;\n autoRelaxFilters?: boolean;\n relaxEntireFilterOnEmpty?: boolean;\n } {\n if (!opts) return {};\n const join = (v: string[] | null | undefined): string | undefined => {\n if (v === undefined) return undefined;\n if (v === null || v.length === 0) return '';\n return v.join(',');\n };\n return {\n filter:\n opts.filter === undefined\n ? undefined\n : opts.filter === null\n ? ''\n : opts.filter,\n boosters: join(opts.boosters),\n rulesSlugs: join(opts.rulesSlugs),\n segmentationId:\n opts.segmentationId === undefined\n ? undefined\n : opts.segmentationId === null\n ? ''\n : opts.segmentationId,\n // Present null \u2192 '' so the query param is sent; engine transform maps\n // '' \u2192 null \u2192 resolve to itemsPerSegment=1 (omit would keep scenario).\n itemsPerSegment:\n opts.itemsPerSegment === undefined\n ? undefined\n : opts.itemsPerSegment === null\n ? ''\n : opts.itemsPerSegment,\n autoRelaxFilters: opts.autoRelaxFilters,\n relaxEntireFilterOnEmpty: opts.relaxEntireFilterOnEmpty,\n };\n }\n\n private overrideBody(opts?: RecommendationOptions): {\n filter?: string | null;\n boosters?: string[] | null;\n rulesSlugs?: string[] | null;\n segmentationId?: string | null;\n itemsPerSegment?: number | null;\n autoRelaxFilters?: boolean;\n relaxEntireFilterOnEmpty?: boolean;\n } {\n if (!opts) return {};\n const body: {\n filter?: string | null;\n boosters?: string[] | null;\n rulesSlugs?: string[] | null;\n segmentationId?: string | null;\n itemsPerSegment?: number | null;\n autoRelaxFilters?: boolean;\n relaxEntireFilterOnEmpty?: boolean;\n } = {};\n if (opts.filter !== undefined) body.filter = opts.filter;\n if (opts.boosters !== undefined) body.boosters = opts.boosters;\n if (opts.rulesSlugs !== undefined) body.rulesSlugs = opts.rulesSlugs;\n if (opts.segmentationId !== undefined) {\n body.segmentationId = opts.segmentationId;\n }\n if (opts.itemsPerSegment !== undefined) {\n body.itemsPerSegment = opts.itemsPerSegment;\n }\n if (opts.autoRelaxFilters !== undefined) {\n body.autoRelaxFilters = opts.autoRelaxFilters;\n }\n if (opts.relaxEntireFilterOnEmpty !== undefined) {\n body.relaxEntireFilterOnEmpty = opts.relaxEntireFilterOnEmpty;\n }\n return body;\n }\n\n /* \u2500\u2500 Recommendations \u2500\u2500 */\n\n getRecommendations(opts?: RecommendationOptions): Promise<RecommendationResult> {\n return this.request(\n 'GET',\n `/recommendations/user-to-item/${this.effectiveUserId()}`,\n undefined,\n this.qs({\n count: opts?.count,\n scenario: opts?.scenario,\n ...this.includeQuery(opts),\n ...this.overrideQuery(opts),\n }),\n );\n }\n\n getRelatedItems(itemId: string, opts?: RecommendationOptions): Promise<RecommendationResult> {\n return this.request(\n 'GET',\n `/recommendations/item-to-item/${itemId}`,\n undefined,\n this.qs({\n count: opts?.count,\n scenario: opts?.scenario,\n ...this.includeQuery(opts),\n ...this.overrideQuery(opts),\n }),\n );\n }\n\n getSimilarProducts(itemId: string, opts?: RecommendationOptions): Promise<RecommendationResult> {\n return this.request(\n 'GET',\n `/recommendations/similar-products/${itemId}`,\n undefined,\n this.qs({\n count: opts?.count,\n scenario: opts?.scenario,\n ...this.includeQuery(opts),\n ...this.overrideQuery(opts),\n }),\n );\n }\n\n getRecentlyViewed(opts?: RecommendationOptions): Promise<RecommendationResult> {\n return this.request(\n 'GET',\n `/recommendations/recently-viewed/${this.effectiveUserId()}`,\n undefined,\n this.qs({\n count: opts?.count,\n scenario: opts?.scenario,\n ...this.includeQuery(opts),\n ...this.overrideQuery(opts),\n }),\n );\n }\n\n getCartRecommendations(itemIds: string[], opts?: RecommendationOptions): Promise<RecommendationResult> {\n return this.request(\n 'POST',\n '/recommendations/items-to-items',\n {\n itemIds,\n count: opts?.count ?? 10,\n returnProperties: opts?.returnProperties,\n includedProperties: opts?.includedProperties,\n ...this.overrideBody(opts),\n },\n this.qs({ scenario: opts?.scenario }),\n );\n }\n\n recommend(opts: RecommendOptions): Promise<RecommendationResult> {\n return this.request('POST', '/recommendations', {\n ...opts,\n userId: this.effectiveUserId(),\n });\n }\n\n /* \u2500\u2500 Search \u2500\u2500 */\n\n search(query: string, opts?: SearchOptions): Promise<SearchResponse> {\n return this.request(\n 'GET',\n '/recommendations/search',\n undefined,\n this.qs({\n query,\n count: opts?.count,\n category: opts?.category,\n priceRange: opts?.priceRange,\n userId: opts?.userId ?? (this.userId || undefined),\n }),\n );\n }\n\n searchV2(query: string, opts?: SearchV2Options): Promise<SearchV2Response> {\n return this.request(\n 'GET',\n '/recommendations/search/v2',\n undefined,\n this.qs({\n query,\n count: opts?.count,\n category: opts?.category,\n priceRange: opts?.priceRange,\n userId: opts?.userId ?? (this.userId || undefined),\n locale: opts?.locale,\n debugScores: opts?.debugScores === true ? true : undefined,\n algorithm: opts?.algorithm,\n }),\n );\n }\n\n autocomplete(\n query: string,\n opts?: { limit?: number },\n ): Promise<SearchV2AutocompleteResponse> {\n return this.request(\n 'GET',\n '/recommendations/search/v2/autocomplete',\n undefined,\n this.qs({\n query,\n limit: opts?.limit,\n }),\n );\n }\n\n getSearchV2Settings(): Promise<SearchV2SettingsResponse> {\n return this.request('GET', '/recommendations/search/v2/settings');\n }\n\n /* \u2500\u2500 Catalog browse (public HMAC eligible) \u2500\u2500 */\n\n listItems(opts?: ListItemsOptions): Promise<CatalogListResponse> {\n return this.request(\n 'GET',\n '/catalog/items',\n undefined,\n this.qs({\n page: opts?.page,\n limit: opts?.limit,\n category: opts?.category,\n search: opts?.search,\n availability: opts?.availability,\n }),\n );\n }\n\n getCategories(): Promise<string[]> {\n return this.request('GET', '/catalog/items/categories');\n }\n\n getItem(externalId: string): Promise<CatalogItem> {\n return this.request('GET', `/catalog/items/${encodeURIComponent(externalId)}`);\n }\n\n /* \u2500\u2500 Event tracking \u2500\u2500 */\n\n trackView(itemId: string, scenarioSlug?: string): void {\n this.fireAndForgetQueue.enqueue({\n path: '/ingestion/interactions',\n method: 'POST',\n payload: {\n interactions: [{ userId: this.effectiveUserId(), itemId, type: 'view', scenarioSlug: scenarioSlug || undefined, timestamp: new Date().toISOString() }],\n },\n });\n }\n\n trackCart(itemId: string, scenarioSlug?: string): void {\n this.fireAndForgetQueue.enqueue({\n path: '/ingestion/interactions',\n method: 'POST',\n payload: {\n interactions: [{ userId: this.effectiveUserId(), itemId, type: 'cart', scenarioSlug: scenarioSlug || undefined, timestamp: new Date().toISOString() }],\n },\n });\n }\n\n trackPurchase(itemId: string, scenarioSlug?: string): void {\n this.fireAndForgetQueue.enqueue({\n path: '/ingestion/interactions',\n method: 'POST',\n payload: {\n interactions: [{ userId: this.effectiveUserId(), itemId, type: 'purchase', scenarioSlug: scenarioSlug || undefined, timestamp: new Date().toISOString() }],\n },\n });\n }\n\n /* \u2500\u2500 Impression tracking \u2500\u2500 */\n\n trackImpressions(container: Element, opts?: ImpressionTrackerOptions): () => void {\n return this.tracker.observe(container, opts);\n }\n\n /* \u2500\u2500 Identity \u2500\u2500 */\n\n setUserId(userId: string): void {\n this.userId = userId;\n }\n\n mergeIdentity(authenticatedUserId: string): Promise<unknown> {\n const anonId = this.sessionId;\n this.userId = authenticatedUserId;\n // Note: never auto-retried (see NEVER_RETRY_PATHS) \u2014 callers should\n // handle 5xx/network failures themselves to avoid duplicate merges.\n return this.request('POST', '/identity/merge', {\n userId: authenticatedUserId,\n sessionIds: [anonId],\n });\n }\n\n /* \u2500\u2500 Lifecycle \u2500\u2500 */\n\n destroy(): void {\n this.tracker.destroy();\n this.fireAndForgetQueue.destroy();\n }\n}\n\n// Shape used by both the SDK's trackView/trackCart/trackPurchase calls\n// and the ImpressionTracker \u2014 they all funnel through the same queue.\nexport type FireAndForgetPayload = {\n interactions?: unknown[];\n impressions?: unknown[];\n};\n\nfunction renderQs(params?: Record<string, string | number | boolean | undefined>): string {\n if (!params) return '';\n const parts: string[] = [];\n for (const [k, v] of Object.entries(params)) {\n // Preserve empty string (GET clear sentinels). Drop only omit/null.\n if (v === undefined || v === null) continue;\n parts.push(`${encodeURIComponent(k)}=${encodeURIComponent(String(v))}`);\n }\n return parts.length ? `?${parts.join('&')}` : '';\n}\n\nasync function extractErrorCode(res: Response): Promise<string | undefined> {\n try {\n const body = (await res.json()) as { errorCode?: unknown };\n return typeof body.errorCode === 'string' ? body.errorCode : undefined;\n } catch {\n return undefined;\n }\n}\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((r) => setTimeout(r, ms));\n}\n"],
5
+ "mappings": "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",
6
6
  "names": ["canonicalJson", "value", "stringify", "keys", "parts", "k", "v", "KEY_CACHE", "sign", "input", "verb", "path", "effectivePath", "timestamp", "bodyHash", "sha256Hex", "canonicalJson", "others", "k", "v", "sortedOthers", "a", "b", "tsParam", "sigParam", "trailing", "queryForSigning", "signingString", "signature", "hmacSha256Hex", "makeIdempotencyKey", "bytes", "toHex", "token", "payload", "key", "getKey", "sig", "cached", "preloadSignerKey", "out", "i", "stripped", "c", "rand", "hex", "DEFAULTS", "RetryQueue", "cfg", "openIndexedDb", "item", "enriched", "makeId", "now", "ready", "i", "url", "headers", "res", "err", "exp", "jitter", "id", "db", "tx", "txDone", "req", "data", "resolve", "reject", "c", "ImpressionTracker", "_baseUrl", "_tenantId", "getUserId", "getSessionId", "retryQueue", "container", "opts", "selector", "threshold", "once", "scenario", "entries", "entry", "el", "itemId", "key", "urgent", "batch", "grouped", "impressions", "scenarioSlug", "itemIds", "SESSION_KEY", "SYNC_RETRIES", "SYNC_INITIAL_BACKOFF_MS", "SYNC_MAX_BACKOFF_MS", "RETRYABLE_STATUS", "AUTH_REPLAY_CACHE_UNAVAILABLE", "NEVER_RETRY_PATHS", "RecomnextBrowserClient", "config", "preloadSignerKey", "RetryQueue", "item", "ImpressionTracker", "sid", "method", "path", "body", "query", "maxRetries", "lastErr", "attempt", "exp", "jitter", "sleep", "url", "headers", "res", "err", "code", "extractErrorCode", "text", "baseHeaders", "qs", "renderQs", "signed", "sign", "params", "opts", "join", "v", "itemId", "itemIds", "externalId", "scenarioSlug", "container", "userId", "authenticatedUserId", "anonId", "parts", "k", "ms", "r"]
7
7
  }
@@ -1,4 +1,4 @@
1
- "use strict";var RecomnextBrowser=(()=>{var S=Object.defineProperty;var O=Object.getOwnPropertyDescriptor;var $=Object.getOwnPropertyNames;var q=Object.prototype.hasOwnProperty;var k=(n,e)=>{for(var t in e)S(n,t,{get:e[t],enumerable:!0})},E=(n,e,t,r)=>{if(e&&typeof e=="object"||typeof e=="function")for(let s of $(e))!q.call(n,s)&&s!==t&&S(n,s,{get:()=>e[s],enumerable:!(r=O(e,s))||r.enumerable});return n};var C=n=>E(S({},"__esModule",{value:!0}),n);var X={};k(X,{ImpressionTracker:()=>g,RecomnextBrowserClient:()=>I});function w(n){return R(n)}function R(n){if(n===null)return"null";switch(typeof n){case"boolean":return n?"true":"false";case"number":if(!Number.isFinite(n))throw new TypeError(`Cannot canonicalize non-finite number: ${String(n)}`);return JSON.stringify(n);case"string":return JSON.stringify(n);case"object":{if(Array.isArray(n))return"["+n.map(R).join(",")+"]";let e=Object.keys(n).sort(),t=[];for(let r of e){let s=n[r];typeof s>"u"||t.push(JSON.stringify(r)+":"+R(s))}return"{"+t.join(",")+"}"}case"undefined":throw new TypeError("Cannot canonicalize undefined at the root");default:throw new TypeError(`Cannot canonicalize value of type ${typeof n}`)}}var T=new Map;async function b(n){let e=n.method.toUpperCase(),t=_(n.path),r=n.now??Math.floor(Date.now()/1e3),s=e==="GET"||e==="DELETE"||e==="HEAD"||e==="OPTIONS"?"":await U(w(n.body??null)),i=new Map;for(let[f,c]of Object.entries(n.query??{}))c!=null&&i.set(f,String(c));i.delete("hmac_timestamp"),i.delete("frontend_timestamp"),i.delete("body_hash"),i.delete("hmac_sign"),i.delete("frontend_sign");let o=Array.from(i.entries()).sort(([f],[c])=>f<c?-1:f>c?1:0).map(([f,c])=>`${encodeURIComponent(f)}=${encodeURIComponent(c)}`),u=n.mode==="public"?"frontend_timestamp":"hmac_timestamp",l=n.mode==="public"?"frontend_sign":"hmac_sign",m=`${u}=${r}&body_hash=${s}`,a=o.length?`${o.join("&")}&${m}`:m,d=`${e}
1
+ "use strict";var RecomnextBrowser=(()=>{var S=Object.defineProperty;var O=Object.getOwnPropertyDescriptor;var A=Object.getOwnPropertyNames;var $=Object.prototype.hasOwnProperty;var q=(n,e)=>{for(var t in e)S(n,t,{get:e[t],enumerable:!0})},k=(n,e,t,r)=>{if(e&&typeof e=="object"||typeof e=="function")for(let i of A(e))!$.call(n,i)&&i!==t&&S(n,i,{get:()=>e[i],enumerable:!(r=O(e,i))||r.enumerable});return n};var F=n=>k(S({},"__esModule",{value:!0}),n);var X={};q(X,{ImpressionTracker:()=>g,RecomnextBrowserClient:()=>I});function b(n){return R(n)}function R(n){if(n===null)return"null";switch(typeof n){case"boolean":return n?"true":"false";case"number":if(!Number.isFinite(n))throw new TypeError(`Cannot canonicalize non-finite number: ${String(n)}`);return JSON.stringify(n);case"string":return JSON.stringify(n);case"object":{if(Array.isArray(n))return"["+n.map(R).join(",")+"]";let e=Object.keys(n).sort(),t=[];for(let r of e){let i=n[r];typeof i>"u"||t.push(JSON.stringify(r)+":"+R(i))}return"{"+t.join(",")+"}"}case"undefined":throw new TypeError("Cannot canonicalize undefined at the root");default:throw new TypeError(`Cannot canonicalize value of type ${typeof n}`)}}var x=new Map;async function w(n){let e=n.method.toUpperCase(),t=_(n.path),r=n.now??Math.floor(Date.now()/1e3),i=e==="GET"||e==="DELETE"||e==="HEAD"||e==="OPTIONS"?"":await C(b(n.body??null)),s=new Map;for(let[f,c]of Object.entries(n.query??{}))c!=null&&s.set(f,String(c));s.delete("hmac_timestamp"),s.delete("frontend_timestamp"),s.delete("body_hash"),s.delete("hmac_sign"),s.delete("frontend_sign");let o=Array.from(s.entries()).sort(([f],[c])=>f<c?-1:f>c?1:0).map(([f,c])=>`${encodeURIComponent(f)}=${encodeURIComponent(c)}`),u=n.mode==="public"?"frontend_timestamp":"hmac_timestamp",l=n.mode==="public"?"frontend_sign":"hmac_sign",m=`${u}=${r}&body_hash=${i}`,a=o.length?`${o.join("&")}&${m}`:m,d=`${e}
2
2
  ${t}?${a}
3
- X-Tenant-Id:${n.tenantId}`,h=await F(n.token,d);return{signedPath:`${t}?${a}&${l}=${h}`,timestamp:r,bodyHash:s,signature:h,idempotencyKey:M()}}async function U(n){let e=await crypto.subtle.digest("SHA-256",new TextEncoder().encode(n));return P(new Uint8Array(e))}async function F(n,e){let t=await A(n),r=await crypto.subtle.sign("HMAC",t,new TextEncoder().encode(e));return P(new Uint8Array(r))}async function A(n){let e=T.get(n);return e||(e=crypto.subtle.importKey("raw",new TextEncoder().encode(n),{name:"HMAC",hash:"SHA-256"},!1,["sign"]),T.set(n,e)),e}function v(n){return A(n)}function P(n){let e="";for(let t=0;t<n.length;t++)e+=n[t].toString(16).padStart(2,"0");return e}function _(n){let e=n.replace(/\/$/,"");return e.length>0?e:"/"}function M(){let n=globalThis.crypto;if(n?.randomUUID)return n.randomUUID();let e=new Uint8Array(16);n?.getRandomValues(e),e[6]=e[6]&15|64,e[8]=e[8]&63|128;let t=P(e);return`${t.slice(0,8)}-${t.slice(8,12)}-${t.slice(12,16)}-${t.slice(16,20)}-${t.slice(20)}`}var y={maxAttempts:8,initialBackoffMs:1e3,maxBackoffMs:6e5,flushIntervalMs:2e3},p=class{constructor(e){if(this.items=[],this.timer=null,this.draining=!1,this.cfg={persist:e.persist??!1,persistKey:e.persistKey,maxAttempts:e.maxAttempts??y.maxAttempts,initialBackoffMs:e.initialBackoffMs??y.initialBackoffMs,maxBackoffMs:e.maxBackoffMs??y.maxBackoffMs,flushIntervalMs:e.flushIntervalMs??y.flushIntervalMs,onRetryDrop:e.onRetryDrop,signRequest:e.signRequest},this.cfg.persist){if(!this.cfg.persistKey)throw new Error("RetryQueue: persistKey required when persist=true");this.idb=B(),this.loadPersisted()}typeof window<"u"&&(this.timer=setInterval(()=>void this.drain(),this.cfg.flushIntervalMs),window.addEventListener("online",()=>void this.drain()),window.addEventListener("pagehide",()=>void this.drain()))}enqueue(e){let t={...e,id:e.id??Q(),attempts:e.attempts??0,nextAttemptAt:e.nextAttemptAt??Date.now()};this.items.push(t),this.persist(),this.drain()}flush(){return this.drain()}destroy(){this.timer&&clearInterval(this.timer),this.timer=null}async drain(){if(!this.draining&&!(typeof navigator<"u"&&navigator.onLine===!1)){this.draining=!0;try{let e=Date.now(),t=this.items.filter(r=>(r.nextAttemptAt??0)<=e);for(let r of t)await this.attempt(r);await this.persist()}finally{this.draining=!1}}}async attempt(e){e.attempts=(e.attempts??0)+1;try{let{url:t,headers:r}=await this.cfg.signRequest(e),s=await fetch(t,{method:e.method,headers:{"Content-Type":"application/json",...r},body:JSON.stringify(e.payload),keepalive:!0});if(s.ok){this.removeById(e.id);return}if(s.status>=400&&s.status<500&&s.status!==429){this.removeById(e.id),this.cfg.onRetryDrop?.(e,new Error(`HTTP ${s.status}`));return}this.scheduleRetry(e,new Error(`HTTP ${s.status}`))}catch(t){this.scheduleRetry(e,t)}}scheduleRetry(e,t){if((e.attempts??0)>=this.cfg.maxAttempts){this.removeById(e.id),this.cfg.onRetryDrop?.(e,t);return}let r=Math.min(this.cfg.maxBackoffMs,this.cfg.initialBackoffMs*Math.pow(2,(e.attempts??1)-1)),s=.5+Math.random()*.5;e.nextAttemptAt=Date.now()+Math.floor(r*s)}removeById(e){this.items=this.items.filter(t=>t.id!==e)}async persist(){if(!(!this.cfg.persist||!this.idb||!this.cfg.persistKey))try{let e=await this.idb;if(!e)return;let t=e.transaction("queue","readwrite");t.objectStore("queue").put(JSON.parse(JSON.stringify(this.items)),this.cfg.persistKey),await D(t)}catch{}}async loadPersisted(){if(!(!this.cfg.persist||!this.idb||!this.cfg.persistKey))try{let e=await this.idb;if(!e)return;let s=e.transaction("queue","readonly").objectStore("queue").get(this.cfg.persistKey),i=await new Promise((o,u)=>{s.onsuccess=()=>o(s.result),s.onerror=()=>u(s.error)});Array.isArray(i)&&(this.items=i,this.drain())}catch{}}};function Q(){let n=globalThis.crypto;return n?.randomUUID?n.randomUUID():`rnx-${Date.now()}-${Math.random().toString(36).slice(2,11)}`}function B(){return typeof indexedDB>"u"?Promise.resolve(null):new Promise(n=>{let e=indexedDB.open("recomnext-retry-queue",1);e.onupgradeneeded=()=>{let t=e.result;t.objectStoreNames.contains("queue")||t.createObjectStore("queue")},e.onsuccess=()=>n(e.result),e.onerror=()=>n(null),e.onblocked=()=>n(null)})}function D(n){return new Promise((e,t)=>{n.oncomplete=()=>e(),n.onerror=()=>t(n.error),n.onabort=()=>t(n.error)})}var g=class{constructor(e,t,r,s,i){this.queue=[];this.flushing=!1;this.observer=null;this.seen=new Set;this.timer=null;this.getUserId=r,this.getSessionId=s,this.retryQueue=i,typeof window<"u"&&(this.timer=setInterval(()=>this.flush(),2e3),window.addEventListener("pagehide",()=>this.flush(!0)))}observe(e,t={}){let r=t.itemSelector??"[data-item-id]",s=t.threshold??.5,i=t.once!==!1,o=t.scenario??"";return this.observer=new IntersectionObserver(l=>{for(let m of l){if(!m.isIntersecting)continue;let a=m.target,d=a.dataset.itemId;if(!d)continue;let h=`${o}:${d}`;i&&this.seen.has(h)||(this.seen.add(h),this.queue.push({itemId:d,scenario:o}),i&&this.observer?.unobserve(a))}},{root:null,threshold:s}),e.querySelectorAll(r).forEach(l=>this.observer.observe(l)),()=>{this.observer?.disconnect(),this.observer=null}}flush(e=!1){if(this.flushing||this.queue.length===0)return;this.flushing=!0;let t=this.queue.splice(0,this.queue.length),r=new Map;for(let{itemId:i,scenario:o}of t)r.has(o)||r.set(o,[]),r.get(o).push(i);let s=Array.from(r.entries()).map(([i,o])=>({userId:this.getUserId()||void 0,sessionId:this.getSessionId()||void 0,itemIds:o,scenarioSlug:i||void 0,timestamp:new Date().toISOString()}));this.retryQueue.enqueue({path:"/ingestion/impressions",method:"POST",payload:{impressions:s}}),e&&this.retryQueue.flush(),this.flushing=!1}destroy(){this.flush(),this.observer?.disconnect(),this.timer&&clearInterval(this.timer)}};var x="recomnext_session_id",V=3,K=200,L=2e3,j=new Set([429,500,502,503,504]),H="auth_replay_cache_unavailable",N=new Set(["/identity/merge"]),I=class{constructor(e){this.baseUrl=e.baseUrl.replace(/\/+$/,""),this.tenantId=e.tenantId,this.publicToken=e.publicToken,this.userId=e.userId??"",this.sessionId=e.sessionId??this.resolveSessionId(),this.publicToken&&v(this.publicToken),this.fireAndForgetQueue=new p({persist:e.persistRetries??!1,persistKey:`rnx:${this.tenantId}:fnf`,onRetryDrop:e.onRetryDrop,signRequest:t=>this.signFireAndForget(t)}),this.tracker=new g(this.baseUrl,this.tenantId,()=>this.userId,()=>this.sessionId,this.fireAndForgetQueue)}resolveSessionId(){if(typeof sessionStorage>"u")return"";let e=sessionStorage.getItem(x);return e||(e=`sess_${Date.now()}_${Math.random().toString(36).slice(2,9)}`,sessionStorage.setItem(x,e)),e}effectiveUserId(){return this.userId||this.sessionId}async request(e,t,r,s){let i=N.has(t)?0:V,o;for(let u=0;u<=i;u++){if(u>0){let d=Math.min(L,K*Math.pow(2,u-1)),h=.5+Math.random()*.5;await Y(Math.floor(d*h))}let{url:l,headers:m}=await this.buildSignedRequest(e,t,s,r),a;try{a=await fetch(l,{method:e,headers:{"Content-Type":"application/json",...m},body:r!==void 0?JSON.stringify(r):void 0})}catch(d){if(o=d,u>=i)throw d;continue}if(a.status===503){let d=await J(a.clone());if(d===H&&u<i){o=new Error(`recomnext: ${e} ${t} ${a.status} ${d}`);continue}}if(j.has(a.status)&&u<i){o=new Error(`recomnext: ${e} ${t} returned ${a.status}`);continue}if(!a.ok){let d=await a.text().catch(()=>"");throw new Error(`recomnext: ${e} ${t} returned ${a.status}: ${d}`)}return a.status===204?void 0:a.json()}throw o??new Error(`recomnext: ${e} ${t} retries exhausted`)}async signFireAndForget(e){let{url:t,headers:r}=await this.buildSignedRequest(e.method,e.path,void 0,e.payload);return{url:t,headers:r}}async buildSignedRequest(e,t,r,s){let i={"X-Tenant-Id":this.tenantId};if(!this.publicToken){let u=G(r);return{url:`${this.baseUrl}${t}${u}`,headers:i}}let o=await b({method:e,path:t,query:r,body:s??null,tenantId:this.tenantId,token:this.publicToken,mode:"public"});return{url:`${this.baseUrl}${o.signedPath}`,headers:{...i,"X-Idempotency-Key":o.idempotencyKey}}}qs(e){return e}includeQuery(e){return{returnProperties:e?.returnProperties,includedProperties:e?.includedProperties&&e.includedProperties.length>0?e.includedProperties.join(","):void 0}}overrideQuery(e){if(!e)return{};let t=r=>{if(r!==void 0)return r===null||r.length===0?"":r.join(",")};return{filter:e.filter===void 0?void 0:e.filter===null?"":e.filter,boosters:t(e.boosters),rulesSlugs:t(e.rulesSlugs),segmentationId:e.segmentationId===void 0?void 0:e.segmentationId===null?"":e.segmentationId,itemsPerSegment:e.itemsPerSegment===void 0?void 0:e.itemsPerSegment===null?"":e.itemsPerSegment}}overrideBody(e){if(!e)return{};let t={};return e.filter!==void 0&&(t.filter=e.filter),e.boosters!==void 0&&(t.boosters=e.boosters),e.rulesSlugs!==void 0&&(t.rulesSlugs=e.rulesSlugs),e.segmentationId!==void 0&&(t.segmentationId=e.segmentationId),e.itemsPerSegment!==void 0&&(t.itemsPerSegment=e.itemsPerSegment),t}getRecommendations(e){return this.request("GET",`/recommendations/user-to-item/${this.effectiveUserId()}`,void 0,this.qs({count:e?.count,scenario:e?.scenario,...this.includeQuery(e),...this.overrideQuery(e)}))}getRelatedItems(e,t){return this.request("GET",`/recommendations/item-to-item/${e}`,void 0,this.qs({count:t?.count,scenario:t?.scenario,...this.includeQuery(t),...this.overrideQuery(t)}))}getSimilarProducts(e,t){return this.request("GET",`/recommendations/similar-products/${e}`,void 0,this.qs({count:t?.count,scenario:t?.scenario,...this.includeQuery(t),...this.overrideQuery(t)}))}getRecentlyViewed(e){return this.request("GET",`/recommendations/recently-viewed/${this.effectiveUserId()}`,void 0,this.qs({count:e?.count,scenario:e?.scenario,...this.includeQuery(e),...this.overrideQuery(e)}))}getCartRecommendations(e,t){return this.request("POST","/recommendations/items-to-items",{itemIds:e,count:t?.count??10,returnProperties:t?.returnProperties,includedProperties:t?.includedProperties,...this.overrideBody(t)},this.qs({scenario:t?.scenario}))}recommend(e){return this.request("POST","/recommendations",{...e,userId:this.effectiveUserId()})}search(e,t){return this.request("GET","/recommendations/search",void 0,this.qs({query:e,count:t?.count,category:t?.category,priceRange:t?.priceRange,userId:t?.userId??(this.userId||void 0)}))}searchV2(e,t){return this.request("GET","/recommendations/search/v2",void 0,this.qs({query:e,count:t?.count,category:t?.category,priceRange:t?.priceRange,userId:t?.userId??(this.userId||void 0),locale:t?.locale,debugScores:t?.debugScores===!0?!0:void 0,algorithm:t?.algorithm}))}autocomplete(e,t){return this.request("GET","/recommendations/search/v2/autocomplete",void 0,this.qs({query:e,limit:t?.limit}))}getSearchV2Settings(){return this.request("GET","/recommendations/search/v2/settings")}listItems(e){return this.request("GET","/catalog/items",void 0,this.qs({page:e?.page,limit:e?.limit,category:e?.category,search:e?.search,availability:e?.availability}))}getCategories(){return this.request("GET","/catalog/items/categories")}getItem(e){return this.request("GET",`/catalog/items/${encodeURIComponent(e)}`)}trackView(e,t){this.fireAndForgetQueue.enqueue({path:"/ingestion/interactions",method:"POST",payload:{interactions:[{userId:this.effectiveUserId(),itemId:e,type:"view",scenarioSlug:t||void 0,timestamp:new Date().toISOString()}]}})}trackCart(e,t){this.fireAndForgetQueue.enqueue({path:"/ingestion/interactions",method:"POST",payload:{interactions:[{userId:this.effectiveUserId(),itemId:e,type:"cart",scenarioSlug:t||void 0,timestamp:new Date().toISOString()}]}})}trackPurchase(e,t){this.fireAndForgetQueue.enqueue({path:"/ingestion/interactions",method:"POST",payload:{interactions:[{userId:this.effectiveUserId(),itemId:e,type:"purchase",scenarioSlug:t||void 0,timestamp:new Date().toISOString()}]}})}trackImpressions(e,t){return this.tracker.observe(e,t)}setUserId(e){this.userId=e}mergeIdentity(e){let t=this.sessionId;return this.userId=e,this.request("POST","/identity/merge",{userId:e,sessionIds:[t]})}destroy(){this.tracker.destroy(),this.fireAndForgetQueue.destroy()}};function G(n){if(!n)return"";let e=[];for(let[t,r]of Object.entries(n))r!=null&&e.push(`${encodeURIComponent(t)}=${encodeURIComponent(String(r))}`);return e.length?`?${e.join("&")}`:""}async function J(n){try{let e=await n.json();return typeof e.errorCode=="string"?e.errorCode:void 0}catch{return}}function Y(n){return new Promise(e=>setTimeout(e,n))}return C(X);})();
3
+ X-Tenant-Id:${n.tenantId}`,h=await U(n.token,d);return{signedPath:`${t}?${a}&${l}=${h}`,timestamp:r,bodyHash:i,signature:h,idempotencyKey:M()}}async function C(n){let e=await crypto.subtle.digest("SHA-256",new TextEncoder().encode(n));return P(new Uint8Array(e))}async function U(n,e){let t=await E(n),r=await crypto.subtle.sign("HMAC",t,new TextEncoder().encode(e));return P(new Uint8Array(r))}async function E(n){let e=x.get(n);return e||(e=crypto.subtle.importKey("raw",new TextEncoder().encode(n),{name:"HMAC",hash:"SHA-256"},!1,["sign"]),x.set(n,e)),e}function v(n){return E(n)}function P(n){let e="";for(let t=0;t<n.length;t++)e+=n[t].toString(16).padStart(2,"0");return e}function _(n){let e=n.replace(/\/$/,"");return e.length>0?e:"/"}function M(){let n=globalThis.crypto;if(n?.randomUUID)return n.randomUUID();let e=new Uint8Array(16);n?.getRandomValues(e),e[6]=e[6]&15|64,e[8]=e[8]&63|128;let t=P(e);return`${t.slice(0,8)}-${t.slice(8,12)}-${t.slice(12,16)}-${t.slice(16,20)}-${t.slice(20)}`}var y={maxAttempts:8,initialBackoffMs:1e3,maxBackoffMs:6e5,flushIntervalMs:2e3},p=class{constructor(e){if(this.items=[],this.timer=null,this.draining=!1,this.cfg={persist:e.persist??!1,persistKey:e.persistKey,maxAttempts:e.maxAttempts??y.maxAttempts,initialBackoffMs:e.initialBackoffMs??y.initialBackoffMs,maxBackoffMs:e.maxBackoffMs??y.maxBackoffMs,flushIntervalMs:e.flushIntervalMs??y.flushIntervalMs,onRetryDrop:e.onRetryDrop,signRequest:e.signRequest},this.cfg.persist){if(!this.cfg.persistKey)throw new Error("RetryQueue: persistKey required when persist=true");this.idb=B(),this.loadPersisted()}typeof window<"u"&&(this.timer=setInterval(()=>void this.drain(),this.cfg.flushIntervalMs),window.addEventListener("online",()=>void this.drain()),window.addEventListener("pagehide",()=>void this.drain()))}enqueue(e){let t={...e,id:e.id??Q(),attempts:e.attempts??0,nextAttemptAt:e.nextAttemptAt??Date.now()};this.items.push(t),this.persist(),this.drain()}flush(){return this.drain()}destroy(){this.timer&&clearInterval(this.timer),this.timer=null}async drain(){if(!this.draining&&!(typeof navigator<"u"&&navigator.onLine===!1)){this.draining=!0;try{let e=Date.now(),t=this.items.filter(r=>(r.nextAttemptAt??0)<=e);for(let r of t)await this.attempt(r);await this.persist()}finally{this.draining=!1}}}async attempt(e){e.attempts=(e.attempts??0)+1;try{let{url:t,headers:r}=await this.cfg.signRequest(e),i=await fetch(t,{method:e.method,headers:{"Content-Type":"application/json",...r},body:JSON.stringify(e.payload),keepalive:!0});if(i.ok){this.removeById(e.id);return}if(i.status>=400&&i.status<500&&i.status!==429){this.removeById(e.id),this.cfg.onRetryDrop?.(e,new Error(`HTTP ${i.status}`));return}this.scheduleRetry(e,new Error(`HTTP ${i.status}`))}catch(t){this.scheduleRetry(e,t)}}scheduleRetry(e,t){if((e.attempts??0)>=this.cfg.maxAttempts){this.removeById(e.id),this.cfg.onRetryDrop?.(e,t);return}let r=Math.min(this.cfg.maxBackoffMs,this.cfg.initialBackoffMs*Math.pow(2,(e.attempts??1)-1)),i=.5+Math.random()*.5;e.nextAttemptAt=Date.now()+Math.floor(r*i)}removeById(e){this.items=this.items.filter(t=>t.id!==e)}async persist(){if(!(!this.cfg.persist||!this.idb||!this.cfg.persistKey))try{let e=await this.idb;if(!e)return;let t=e.transaction("queue","readwrite");t.objectStore("queue").put(JSON.parse(JSON.stringify(this.items)),this.cfg.persistKey),await D(t)}catch{}}async loadPersisted(){if(!(!this.cfg.persist||!this.idb||!this.cfg.persistKey))try{let e=await this.idb;if(!e)return;let i=e.transaction("queue","readonly").objectStore("queue").get(this.cfg.persistKey),s=await new Promise((o,u)=>{i.onsuccess=()=>o(i.result),i.onerror=()=>u(i.error)});Array.isArray(s)&&(this.items=s,this.drain())}catch{}}};function Q(){let n=globalThis.crypto;return n?.randomUUID?n.randomUUID():`rnx-${Date.now()}-${Math.random().toString(36).slice(2,11)}`}function B(){return typeof indexedDB>"u"?Promise.resolve(null):new Promise(n=>{let e=indexedDB.open("recomnext-retry-queue",1);e.onupgradeneeded=()=>{let t=e.result;t.objectStoreNames.contains("queue")||t.createObjectStore("queue")},e.onsuccess=()=>n(e.result),e.onerror=()=>n(null),e.onblocked=()=>n(null)})}function D(n){return new Promise((e,t)=>{n.oncomplete=()=>e(),n.onerror=()=>t(n.error),n.onabort=()=>t(n.error)})}var g=class{constructor(e,t,r,i,s){this.queue=[];this.flushing=!1;this.observer=null;this.seen=new Set;this.timer=null;this.getUserId=r,this.getSessionId=i,this.retryQueue=s,typeof window<"u"&&(this.timer=setInterval(()=>this.flush(),2e3),window.addEventListener("pagehide",()=>this.flush(!0)))}observe(e,t={}){let r=t.itemSelector??"[data-item-id]",i=t.threshold??.5,s=t.once!==!1,o=t.scenario??"";return this.observer=new IntersectionObserver(l=>{for(let m of l){if(!m.isIntersecting)continue;let a=m.target,d=a.dataset.itemId;if(!d)continue;let h=`${o}:${d}`;s&&this.seen.has(h)||(this.seen.add(h),this.queue.push({itemId:d,scenario:o}),s&&this.observer?.unobserve(a))}},{root:null,threshold:i}),e.querySelectorAll(r).forEach(l=>this.observer.observe(l)),()=>{this.observer?.disconnect(),this.observer=null}}flush(e=!1){if(this.flushing||this.queue.length===0)return;this.flushing=!0;let t=this.queue.splice(0,this.queue.length),r=new Map;for(let{itemId:s,scenario:o}of t)r.has(o)||r.set(o,[]),r.get(o).push(s);let i=Array.from(r.entries()).map(([s,o])=>({userId:this.getUserId()||void 0,sessionId:this.getSessionId()||void 0,itemIds:o,scenarioSlug:s||void 0,timestamp:new Date().toISOString()}));this.retryQueue.enqueue({path:"/ingestion/impressions",method:"POST",payload:{impressions:i}}),e&&this.retryQueue.flush(),this.flushing=!1}destroy(){this.flush(),this.observer?.disconnect(),this.timer&&clearInterval(this.timer)}};var T="recomnext_session_id",V=3,K=200,L=2e3,j=new Set([429,500,502,503,504]),H="auth_replay_cache_unavailable",N=new Set(["/identity/merge"]),I=class{constructor(e){this.baseUrl=e.baseUrl.replace(/\/+$/,""),this.tenantId=e.tenantId,this.publicToken=e.publicToken,this.userId=e.userId??"",this.sessionId=e.sessionId??this.resolveSessionId(),this.publicToken&&v(this.publicToken),this.fireAndForgetQueue=new p({persist:e.persistRetries??!1,persistKey:`rnx:${this.tenantId}:fnf`,onRetryDrop:e.onRetryDrop,signRequest:t=>this.signFireAndForget(t)}),this.tracker=new g(this.baseUrl,this.tenantId,()=>this.userId,()=>this.sessionId,this.fireAndForgetQueue)}resolveSessionId(){if(typeof sessionStorage>"u")return"";let e=sessionStorage.getItem(T);return e||(e=`sess_${Date.now()}_${Math.random().toString(36).slice(2,9)}`,sessionStorage.setItem(T,e)),e}effectiveUserId(){return this.userId||this.sessionId}async request(e,t,r,i){let s=N.has(t)?0:V,o;for(let u=0;u<=s;u++){if(u>0){let d=Math.min(L,K*Math.pow(2,u-1)),h=.5+Math.random()*.5;await Y(Math.floor(d*h))}let{url:l,headers:m}=await this.buildSignedRequest(e,t,i,r),a;try{a=await fetch(l,{method:e,headers:{"Content-Type":"application/json",...m},body:r!==void 0?JSON.stringify(r):void 0})}catch(d){if(o=d,u>=s)throw d;continue}if(a.status===503){let d=await J(a.clone());if(d===H&&u<s){o=new Error(`recomnext: ${e} ${t} ${a.status} ${d}`);continue}}if(j.has(a.status)&&u<s){o=new Error(`recomnext: ${e} ${t} returned ${a.status}`);continue}if(!a.ok){let d=await a.text().catch(()=>"");throw new Error(`recomnext: ${e} ${t} returned ${a.status}: ${d}`)}return a.status===204?void 0:a.json()}throw o??new Error(`recomnext: ${e} ${t} retries exhausted`)}async signFireAndForget(e){let{url:t,headers:r}=await this.buildSignedRequest(e.method,e.path,void 0,e.payload);return{url:t,headers:r}}async buildSignedRequest(e,t,r,i){let s={"X-Tenant-Id":this.tenantId};if(!this.publicToken){let u=G(r);return{url:`${this.baseUrl}${t}${u}`,headers:s}}let o=await w({method:e,path:t,query:r,body:i??null,tenantId:this.tenantId,token:this.publicToken,mode:"public"});return{url:`${this.baseUrl}${o.signedPath}`,headers:{...s,"X-Idempotency-Key":o.idempotencyKey}}}qs(e){return e}includeQuery(e){return{returnProperties:e?.returnProperties,includedProperties:e?.includedProperties&&e.includedProperties.length>0?e.includedProperties.join(","):void 0}}overrideQuery(e){if(!e)return{};let t=r=>{if(r!==void 0)return r===null||r.length===0?"":r.join(",")};return{filter:e.filter===void 0?void 0:e.filter===null?"":e.filter,boosters:t(e.boosters),rulesSlugs:t(e.rulesSlugs),segmentationId:e.segmentationId===void 0?void 0:e.segmentationId===null?"":e.segmentationId,itemsPerSegment:e.itemsPerSegment===void 0?void 0:e.itemsPerSegment===null?"":e.itemsPerSegment,autoRelaxFilters:e.autoRelaxFilters,relaxEntireFilterOnEmpty:e.relaxEntireFilterOnEmpty}}overrideBody(e){if(!e)return{};let t={};return e.filter!==void 0&&(t.filter=e.filter),e.boosters!==void 0&&(t.boosters=e.boosters),e.rulesSlugs!==void 0&&(t.rulesSlugs=e.rulesSlugs),e.segmentationId!==void 0&&(t.segmentationId=e.segmentationId),e.itemsPerSegment!==void 0&&(t.itemsPerSegment=e.itemsPerSegment),e.autoRelaxFilters!==void 0&&(t.autoRelaxFilters=e.autoRelaxFilters),e.relaxEntireFilterOnEmpty!==void 0&&(t.relaxEntireFilterOnEmpty=e.relaxEntireFilterOnEmpty),t}getRecommendations(e){return this.request("GET",`/recommendations/user-to-item/${this.effectiveUserId()}`,void 0,this.qs({count:e?.count,scenario:e?.scenario,...this.includeQuery(e),...this.overrideQuery(e)}))}getRelatedItems(e,t){return this.request("GET",`/recommendations/item-to-item/${e}`,void 0,this.qs({count:t?.count,scenario:t?.scenario,...this.includeQuery(t),...this.overrideQuery(t)}))}getSimilarProducts(e,t){return this.request("GET",`/recommendations/similar-products/${e}`,void 0,this.qs({count:t?.count,scenario:t?.scenario,...this.includeQuery(t),...this.overrideQuery(t)}))}getRecentlyViewed(e){return this.request("GET",`/recommendations/recently-viewed/${this.effectiveUserId()}`,void 0,this.qs({count:e?.count,scenario:e?.scenario,...this.includeQuery(e),...this.overrideQuery(e)}))}getCartRecommendations(e,t){return this.request("POST","/recommendations/items-to-items",{itemIds:e,count:t?.count??10,returnProperties:t?.returnProperties,includedProperties:t?.includedProperties,...this.overrideBody(t)},this.qs({scenario:t?.scenario}))}recommend(e){return this.request("POST","/recommendations",{...e,userId:this.effectiveUserId()})}search(e,t){return this.request("GET","/recommendations/search",void 0,this.qs({query:e,count:t?.count,category:t?.category,priceRange:t?.priceRange,userId:t?.userId??(this.userId||void 0)}))}searchV2(e,t){return this.request("GET","/recommendations/search/v2",void 0,this.qs({query:e,count:t?.count,category:t?.category,priceRange:t?.priceRange,userId:t?.userId??(this.userId||void 0),locale:t?.locale,debugScores:t?.debugScores===!0?!0:void 0,algorithm:t?.algorithm}))}autocomplete(e,t){return this.request("GET","/recommendations/search/v2/autocomplete",void 0,this.qs({query:e,limit:t?.limit}))}getSearchV2Settings(){return this.request("GET","/recommendations/search/v2/settings")}listItems(e){return this.request("GET","/catalog/items",void 0,this.qs({page:e?.page,limit:e?.limit,category:e?.category,search:e?.search,availability:e?.availability}))}getCategories(){return this.request("GET","/catalog/items/categories")}getItem(e){return this.request("GET",`/catalog/items/${encodeURIComponent(e)}`)}trackView(e,t){this.fireAndForgetQueue.enqueue({path:"/ingestion/interactions",method:"POST",payload:{interactions:[{userId:this.effectiveUserId(),itemId:e,type:"view",scenarioSlug:t||void 0,timestamp:new Date().toISOString()}]}})}trackCart(e,t){this.fireAndForgetQueue.enqueue({path:"/ingestion/interactions",method:"POST",payload:{interactions:[{userId:this.effectiveUserId(),itemId:e,type:"cart",scenarioSlug:t||void 0,timestamp:new Date().toISOString()}]}})}trackPurchase(e,t){this.fireAndForgetQueue.enqueue({path:"/ingestion/interactions",method:"POST",payload:{interactions:[{userId:this.effectiveUserId(),itemId:e,type:"purchase",scenarioSlug:t||void 0,timestamp:new Date().toISOString()}]}})}trackImpressions(e,t){return this.tracker.observe(e,t)}setUserId(e){this.userId=e}mergeIdentity(e){let t=this.sessionId;return this.userId=e,this.request("POST","/identity/merge",{userId:e,sessionIds:[t]})}destroy(){this.tracker.destroy(),this.fireAndForgetQueue.destroy()}};function G(n){if(!n)return"";let e=[];for(let[t,r]of Object.entries(n))r!=null&&e.push(`${encodeURIComponent(t)}=${encodeURIComponent(String(r))}`);return e.length?`?${e.join("&")}`:""}async function J(n){try{let e=await n.json();return typeof e.errorCode=="string"?e.errorCode:void 0}catch{return}}function Y(n){return new Promise(e=>setTimeout(e,n))}return F(X);})();
4
4
  //# sourceMappingURL=recomnext-browser.umd.js.map
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/index.ts", "../../shared-browser-auth/src/canonical-json.ts", "../../shared-browser-auth/src/sign.ts", "../../shared-browser-auth/src/retry-queue.ts", "../src/tracker.ts", "../src/client.ts"],
4
- "sourcesContent": ["export { RecomnextBrowserClient } from './client';\nexport { ImpressionTracker } from './tracker';\nexport type { FireAndForgetPayload } from './client';\nexport type {\n FacetBucket,\n SearchFacets,\n SearchOptions,\n SearchResponse,\n SearchV2QueryConstraint,\n SearchV2ScoreBreakdown,\n SearchV2ItemScore,\n SearchV2Options,\n SearchV2Response,\n SearchV2AutocompleteResponse,\n SearchV2SettingsResponse,\n CatalogItem,\n CatalogListResponse,\n ListItemsOptions,\n} from '@recomnext/shared-search-types';\nexport type {\n BrowserClientConfig,\n RecommendedItem,\n RecommendationResult,\n ProjectionOptions,\n RecommendationOptions,\n RecommendOptions,\n PostProcessOverrideOptions,\n ImpressionTrackerOptions,\n} from './types';\n", "// Canonical JSON serializer \u2014 byte-identical with the engine, the Node\n// server SDK, and the Python SDK. See repo-root\n// `tests/auth/canonical-vectors.json` for the conformance gate.\n\nexport function canonicalJson(value: unknown): string {\n return stringify(value);\n}\n\nfunction stringify(value: unknown): string {\n if (value === null) return 'null';\n switch (typeof value) {\n case 'boolean':\n return value ? 'true' : 'false';\n case 'number':\n if (!Number.isFinite(value)) {\n throw new TypeError(\n `Cannot canonicalize non-finite number: ${String(value)}`,\n );\n }\n return JSON.stringify(value);\n case 'string':\n return JSON.stringify(value);\n case 'object': {\n if (Array.isArray(value)) {\n return '[' + value.map(stringify).join(',') + ']';\n }\n const keys = Object.keys(value as Record<string, unknown>).sort();\n const parts: string[] = [];\n for (const k of keys) {\n const v = (value as Record<string, unknown>)[k];\n if (typeof v === 'undefined') continue;\n parts.push(JSON.stringify(k) + ':' + stringify(v));\n }\n return '{' + parts.join(',') + '}';\n }\n case 'undefined':\n throw new TypeError('Cannot canonicalize undefined at the root');\n default:\n throw new TypeError(\n `Cannot canonicalize value of type ${typeof value}`,\n );\n }\n}\n", "// WebCrypto-based HMAC-SHA256 signing for browser-side recomnext callers.\n// Same wire format as the server SDK (sdks/node/src/signing.ts) and the\n// engine guard (engine/src/auth/guards/hmac.guard.ts).\n//\n// Public vs private token contract:\n// - PUBLIC token: shipped in JS bundles. Wire uses `frontend_sign` +\n// `frontend_timestamp` query params. Engine rejects unless the request\n// origin is on the tenant's allow-list AND the route is on the\n// public-write whitelist (or is a GET).\n// - PRIVATE token: NEVER ship to a browser. WebCrypto signing of private\n// tokens is provided here for completeness (e.g. for Node-via-bundler\n// consumers), but the recomnext browser SDK refuses to accept a\n// privateToken in its config.\n\nimport { canonicalJson } from './canonical-json.js';\n\nexport type SignerMode = 'public' | 'private';\n\nexport interface SignInput {\n method: string;\n // Path WITHOUT querystring or trailing slash.\n path: string;\n // Caller-supplied query params; values coerced to strings.\n query?: Record<string, string | number | boolean | undefined>;\n // Parsed body \u2014 undefined for GET/DELETE/HEAD/OPTIONS.\n body?: unknown;\n tenantId: string;\n token: string;\n mode: SignerMode;\n // Test-only clock override.\n now?: number;\n}\n\nexport interface SignedRequest {\n signedPath: string;\n timestamp: number;\n bodyHash: string;\n signature: string;\n idempotencyKey: string;\n}\n\n// Cached CryptoKey objects keyed by raw token bytes. importKey() is async\n// and non-trivial; pages calling sign() in a hot loop (e.g. mouse-move\n// telemetry) would otherwise pay the cost on every event.\nconst KEY_CACHE = new Map<string, Promise<CryptoKey>>();\n\nexport async function sign(input: SignInput): Promise<SignedRequest> {\n const verb = input.method.toUpperCase();\n const path = effectivePath(input.path);\n const timestamp = input.now ?? Math.floor(Date.now() / 1000);\n\n const bodyHash =\n verb === 'GET' || verb === 'DELETE' || verb === 'HEAD' || verb === 'OPTIONS'\n ? ''\n : await sha256Hex(canonicalJson(input.body ?? null));\n\n // Preserve empty-string values (e.g. filter= / itemsPerSegment= clear\n // sentinels for request-time overrides). Only drop undefined/null omit.\n // Must stay lock-step with engine HmacGuard + node/python signers.\n const others = new Map<string, string>();\n for (const [k, v] of Object.entries(input.query ?? {})) {\n if (v === undefined || v === null) continue;\n others.set(k, String(v));\n }\n others.delete('hmac_timestamp');\n others.delete('frontend_timestamp');\n others.delete('body_hash');\n others.delete('hmac_sign');\n others.delete('frontend_sign');\n const sortedOthers = Array.from(others.entries())\n .sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0))\n .map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(v)}`);\n\n const tsParam = input.mode === 'public' ? 'frontend_timestamp' : 'hmac_timestamp';\n const sigParam = input.mode === 'public' ? 'frontend_sign' : 'hmac_sign';\n\n const trailing = `${tsParam}=${timestamp}&body_hash=${bodyHash}`;\n const queryForSigning = sortedOthers.length\n ? `${sortedOthers.join('&')}&${trailing}`\n : trailing;\n\n const signingString = `${verb}\\n${path}?${queryForSigning}\\nX-Tenant-Id:${input.tenantId}`;\n const signature = await hmacSha256Hex(input.token, signingString);\n const signedPath = `${path}?${queryForSigning}&${sigParam}=${signature}`;\n\n return {\n signedPath,\n timestamp,\n bodyHash,\n signature,\n idempotencyKey: makeIdempotencyKey(),\n };\n}\n\nasync function sha256Hex(input: string): Promise<string> {\n const bytes = await crypto.subtle.digest(\n 'SHA-256',\n new TextEncoder().encode(input),\n );\n return toHex(new Uint8Array(bytes));\n}\n\nasync function hmacSha256Hex(token: string, payload: string): Promise<string> {\n const key = await getKey(token);\n const sig = await crypto.subtle.sign(\n 'HMAC',\n key,\n new TextEncoder().encode(payload),\n );\n return toHex(new Uint8Array(sig));\n}\n\nasync function getKey(token: string): Promise<CryptoKey> {\n let cached = KEY_CACHE.get(token);\n if (!cached) {\n cached = crypto.subtle.importKey(\n 'raw',\n new TextEncoder().encode(token),\n { name: 'HMAC', hash: 'SHA-256' },\n false,\n ['sign'],\n );\n KEY_CACHE.set(token, cached);\n }\n return cached;\n}\n\n// Preload the CryptoKey ahead of any sign() calls. Useful at SDK init time\n// so the first user interaction doesn't pay the importKey cost on the hot\n// path. Returns immediately on a second call for the same token.\nexport function preloadSignerKey(token: string): Promise<CryptoKey> {\n return getKey(token);\n}\n\nfunction toHex(bytes: Uint8Array): string {\n let out = '';\n for (let i = 0; i < bytes.length; i++) {\n out += bytes[i].toString(16).padStart(2, '0');\n }\n return out;\n}\n\nfunction effectivePath(path: string): string {\n const stripped = path.replace(/\\/$/, '');\n return stripped.length > 0 ? stripped : '/';\n}\n\nfunction makeIdempotencyKey(): string {\n const c = (globalThis as unknown as { crypto?: Crypto & { randomUUID?: () => string } }).crypto;\n if (c?.randomUUID) return c.randomUUID();\n // Fallback for older WebView shells without randomUUID.\n const rand = new Uint8Array(16);\n c?.getRandomValues(rand);\n rand[6] = (rand[6] & 0x0f) | 0x40;\n rand[8] = (rand[8] & 0x3f) | 0x80;\n const hex = toHex(rand);\n return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;\n}\n", "// Durable retry queue for browser fire-and-forget endpoints (impressions,\n// interactions). Per the auth-rollout plan:\n// - Synchronous retries (3 attempts, ~5s) for request-response calls\n// happen inline in the SDK; THIS file is exclusively for the\n// fire-and-forget endpoints (/ingestion/impressions,\n// /ingestion/interactions).\n// - `/identity/merge` is NEVER auto-retried (non-idempotent) \u2014 callers\n// must NOT enqueue merge requests via this class.\n// - Exponential backoff, capped at 10 minutes / 8 attempts. After the\n// cap, items are dropped and `onRetryDrop` is invoked for observability.\n// - Optional IndexedDB persistence via `persistRetries: true` so queued\n// items survive page reloads / device-sleep / network outages.\n//\n// Re-signing happens on EVERY attempt \u2014 the engine's replay cache rejects\n// the same signature twice, and the 30s signing window means a stale sig\n// is worse than no retry. The `signRequest` callback is invoked with the\n// item payload and must return a freshly-signed URL + idempotency key.\n\nexport interface QueuedItem<TPayload = unknown> {\n // Unique id used for dedupe / persistence. Auto-generated when not set.\n id?: string;\n // Logical path the item targets (used for routing on flush).\n path: string;\n // HTTP method. Currently every fire-and-forget endpoint is POST; the\n // field exists so the queue can also handle PUTs if needed later.\n method: string;\n // Payload sent as the JSON body. Must be canonical-JSON serializable.\n payload: TPayload;\n // Number of attempts so far (0 on first enqueue). The queue increments.\n attempts?: number;\n // Earliest wall-clock ms at which the next attempt should run. Updated\n // each time we back off.\n nextAttemptAt?: number;\n}\n\nexport interface RetryQueueConfig<TPayload = unknown> {\n // Persist queued items across page reloads. Disabled by default \u2014 the\n // demo / read-mostly integrations don't need it. Enabling pulls in an\n // IndexedDB-backed `idb-keyval`-style adapter (see indexed-db.ts).\n persist?: boolean;\n // Storage key for IndexedDB persistence (so multi-tenant pages don't\n // share queue state). Required when `persist: true`.\n persistKey?: string;\n // Maximum attempts before the item is dropped. Default 8.\n maxAttempts?: number;\n // Initial backoff in ms. Default 1000.\n initialBackoffMs?: number;\n // Backoff cap in ms. Default 600_000 (10m).\n maxBackoffMs?: number;\n // Periodic drain interval in ms. Default 2000.\n flushIntervalMs?: number;\n // Called for every item dropped after exhausting attempts. Used for\n // observability \u2014 log to Sentry / surface as a tracker event.\n onRetryDrop?: (item: QueuedItem<TPayload>, error: unknown) => void;\n // Signs each attempt's request fresh. Returns the absolute URL to POST\n // to plus any auth headers (X-Idempotency-Key, etc.). Must NOT cache\n // its return value \u2014 the queue calls this once per attempt.\n signRequest: (item: QueuedItem<TPayload>) => Promise<{\n url: string;\n headers: Record<string, string>;\n }>;\n}\n\nconst DEFAULTS = {\n maxAttempts: 8,\n initialBackoffMs: 1000,\n maxBackoffMs: 600_000,\n flushIntervalMs: 2000,\n};\n\nexport class RetryQueue<TPayload = unknown> {\n private readonly cfg: Required<\n Omit<RetryQueueConfig<TPayload>, 'persist' | 'persistKey' | 'onRetryDrop'>\n > & {\n persist: boolean;\n persistKey?: string;\n onRetryDrop?: RetryQueueConfig<TPayload>['onRetryDrop'];\n };\n private items: QueuedItem<TPayload>[] = [];\n private timer: ReturnType<typeof setInterval> | null = null;\n private draining = false;\n private idb?: Promise<IDBDatabase | null>;\n\n constructor(cfg: RetryQueueConfig<TPayload>) {\n this.cfg = {\n persist: cfg.persist ?? false,\n persistKey: cfg.persistKey,\n maxAttempts: cfg.maxAttempts ?? DEFAULTS.maxAttempts,\n initialBackoffMs: cfg.initialBackoffMs ?? DEFAULTS.initialBackoffMs,\n maxBackoffMs: cfg.maxBackoffMs ?? DEFAULTS.maxBackoffMs,\n flushIntervalMs: cfg.flushIntervalMs ?? DEFAULTS.flushIntervalMs,\n onRetryDrop: cfg.onRetryDrop,\n signRequest: cfg.signRequest,\n };\n\n if (this.cfg.persist) {\n if (!this.cfg.persistKey) {\n throw new Error('RetryQueue: persistKey required when persist=true');\n }\n this.idb = openIndexedDb();\n void this.loadPersisted();\n }\n\n if (typeof window !== 'undefined') {\n this.timer = setInterval(() => void this.drain(), this.cfg.flushIntervalMs);\n window.addEventListener('online', () => void this.drain());\n // Best-effort flush on page hide \u2014 sendBeacon is the documented\n // companion for items we never want to drop.\n window.addEventListener('pagehide', () => void this.drain());\n }\n }\n\n enqueue(item: QueuedItem<TPayload>): void {\n const enriched: QueuedItem<TPayload> = {\n ...item,\n id: item.id ?? makeId(),\n attempts: item.attempts ?? 0,\n nextAttemptAt: item.nextAttemptAt ?? Date.now(),\n };\n this.items.push(enriched);\n void this.persist();\n // Eager drain attempt \u2014 useful when the page is interactive and the\n // queued event should leave the page within a hundred ms or two.\n void this.drain();\n }\n\n // Manually trigger a drain cycle. Used by callers on critical pageviews\n // (e.g. checkout) where dropping the next backoff window matters.\n flush(): Promise<void> {\n return this.drain();\n }\n\n destroy(): void {\n if (this.timer) clearInterval(this.timer);\n this.timer = null;\n }\n\n private async drain(): Promise<void> {\n if (this.draining) return;\n if (typeof navigator !== 'undefined' && navigator.onLine === false) return;\n this.draining = true;\n try {\n const now = Date.now();\n const ready = this.items.filter((i) => (i.nextAttemptAt ?? 0) <= now);\n for (const item of ready) {\n await this.attempt(item);\n }\n await this.persist();\n } finally {\n this.draining = false;\n }\n }\n\n private async attempt(item: QueuedItem<TPayload>): Promise<void> {\n item.attempts = (item.attempts ?? 0) + 1;\n try {\n const { url, headers } = await this.cfg.signRequest(item);\n const res = await fetch(url, {\n method: item.method,\n headers: {\n 'Content-Type': 'application/json',\n ...headers,\n },\n body: JSON.stringify(item.payload),\n keepalive: true,\n });\n if (res.ok) {\n this.removeById(item.id!);\n return;\n }\n // 4xx other than 429 is permanent \u2014 no amount of retry fixes a bad\n // payload. Drop immediately so we don't keep poisoning the queue.\n if (res.status >= 400 && res.status < 500 && res.status !== 429) {\n this.removeById(item.id!);\n this.cfg.onRetryDrop?.(item, new Error(`HTTP ${res.status}`));\n return;\n }\n this.scheduleRetry(item, new Error(`HTTP ${res.status}`));\n } catch (err) {\n this.scheduleRetry(item, err);\n }\n }\n\n private scheduleRetry(item: QueuedItem<TPayload>, err: unknown): void {\n if ((item.attempts ?? 0) >= this.cfg.maxAttempts) {\n this.removeById(item.id!);\n this.cfg.onRetryDrop?.(item, err);\n return;\n }\n const exp = Math.min(\n this.cfg.maxBackoffMs,\n this.cfg.initialBackoffMs * Math.pow(2, (item.attempts ?? 1) - 1),\n );\n // Decorrelated jitter \u2014 full backoff with a uniformly-distributed\n // multiplier in [0.5, 1.0) so multiple replicas don't sync into a\n // thundering herd when the backend recovers.\n const jitter = 0.5 + Math.random() * 0.5;\n item.nextAttemptAt = Date.now() + Math.floor(exp * jitter);\n }\n\n private removeById(id: string): void {\n this.items = this.items.filter((i) => i.id !== id);\n }\n\n // \u2500\u2500 IndexedDB persistence \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n private async persist(): Promise<void> {\n if (!this.cfg.persist || !this.idb || !this.cfg.persistKey) return;\n try {\n const db = await this.idb;\n if (!db) return;\n const tx = db.transaction('queue', 'readwrite');\n const store = tx.objectStore('queue');\n store.put(JSON.parse(JSON.stringify(this.items)), this.cfg.persistKey);\n await txDone(tx);\n } catch {\n // Persistence is best-effort \u2014 never throw out of an enqueue path.\n }\n }\n\n private async loadPersisted(): Promise<void> {\n if (!this.cfg.persist || !this.idb || !this.cfg.persistKey) return;\n try {\n const db = await this.idb;\n if (!db) return;\n const tx = db.transaction('queue', 'readonly');\n const store = tx.objectStore('queue');\n const req = store.get(this.cfg.persistKey);\n const data: unknown = await new Promise((resolve, reject) => {\n req.onsuccess = () => resolve(req.result);\n req.onerror = () => reject(req.error);\n });\n if (Array.isArray(data)) {\n this.items = data as QueuedItem<TPayload>[];\n void this.drain();\n }\n } catch {\n // Lost-persistence is silent by design \u2014 don't break enqueue on\n // private-browsing / disabled-storage.\n }\n }\n}\n\nfunction makeId(): string {\n const c = (globalThis as unknown as { crypto?: Crypto & { randomUUID?: () => string } }).crypto;\n if (c?.randomUUID) return c.randomUUID();\n return `rnx-${Date.now()}-${Math.random().toString(36).slice(2, 11)}`;\n}\n\nfunction openIndexedDb(): Promise<IDBDatabase | null> {\n if (typeof indexedDB === 'undefined') return Promise.resolve(null);\n return new Promise((resolve) => {\n const req = indexedDB.open('recomnext-retry-queue', 1);\n req.onupgradeneeded = () => {\n const db = req.result;\n if (!db.objectStoreNames.contains('queue')) db.createObjectStore('queue');\n };\n req.onsuccess = () => resolve(req.result);\n req.onerror = () => resolve(null);\n req.onblocked = () => resolve(null);\n });\n}\n\nfunction txDone(tx: IDBTransaction): Promise<void> {\n return new Promise((resolve, reject) => {\n tx.oncomplete = () => resolve();\n tx.onerror = () => reject(tx.error);\n tx.onabort = () => reject(tx.error);\n });\n}\n", "import type { RetryQueue } from '@recomnext/shared-browser-auth';\nimport type { FireAndForgetPayload } from './client';\nimport type { ImpressionTrackerOptions } from './types';\n\nconst FLUSH_INTERVAL = 2000;\n\nexport class ImpressionTracker {\n private queue: { itemId: string; scenario: string }[] = [];\n private flushing = false;\n private observer: IntersectionObserver | null = null;\n private seen = new Set<string>();\n private timer: ReturnType<typeof setInterval> | null = null;\n private readonly getUserId: () => string;\n private readonly getSessionId: () => string;\n // The RetryQueue handles all signed POSTs to /ingestion/impressions\n // (single source of truth for fire-and-forget on this page). We still\n // batch impressions every 2s before enqueuing so we don't fan out N\n // signed requests for one viewport scan.\n private readonly retryQueue: RetryQueue<FireAndForgetPayload>;\n\n constructor(\n _baseUrl: string,\n _tenantId: string,\n getUserId: () => string,\n getSessionId: () => string,\n retryQueue: RetryQueue<FireAndForgetPayload>,\n ) {\n // baseUrl + tenantId are captured by the client during signRequest()\n // and no longer needed at the tracker level. Kept in the signature\n // for backwards compatibility with anyone constructing the tracker\n // directly (rare \u2014 almost everyone uses RecomnextBrowserClient).\n this.getUserId = getUserId;\n this.getSessionId = getSessionId;\n this.retryQueue = retryQueue;\n if (typeof window !== 'undefined') {\n this.timer = setInterval(() => this.flush(), FLUSH_INTERVAL);\n // sendBeacon is best-effort and CANNOT carry the X-Idempotency-Key\n // header (it's a Blob POST). On pagehide we therefore force the\n // queue's normal signed-fetch path to drain via `keepalive: true`,\n // which is now widely supported in all evergreen browsers.\n window.addEventListener('pagehide', () => this.flush(true));\n }\n }\n\n observe(container: Element, opts: ImpressionTrackerOptions = {}): () => void {\n const selector = opts.itemSelector ?? '[data-item-id]';\n const threshold = opts.threshold ?? 0.5;\n const once = opts.once !== false;\n const scenario = opts.scenario ?? '';\n\n this.observer = new IntersectionObserver(\n (entries) => {\n for (const entry of entries) {\n if (!entry.isIntersecting) continue;\n const el = entry.target as HTMLElement;\n const itemId = el.dataset.itemId;\n if (!itemId) continue;\n const key = `${scenario}:${itemId}`;\n if (once && this.seen.has(key)) continue;\n this.seen.add(key);\n this.queue.push({ itemId, scenario });\n if (once) this.observer?.unobserve(el);\n }\n },\n { root: null, threshold },\n );\n\n const elements = container.querySelectorAll(selector);\n elements.forEach((el) => this.observer!.observe(el));\n\n return () => {\n this.observer?.disconnect();\n this.observer = null;\n };\n }\n\n // `urgent` is set during pagehide. When true we kick the RetryQueue\n // immediately so it has a chance to land items inside the unload window\n // (keepalive: true on the inner fetch keeps the connection alive past\n // the navigation).\n private flush(urgent = false): void {\n if (this.flushing || this.queue.length === 0) return;\n this.flushing = true;\n const batch = this.queue.splice(0, this.queue.length);\n\n const grouped = new Map<string, string[]>();\n for (const { itemId, scenario } of batch) {\n if (!grouped.has(scenario)) grouped.set(scenario, []);\n grouped.get(scenario)!.push(itemId);\n }\n\n const impressions = Array.from(grouped.entries()).map(([scenarioSlug, itemIds]) => ({\n userId: this.getUserId() || undefined,\n sessionId: this.getSessionId() || undefined,\n itemIds,\n scenarioSlug: scenarioSlug || undefined,\n timestamp: new Date().toISOString(),\n }));\n\n this.retryQueue.enqueue({\n path: '/ingestion/impressions',\n method: 'POST',\n payload: { impressions },\n });\n\n if (urgent) void this.retryQueue.flush();\n this.flushing = false;\n }\n\n destroy(): void {\n this.flush();\n this.observer?.disconnect();\n if (this.timer) clearInterval(this.timer);\n }\n}\n", "import {\n preloadSignerKey,\n RetryQueue,\n sign as signRequest,\n type QueuedItem,\n} from '@recomnext/shared-browser-auth';\n\nimport type {\n SearchOptions,\n SearchResponse,\n SearchV2Options,\n SearchV2Response,\n SearchV2AutocompleteResponse,\n SearchV2SettingsResponse,\n CatalogItem,\n CatalogListResponse,\n ListItemsOptions,\n} from '@recomnext/shared-search-types';\n\nimport { ImpressionTracker } from './tracker';\nimport type {\n BrowserClientConfig,\n RecommendationResult,\n RecommendationOptions,\n RecommendOptions,\n ImpressionTrackerOptions,\n} from './types';\n\nconst SESSION_KEY = 'recomnext_session_id';\n\n// Synchronous retry budget for request-response calls (recommendations,\n// catalog reads, identity/merge). Mirrors the Node + Python SDK behavior:\n// 3 attempts total, ~5s aggregate, re-signs on every attempt.\nconst SYNC_RETRIES = 3;\nconst SYNC_INITIAL_BACKOFF_MS = 200;\nconst SYNC_MAX_BACKOFF_MS = 2_000;\n\n// 5xx + 429 are auto-retried. We special-case 503 +\n// errorCode=auth_replay_cache_unavailable so the SDK retries even if an\n// operator tunes 503 out of the retry set.\nconst RETRYABLE_STATUS = new Set([429, 500, 502, 503, 504]);\nconst AUTH_REPLAY_CACHE_UNAVAILABLE = 'auth_replay_cache_unavailable';\n\n// `/identity/merge` mutates the identity graph on every call. The plan\n// locks this to single-shot until the engine grows server-side\n// idempotency \u2014 never auto-retry, even on a transient 503.\nconst NEVER_RETRY_PATHS = new Set(['/identity/merge']);\n\nexport class RecomnextBrowserClient {\n private readonly baseUrl: string;\n private readonly tenantId: string;\n private readonly publicToken: string | undefined;\n private userId: string;\n private sessionId: string;\n private readonly tracker: ImpressionTracker;\n // Lazy \u2014 only constructed once for fire-and-forget queues that the\n // tracker also reuses.\n readonly fireAndForgetQueue: RetryQueue<FireAndForgetPayload>;\n\n constructor(config: BrowserClientConfig) {\n this.baseUrl = config.baseUrl.replace(/\\/+$/, '');\n this.tenantId = config.tenantId;\n this.publicToken = config.publicToken;\n this.userId = config.userId ?? '';\n this.sessionId = config.sessionId ?? this.resolveSessionId();\n\n // Eagerly import the CryptoKey so the first signed event after page\n // load doesn't pay the importKey latency on the hot path.\n if (this.publicToken) void preloadSignerKey(this.publicToken);\n\n this.fireAndForgetQueue = new RetryQueue<FireAndForgetPayload>({\n persist: config.persistRetries ?? false,\n persistKey: `rnx:${this.tenantId}:fnf`,\n onRetryDrop: config.onRetryDrop,\n signRequest: (item) => this.signFireAndForget(item),\n });\n\n this.tracker = new ImpressionTracker(\n this.baseUrl,\n this.tenantId,\n () => this.userId,\n () => this.sessionId,\n this.fireAndForgetQueue,\n );\n }\n\n private resolveSessionId(): string {\n if (typeof sessionStorage === 'undefined') return '';\n let sid = sessionStorage.getItem(SESSION_KEY);\n if (!sid) {\n sid = `sess_${Date.now()}_${Math.random().toString(36).slice(2, 9)}`;\n sessionStorage.setItem(SESSION_KEY, sid);\n }\n return sid;\n }\n\n private effectiveUserId(): string {\n return this.userId || this.sessionId;\n }\n\n private async request<T>(\n method: string,\n path: string,\n body?: unknown,\n query?: Record<string, string | number | boolean | undefined>,\n ): Promise<T> {\n const maxRetries = NEVER_RETRY_PATHS.has(path) ? 0 : SYNC_RETRIES;\n let lastErr: unknown;\n\n for (let attempt = 0; attempt <= maxRetries; attempt++) {\n if (attempt > 0) {\n const exp = Math.min(\n SYNC_MAX_BACKOFF_MS,\n SYNC_INITIAL_BACKOFF_MS * Math.pow(2, attempt - 1),\n );\n const jitter = 0.5 + Math.random() * 0.5;\n await sleep(Math.floor(exp * jitter));\n }\n\n // Always re-sign \u2014 the engine's replay cache rejects the same\n // signature twice, and the 30s signing window means a stale sig\n // would also fail.\n const { url, headers } = await this.buildSignedRequest(\n method,\n path,\n query,\n body,\n );\n\n let res: Response;\n try {\n res = await fetch(url, {\n method,\n headers: {\n 'Content-Type': 'application/json',\n ...headers,\n },\n body: body !== undefined ? JSON.stringify(body) : undefined,\n });\n } catch (err) {\n lastErr = err;\n if (attempt >= maxRetries) throw err;\n continue;\n }\n\n if (res.status === 503) {\n const code = await extractErrorCode(res.clone());\n if (code === AUTH_REPLAY_CACHE_UNAVAILABLE && attempt < maxRetries) {\n lastErr = new Error(`recomnext: ${method} ${path} ${res.status} ${code}`);\n continue;\n }\n }\n\n if (RETRYABLE_STATUS.has(res.status) && attempt < maxRetries) {\n lastErr = new Error(`recomnext: ${method} ${path} returned ${res.status}`);\n continue;\n }\n\n if (!res.ok) {\n const text = await res.text().catch(() => '');\n throw new Error(`recomnext: ${method} ${path} returned ${res.status}: ${text}`);\n }\n\n if (res.status === 204) return undefined as unknown as T;\n return res.json() as Promise<T>;\n }\n\n throw lastErr ?? new Error(`recomnext: ${method} ${path} retries exhausted`);\n }\n\n // Same shape as the RetryQueue's signRequest contract \u2014 used by the\n // queue *and* the impression tracker so both share a single signing\n // path.\n async signFireAndForget(item: QueuedItem<FireAndForgetPayload>) {\n const { url, headers } = await this.buildSignedRequest(\n item.method,\n item.path,\n undefined,\n item.payload,\n );\n return { url, headers };\n }\n\n private async buildSignedRequest(\n method: string,\n path: string,\n query: Record<string, string | number | boolean | undefined> | undefined,\n body: unknown,\n ): Promise<{ url: string; headers: Record<string, string> }> {\n const baseHeaders: Record<string, string> = {\n 'X-Tenant-Id': this.tenantId,\n };\n\n if (!this.publicToken) {\n // Legacy / no-auth mode. Engine accepts because\n // LEGACY_TENANT_AUTH=true. After Phase 7 cutover this path will be\n // rejected with 401.\n const qs = renderQs(query);\n return { url: `${this.baseUrl}${path}${qs}`, headers: baseHeaders };\n }\n\n const signed = await signRequest({\n method,\n path,\n query,\n body: body ?? null,\n tenantId: this.tenantId,\n token: this.publicToken,\n mode: 'public',\n });\n return {\n url: `${this.baseUrl}${signed.signedPath}`,\n headers: {\n ...baseHeaders,\n 'X-Idempotency-Key': signed.idempotencyKey,\n },\n };\n }\n\n private qs(\n params: Record<string, string | number | boolean | undefined>,\n ): Record<string, string | number | boolean | undefined> {\n return params;\n }\n\n private includeQuery(opts?: RecommendationOptions): {\n returnProperties?: boolean;\n includedProperties?: string;\n } {\n return {\n returnProperties: opts?.returnProperties,\n includedProperties:\n opts?.includedProperties && opts.includedProperties.length > 0\n ? opts.includedProperties.join(',')\n : undefined,\n };\n }\n\n /** Serialize override fields for GET query (comma-separated lists; '' clears). */\n private overrideQuery(opts?: RecommendationOptions): {\n filter?: string;\n boosters?: string;\n rulesSlugs?: string;\n segmentationId?: string;\n itemsPerSegment?: number | string;\n } {\n if (!opts) return {};\n const join = (v: string[] | null | undefined): string | undefined => {\n if (v === undefined) return undefined;\n if (v === null || v.length === 0) return '';\n return v.join(',');\n };\n return {\n filter:\n opts.filter === undefined\n ? undefined\n : opts.filter === null\n ? ''\n : opts.filter,\n boosters: join(opts.boosters),\n rulesSlugs: join(opts.rulesSlugs),\n segmentationId:\n opts.segmentationId === undefined\n ? undefined\n : opts.segmentationId === null\n ? ''\n : opts.segmentationId,\n // Present null \u2192 '' so the query param is sent; engine transform maps\n // '' \u2192 null \u2192 resolve to itemsPerSegment=1 (omit would keep scenario).\n itemsPerSegment:\n opts.itemsPerSegment === undefined\n ? undefined\n : opts.itemsPerSegment === null\n ? ''\n : opts.itemsPerSegment,\n };\n }\n\n private overrideBody(opts?: RecommendationOptions): {\n filter?: string | null;\n boosters?: string[] | null;\n rulesSlugs?: string[] | null;\n segmentationId?: string | null;\n itemsPerSegment?: number | null;\n } {\n if (!opts) return {};\n const body: {\n filter?: string | null;\n boosters?: string[] | null;\n rulesSlugs?: string[] | null;\n segmentationId?: string | null;\n itemsPerSegment?: number | null;\n } = {};\n if (opts.filter !== undefined) body.filter = opts.filter;\n if (opts.boosters !== undefined) body.boosters = opts.boosters;\n if (opts.rulesSlugs !== undefined) body.rulesSlugs = opts.rulesSlugs;\n if (opts.segmentationId !== undefined) {\n body.segmentationId = opts.segmentationId;\n }\n if (opts.itemsPerSegment !== undefined) {\n body.itemsPerSegment = opts.itemsPerSegment;\n }\n return body;\n }\n\n /* \u2500\u2500 Recommendations \u2500\u2500 */\n\n getRecommendations(opts?: RecommendationOptions): Promise<RecommendationResult> {\n return this.request(\n 'GET',\n `/recommendations/user-to-item/${this.effectiveUserId()}`,\n undefined,\n this.qs({\n count: opts?.count,\n scenario: opts?.scenario,\n ...this.includeQuery(opts),\n ...this.overrideQuery(opts),\n }),\n );\n }\n\n getRelatedItems(itemId: string, opts?: RecommendationOptions): Promise<RecommendationResult> {\n return this.request(\n 'GET',\n `/recommendations/item-to-item/${itemId}`,\n undefined,\n this.qs({\n count: opts?.count,\n scenario: opts?.scenario,\n ...this.includeQuery(opts),\n ...this.overrideQuery(opts),\n }),\n );\n }\n\n getSimilarProducts(itemId: string, opts?: RecommendationOptions): Promise<RecommendationResult> {\n return this.request(\n 'GET',\n `/recommendations/similar-products/${itemId}`,\n undefined,\n this.qs({\n count: opts?.count,\n scenario: opts?.scenario,\n ...this.includeQuery(opts),\n ...this.overrideQuery(opts),\n }),\n );\n }\n\n getRecentlyViewed(opts?: RecommendationOptions): Promise<RecommendationResult> {\n return this.request(\n 'GET',\n `/recommendations/recently-viewed/${this.effectiveUserId()}`,\n undefined,\n this.qs({\n count: opts?.count,\n scenario: opts?.scenario,\n ...this.includeQuery(opts),\n ...this.overrideQuery(opts),\n }),\n );\n }\n\n getCartRecommendations(itemIds: string[], opts?: RecommendationOptions): Promise<RecommendationResult> {\n return this.request(\n 'POST',\n '/recommendations/items-to-items',\n {\n itemIds,\n count: opts?.count ?? 10,\n returnProperties: opts?.returnProperties,\n includedProperties: opts?.includedProperties,\n ...this.overrideBody(opts),\n },\n this.qs({ scenario: opts?.scenario }),\n );\n }\n\n recommend(opts: RecommendOptions): Promise<RecommendationResult> {\n return this.request('POST', '/recommendations', {\n ...opts,\n userId: this.effectiveUserId(),\n });\n }\n\n /* \u2500\u2500 Search \u2500\u2500 */\n\n search(query: string, opts?: SearchOptions): Promise<SearchResponse> {\n return this.request(\n 'GET',\n '/recommendations/search',\n undefined,\n this.qs({\n query,\n count: opts?.count,\n category: opts?.category,\n priceRange: opts?.priceRange,\n userId: opts?.userId ?? (this.userId || undefined),\n }),\n );\n }\n\n searchV2(query: string, opts?: SearchV2Options): Promise<SearchV2Response> {\n return this.request(\n 'GET',\n '/recommendations/search/v2',\n undefined,\n this.qs({\n query,\n count: opts?.count,\n category: opts?.category,\n priceRange: opts?.priceRange,\n userId: opts?.userId ?? (this.userId || undefined),\n locale: opts?.locale,\n debugScores: opts?.debugScores === true ? true : undefined,\n algorithm: opts?.algorithm,\n }),\n );\n }\n\n autocomplete(\n query: string,\n opts?: { limit?: number },\n ): Promise<SearchV2AutocompleteResponse> {\n return this.request(\n 'GET',\n '/recommendations/search/v2/autocomplete',\n undefined,\n this.qs({\n query,\n limit: opts?.limit,\n }),\n );\n }\n\n getSearchV2Settings(): Promise<SearchV2SettingsResponse> {\n return this.request('GET', '/recommendations/search/v2/settings');\n }\n\n /* \u2500\u2500 Catalog browse (public HMAC eligible) \u2500\u2500 */\n\n listItems(opts?: ListItemsOptions): Promise<CatalogListResponse> {\n return this.request(\n 'GET',\n '/catalog/items',\n undefined,\n this.qs({\n page: opts?.page,\n limit: opts?.limit,\n category: opts?.category,\n search: opts?.search,\n availability: opts?.availability,\n }),\n );\n }\n\n getCategories(): Promise<string[]> {\n return this.request('GET', '/catalog/items/categories');\n }\n\n getItem(externalId: string): Promise<CatalogItem> {\n return this.request('GET', `/catalog/items/${encodeURIComponent(externalId)}`);\n }\n\n /* \u2500\u2500 Event tracking \u2500\u2500 */\n\n trackView(itemId: string, scenarioSlug?: string): void {\n this.fireAndForgetQueue.enqueue({\n path: '/ingestion/interactions',\n method: 'POST',\n payload: {\n interactions: [{ userId: this.effectiveUserId(), itemId, type: 'view', scenarioSlug: scenarioSlug || undefined, timestamp: new Date().toISOString() }],\n },\n });\n }\n\n trackCart(itemId: string, scenarioSlug?: string): void {\n this.fireAndForgetQueue.enqueue({\n path: '/ingestion/interactions',\n method: 'POST',\n payload: {\n interactions: [{ userId: this.effectiveUserId(), itemId, type: 'cart', scenarioSlug: scenarioSlug || undefined, timestamp: new Date().toISOString() }],\n },\n });\n }\n\n trackPurchase(itemId: string, scenarioSlug?: string): void {\n this.fireAndForgetQueue.enqueue({\n path: '/ingestion/interactions',\n method: 'POST',\n payload: {\n interactions: [{ userId: this.effectiveUserId(), itemId, type: 'purchase', scenarioSlug: scenarioSlug || undefined, timestamp: new Date().toISOString() }],\n },\n });\n }\n\n /* \u2500\u2500 Impression tracking \u2500\u2500 */\n\n trackImpressions(container: Element, opts?: ImpressionTrackerOptions): () => void {\n return this.tracker.observe(container, opts);\n }\n\n /* \u2500\u2500 Identity \u2500\u2500 */\n\n setUserId(userId: string): void {\n this.userId = userId;\n }\n\n mergeIdentity(authenticatedUserId: string): Promise<unknown> {\n const anonId = this.sessionId;\n this.userId = authenticatedUserId;\n // Note: never auto-retried (see NEVER_RETRY_PATHS) \u2014 callers should\n // handle 5xx/network failures themselves to avoid duplicate merges.\n return this.request('POST', '/identity/merge', {\n userId: authenticatedUserId,\n sessionIds: [anonId],\n });\n }\n\n /* \u2500\u2500 Lifecycle \u2500\u2500 */\n\n destroy(): void {\n this.tracker.destroy();\n this.fireAndForgetQueue.destroy();\n }\n}\n\n// Shape used by both the SDK's trackView/trackCart/trackPurchase calls\n// and the ImpressionTracker \u2014 they all funnel through the same queue.\nexport type FireAndForgetPayload = {\n interactions?: unknown[];\n impressions?: unknown[];\n};\n\nfunction renderQs(params?: Record<string, string | number | boolean | undefined>): string {\n if (!params) return '';\n const parts: string[] = [];\n for (const [k, v] of Object.entries(params)) {\n // Preserve empty string (GET clear sentinels). Drop only omit/null.\n if (v === undefined || v === null) continue;\n parts.push(`${encodeURIComponent(k)}=${encodeURIComponent(String(v))}`);\n }\n return parts.length ? `?${parts.join('&')}` : '';\n}\n\nasync function extractErrorCode(res: Response): Promise<string | undefined> {\n try {\n const body = (await res.json()) as { errorCode?: unknown };\n return typeof body.errorCode === 'string' ? body.errorCode : undefined;\n } catch {\n return undefined;\n }\n}\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((r) => setTimeout(r, ms));\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["export { RecomnextBrowserClient } from './client';\nexport { ImpressionTracker } from './tracker';\nexport type { FireAndForgetPayload } from './client';\nexport type {\n FacetBucket,\n SearchFacets,\n SearchOptions,\n SearchResponse,\n SearchV2QueryConstraint,\n SearchV2ScoreBreakdown,\n SearchV2ItemScore,\n SearchV2Options,\n SearchV2Response,\n SearchV2AutocompleteResponse,\n SearchV2SettingsResponse,\n CatalogItem,\n CatalogListResponse,\n ListItemsOptions,\n} from '@recomnext/shared-search-types';\nexport type {\n BrowserClientConfig,\n RecommendedItem,\n RecommendationResult,\n FilterRelaxation,\n ProjectionOptions,\n RecommendationOptions,\n RecommendOptions,\n PostProcessOverrideOptions,\n ImpressionTrackerOptions,\n} from './types';\n", "// Canonical JSON serializer \u2014 byte-identical with the engine, the Node\n// server SDK, and the Python SDK. See repo-root\n// `tests/auth/canonical-vectors.json` for the conformance gate.\n\nexport function canonicalJson(value: unknown): string {\n return stringify(value);\n}\n\nfunction stringify(value: unknown): string {\n if (value === null) return 'null';\n switch (typeof value) {\n case 'boolean':\n return value ? 'true' : 'false';\n case 'number':\n if (!Number.isFinite(value)) {\n throw new TypeError(\n `Cannot canonicalize non-finite number: ${String(value)}`,\n );\n }\n return JSON.stringify(value);\n case 'string':\n return JSON.stringify(value);\n case 'object': {\n if (Array.isArray(value)) {\n return '[' + value.map(stringify).join(',') + ']';\n }\n const keys = Object.keys(value as Record<string, unknown>).sort();\n const parts: string[] = [];\n for (const k of keys) {\n const v = (value as Record<string, unknown>)[k];\n if (typeof v === 'undefined') continue;\n parts.push(JSON.stringify(k) + ':' + stringify(v));\n }\n return '{' + parts.join(',') + '}';\n }\n case 'undefined':\n throw new TypeError('Cannot canonicalize undefined at the root');\n default:\n throw new TypeError(\n `Cannot canonicalize value of type ${typeof value}`,\n );\n }\n}\n", "// WebCrypto-based HMAC-SHA256 signing for browser-side recomnext callers.\n// Same wire format as the server SDK (sdks/node/src/signing.ts) and the\n// engine guard (engine/src/auth/guards/hmac.guard.ts).\n//\n// Public vs private token contract:\n// - PUBLIC token: shipped in JS bundles. Wire uses `frontend_sign` +\n// `frontend_timestamp` query params. Engine rejects unless the request\n// origin is on the tenant's allow-list AND the route is on the\n// public-write whitelist (or is a GET).\n// - PRIVATE token: NEVER ship to a browser. WebCrypto signing of private\n// tokens is provided here for completeness (e.g. for Node-via-bundler\n// consumers), but the recomnext browser SDK refuses to accept a\n// privateToken in its config.\n\nimport { canonicalJson } from './canonical-json.js';\n\nexport type SignerMode = 'public' | 'private';\n\nexport interface SignInput {\n method: string;\n // Path WITHOUT querystring or trailing slash.\n path: string;\n // Caller-supplied query params; values coerced to strings.\n query?: Record<string, string | number | boolean | undefined>;\n // Parsed body \u2014 undefined for GET/DELETE/HEAD/OPTIONS.\n body?: unknown;\n tenantId: string;\n token: string;\n mode: SignerMode;\n // Test-only clock override.\n now?: number;\n}\n\nexport interface SignedRequest {\n signedPath: string;\n timestamp: number;\n bodyHash: string;\n signature: string;\n idempotencyKey: string;\n}\n\n// Cached CryptoKey objects keyed by raw token bytes. importKey() is async\n// and non-trivial; pages calling sign() in a hot loop (e.g. mouse-move\n// telemetry) would otherwise pay the cost on every event.\nconst KEY_CACHE = new Map<string, Promise<CryptoKey>>();\n\nexport async function sign(input: SignInput): Promise<SignedRequest> {\n const verb = input.method.toUpperCase();\n const path = effectivePath(input.path);\n const timestamp = input.now ?? Math.floor(Date.now() / 1000);\n\n const bodyHash =\n verb === 'GET' || verb === 'DELETE' || verb === 'HEAD' || verb === 'OPTIONS'\n ? ''\n : await sha256Hex(canonicalJson(input.body ?? null));\n\n // Preserve empty-string values (e.g. filter= / itemsPerSegment= clear\n // sentinels for request-time overrides). Only drop undefined/null omit.\n // Must stay lock-step with engine HmacGuard + node/python signers.\n const others = new Map<string, string>();\n for (const [k, v] of Object.entries(input.query ?? {})) {\n if (v === undefined || v === null) continue;\n others.set(k, String(v));\n }\n others.delete('hmac_timestamp');\n others.delete('frontend_timestamp');\n others.delete('body_hash');\n others.delete('hmac_sign');\n others.delete('frontend_sign');\n const sortedOthers = Array.from(others.entries())\n .sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0))\n .map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(v)}`);\n\n const tsParam = input.mode === 'public' ? 'frontend_timestamp' : 'hmac_timestamp';\n const sigParam = input.mode === 'public' ? 'frontend_sign' : 'hmac_sign';\n\n const trailing = `${tsParam}=${timestamp}&body_hash=${bodyHash}`;\n const queryForSigning = sortedOthers.length\n ? `${sortedOthers.join('&')}&${trailing}`\n : trailing;\n\n const signingString = `${verb}\\n${path}?${queryForSigning}\\nX-Tenant-Id:${input.tenantId}`;\n const signature = await hmacSha256Hex(input.token, signingString);\n const signedPath = `${path}?${queryForSigning}&${sigParam}=${signature}`;\n\n return {\n signedPath,\n timestamp,\n bodyHash,\n signature,\n idempotencyKey: makeIdempotencyKey(),\n };\n}\n\nasync function sha256Hex(input: string): Promise<string> {\n const bytes = await crypto.subtle.digest(\n 'SHA-256',\n new TextEncoder().encode(input),\n );\n return toHex(new Uint8Array(bytes));\n}\n\nasync function hmacSha256Hex(token: string, payload: string): Promise<string> {\n const key = await getKey(token);\n const sig = await crypto.subtle.sign(\n 'HMAC',\n key,\n new TextEncoder().encode(payload),\n );\n return toHex(new Uint8Array(sig));\n}\n\nasync function getKey(token: string): Promise<CryptoKey> {\n let cached = KEY_CACHE.get(token);\n if (!cached) {\n cached = crypto.subtle.importKey(\n 'raw',\n new TextEncoder().encode(token),\n { name: 'HMAC', hash: 'SHA-256' },\n false,\n ['sign'],\n );\n KEY_CACHE.set(token, cached);\n }\n return cached;\n}\n\n// Preload the CryptoKey ahead of any sign() calls. Useful at SDK init time\n// so the first user interaction doesn't pay the importKey cost on the hot\n// path. Returns immediately on a second call for the same token.\nexport function preloadSignerKey(token: string): Promise<CryptoKey> {\n return getKey(token);\n}\n\nfunction toHex(bytes: Uint8Array): string {\n let out = '';\n for (let i = 0; i < bytes.length; i++) {\n out += bytes[i].toString(16).padStart(2, '0');\n }\n return out;\n}\n\nfunction effectivePath(path: string): string {\n const stripped = path.replace(/\\/$/, '');\n return stripped.length > 0 ? stripped : '/';\n}\n\nfunction makeIdempotencyKey(): string {\n const c = (globalThis as unknown as { crypto?: Crypto & { randomUUID?: () => string } }).crypto;\n if (c?.randomUUID) return c.randomUUID();\n // Fallback for older WebView shells without randomUUID.\n const rand = new Uint8Array(16);\n c?.getRandomValues(rand);\n rand[6] = (rand[6] & 0x0f) | 0x40;\n rand[8] = (rand[8] & 0x3f) | 0x80;\n const hex = toHex(rand);\n return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;\n}\n", "// Durable retry queue for browser fire-and-forget endpoints (impressions,\n// interactions). Per the auth-rollout plan:\n// - Synchronous retries (3 attempts, ~5s) for request-response calls\n// happen inline in the SDK; THIS file is exclusively for the\n// fire-and-forget endpoints (/ingestion/impressions,\n// /ingestion/interactions).\n// - `/identity/merge` is NEVER auto-retried (non-idempotent) \u2014 callers\n// must NOT enqueue merge requests via this class.\n// - Exponential backoff, capped at 10 minutes / 8 attempts. After the\n// cap, items are dropped and `onRetryDrop` is invoked for observability.\n// - Optional IndexedDB persistence via `persistRetries: true` so queued\n// items survive page reloads / device-sleep / network outages.\n//\n// Re-signing happens on EVERY attempt \u2014 the engine's replay cache rejects\n// the same signature twice, and the 30s signing window means a stale sig\n// is worse than no retry. The `signRequest` callback is invoked with the\n// item payload and must return a freshly-signed URL + idempotency key.\n\nexport interface QueuedItem<TPayload = unknown> {\n // Unique id used for dedupe / persistence. Auto-generated when not set.\n id?: string;\n // Logical path the item targets (used for routing on flush).\n path: string;\n // HTTP method. Currently every fire-and-forget endpoint is POST; the\n // field exists so the queue can also handle PUTs if needed later.\n method: string;\n // Payload sent as the JSON body. Must be canonical-JSON serializable.\n payload: TPayload;\n // Number of attempts so far (0 on first enqueue). The queue increments.\n attempts?: number;\n // Earliest wall-clock ms at which the next attempt should run. Updated\n // each time we back off.\n nextAttemptAt?: number;\n}\n\nexport interface RetryQueueConfig<TPayload = unknown> {\n // Persist queued items across page reloads. Disabled by default \u2014 the\n // demo / read-mostly integrations don't need it. Enabling pulls in an\n // IndexedDB-backed `idb-keyval`-style adapter (see indexed-db.ts).\n persist?: boolean;\n // Storage key for IndexedDB persistence (so multi-tenant pages don't\n // share queue state). Required when `persist: true`.\n persistKey?: string;\n // Maximum attempts before the item is dropped. Default 8.\n maxAttempts?: number;\n // Initial backoff in ms. Default 1000.\n initialBackoffMs?: number;\n // Backoff cap in ms. Default 600_000 (10m).\n maxBackoffMs?: number;\n // Periodic drain interval in ms. Default 2000.\n flushIntervalMs?: number;\n // Called for every item dropped after exhausting attempts. Used for\n // observability \u2014 log to Sentry / surface as a tracker event.\n onRetryDrop?: (item: QueuedItem<TPayload>, error: unknown) => void;\n // Signs each attempt's request fresh. Returns the absolute URL to POST\n // to plus any auth headers (X-Idempotency-Key, etc.). Must NOT cache\n // its return value \u2014 the queue calls this once per attempt.\n signRequest: (item: QueuedItem<TPayload>) => Promise<{\n url: string;\n headers: Record<string, string>;\n }>;\n}\n\nconst DEFAULTS = {\n maxAttempts: 8,\n initialBackoffMs: 1000,\n maxBackoffMs: 600_000,\n flushIntervalMs: 2000,\n};\n\nexport class RetryQueue<TPayload = unknown> {\n private readonly cfg: Required<\n Omit<RetryQueueConfig<TPayload>, 'persist' | 'persistKey' | 'onRetryDrop'>\n > & {\n persist: boolean;\n persistKey?: string;\n onRetryDrop?: RetryQueueConfig<TPayload>['onRetryDrop'];\n };\n private items: QueuedItem<TPayload>[] = [];\n private timer: ReturnType<typeof setInterval> | null = null;\n private draining = false;\n private idb?: Promise<IDBDatabase | null>;\n\n constructor(cfg: RetryQueueConfig<TPayload>) {\n this.cfg = {\n persist: cfg.persist ?? false,\n persistKey: cfg.persistKey,\n maxAttempts: cfg.maxAttempts ?? DEFAULTS.maxAttempts,\n initialBackoffMs: cfg.initialBackoffMs ?? DEFAULTS.initialBackoffMs,\n maxBackoffMs: cfg.maxBackoffMs ?? DEFAULTS.maxBackoffMs,\n flushIntervalMs: cfg.flushIntervalMs ?? DEFAULTS.flushIntervalMs,\n onRetryDrop: cfg.onRetryDrop,\n signRequest: cfg.signRequest,\n };\n\n if (this.cfg.persist) {\n if (!this.cfg.persistKey) {\n throw new Error('RetryQueue: persistKey required when persist=true');\n }\n this.idb = openIndexedDb();\n void this.loadPersisted();\n }\n\n if (typeof window !== 'undefined') {\n this.timer = setInterval(() => void this.drain(), this.cfg.flushIntervalMs);\n window.addEventListener('online', () => void this.drain());\n // Best-effort flush on page hide \u2014 sendBeacon is the documented\n // companion for items we never want to drop.\n window.addEventListener('pagehide', () => void this.drain());\n }\n }\n\n enqueue(item: QueuedItem<TPayload>): void {\n const enriched: QueuedItem<TPayload> = {\n ...item,\n id: item.id ?? makeId(),\n attempts: item.attempts ?? 0,\n nextAttemptAt: item.nextAttemptAt ?? Date.now(),\n };\n this.items.push(enriched);\n void this.persist();\n // Eager drain attempt \u2014 useful when the page is interactive and the\n // queued event should leave the page within a hundred ms or two.\n void this.drain();\n }\n\n // Manually trigger a drain cycle. Used by callers on critical pageviews\n // (e.g. checkout) where dropping the next backoff window matters.\n flush(): Promise<void> {\n return this.drain();\n }\n\n destroy(): void {\n if (this.timer) clearInterval(this.timer);\n this.timer = null;\n }\n\n private async drain(): Promise<void> {\n if (this.draining) return;\n if (typeof navigator !== 'undefined' && navigator.onLine === false) return;\n this.draining = true;\n try {\n const now = Date.now();\n const ready = this.items.filter((i) => (i.nextAttemptAt ?? 0) <= now);\n for (const item of ready) {\n await this.attempt(item);\n }\n await this.persist();\n } finally {\n this.draining = false;\n }\n }\n\n private async attempt(item: QueuedItem<TPayload>): Promise<void> {\n item.attempts = (item.attempts ?? 0) + 1;\n try {\n const { url, headers } = await this.cfg.signRequest(item);\n const res = await fetch(url, {\n method: item.method,\n headers: {\n 'Content-Type': 'application/json',\n ...headers,\n },\n body: JSON.stringify(item.payload),\n keepalive: true,\n });\n if (res.ok) {\n this.removeById(item.id!);\n return;\n }\n // 4xx other than 429 is permanent \u2014 no amount of retry fixes a bad\n // payload. Drop immediately so we don't keep poisoning the queue.\n if (res.status >= 400 && res.status < 500 && res.status !== 429) {\n this.removeById(item.id!);\n this.cfg.onRetryDrop?.(item, new Error(`HTTP ${res.status}`));\n return;\n }\n this.scheduleRetry(item, new Error(`HTTP ${res.status}`));\n } catch (err) {\n this.scheduleRetry(item, err);\n }\n }\n\n private scheduleRetry(item: QueuedItem<TPayload>, err: unknown): void {\n if ((item.attempts ?? 0) >= this.cfg.maxAttempts) {\n this.removeById(item.id!);\n this.cfg.onRetryDrop?.(item, err);\n return;\n }\n const exp = Math.min(\n this.cfg.maxBackoffMs,\n this.cfg.initialBackoffMs * Math.pow(2, (item.attempts ?? 1) - 1),\n );\n // Decorrelated jitter \u2014 full backoff with a uniformly-distributed\n // multiplier in [0.5, 1.0) so multiple replicas don't sync into a\n // thundering herd when the backend recovers.\n const jitter = 0.5 + Math.random() * 0.5;\n item.nextAttemptAt = Date.now() + Math.floor(exp * jitter);\n }\n\n private removeById(id: string): void {\n this.items = this.items.filter((i) => i.id !== id);\n }\n\n // \u2500\u2500 IndexedDB persistence \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n private async persist(): Promise<void> {\n if (!this.cfg.persist || !this.idb || !this.cfg.persistKey) return;\n try {\n const db = await this.idb;\n if (!db) return;\n const tx = db.transaction('queue', 'readwrite');\n const store = tx.objectStore('queue');\n store.put(JSON.parse(JSON.stringify(this.items)), this.cfg.persistKey);\n await txDone(tx);\n } catch {\n // Persistence is best-effort \u2014 never throw out of an enqueue path.\n }\n }\n\n private async loadPersisted(): Promise<void> {\n if (!this.cfg.persist || !this.idb || !this.cfg.persistKey) return;\n try {\n const db = await this.idb;\n if (!db) return;\n const tx = db.transaction('queue', 'readonly');\n const store = tx.objectStore('queue');\n const req = store.get(this.cfg.persistKey);\n const data: unknown = await new Promise((resolve, reject) => {\n req.onsuccess = () => resolve(req.result);\n req.onerror = () => reject(req.error);\n });\n if (Array.isArray(data)) {\n this.items = data as QueuedItem<TPayload>[];\n void this.drain();\n }\n } catch {\n // Lost-persistence is silent by design \u2014 don't break enqueue on\n // private-browsing / disabled-storage.\n }\n }\n}\n\nfunction makeId(): string {\n const c = (globalThis as unknown as { crypto?: Crypto & { randomUUID?: () => string } }).crypto;\n if (c?.randomUUID) return c.randomUUID();\n return `rnx-${Date.now()}-${Math.random().toString(36).slice(2, 11)}`;\n}\n\nfunction openIndexedDb(): Promise<IDBDatabase | null> {\n if (typeof indexedDB === 'undefined') return Promise.resolve(null);\n return new Promise((resolve) => {\n const req = indexedDB.open('recomnext-retry-queue', 1);\n req.onupgradeneeded = () => {\n const db = req.result;\n if (!db.objectStoreNames.contains('queue')) db.createObjectStore('queue');\n };\n req.onsuccess = () => resolve(req.result);\n req.onerror = () => resolve(null);\n req.onblocked = () => resolve(null);\n });\n}\n\nfunction txDone(tx: IDBTransaction): Promise<void> {\n return new Promise((resolve, reject) => {\n tx.oncomplete = () => resolve();\n tx.onerror = () => reject(tx.error);\n tx.onabort = () => reject(tx.error);\n });\n}\n", "import type { RetryQueue } from '@recomnext/shared-browser-auth';\nimport type { FireAndForgetPayload } from './client';\nimport type { ImpressionTrackerOptions } from './types';\n\nconst FLUSH_INTERVAL = 2000;\n\nexport class ImpressionTracker {\n private queue: { itemId: string; scenario: string }[] = [];\n private flushing = false;\n private observer: IntersectionObserver | null = null;\n private seen = new Set<string>();\n private timer: ReturnType<typeof setInterval> | null = null;\n private readonly getUserId: () => string;\n private readonly getSessionId: () => string;\n // The RetryQueue handles all signed POSTs to /ingestion/impressions\n // (single source of truth for fire-and-forget on this page). We still\n // batch impressions every 2s before enqueuing so we don't fan out N\n // signed requests for one viewport scan.\n private readonly retryQueue: RetryQueue<FireAndForgetPayload>;\n\n constructor(\n _baseUrl: string,\n _tenantId: string,\n getUserId: () => string,\n getSessionId: () => string,\n retryQueue: RetryQueue<FireAndForgetPayload>,\n ) {\n // baseUrl + tenantId are captured by the client during signRequest()\n // and no longer needed at the tracker level. Kept in the signature\n // for backwards compatibility with anyone constructing the tracker\n // directly (rare \u2014 almost everyone uses RecomnextBrowserClient).\n this.getUserId = getUserId;\n this.getSessionId = getSessionId;\n this.retryQueue = retryQueue;\n if (typeof window !== 'undefined') {\n this.timer = setInterval(() => this.flush(), FLUSH_INTERVAL);\n // sendBeacon is best-effort and CANNOT carry the X-Idempotency-Key\n // header (it's a Blob POST). On pagehide we therefore force the\n // queue's normal signed-fetch path to drain via `keepalive: true`,\n // which is now widely supported in all evergreen browsers.\n window.addEventListener('pagehide', () => this.flush(true));\n }\n }\n\n observe(container: Element, opts: ImpressionTrackerOptions = {}): () => void {\n const selector = opts.itemSelector ?? '[data-item-id]';\n const threshold = opts.threshold ?? 0.5;\n const once = opts.once !== false;\n const scenario = opts.scenario ?? '';\n\n this.observer = new IntersectionObserver(\n (entries) => {\n for (const entry of entries) {\n if (!entry.isIntersecting) continue;\n const el = entry.target as HTMLElement;\n const itemId = el.dataset.itemId;\n if (!itemId) continue;\n const key = `${scenario}:${itemId}`;\n if (once && this.seen.has(key)) continue;\n this.seen.add(key);\n this.queue.push({ itemId, scenario });\n if (once) this.observer?.unobserve(el);\n }\n },\n { root: null, threshold },\n );\n\n const elements = container.querySelectorAll(selector);\n elements.forEach((el) => this.observer!.observe(el));\n\n return () => {\n this.observer?.disconnect();\n this.observer = null;\n };\n }\n\n // `urgent` is set during pagehide. When true we kick the RetryQueue\n // immediately so it has a chance to land items inside the unload window\n // (keepalive: true on the inner fetch keeps the connection alive past\n // the navigation).\n private flush(urgent = false): void {\n if (this.flushing || this.queue.length === 0) return;\n this.flushing = true;\n const batch = this.queue.splice(0, this.queue.length);\n\n const grouped = new Map<string, string[]>();\n for (const { itemId, scenario } of batch) {\n if (!grouped.has(scenario)) grouped.set(scenario, []);\n grouped.get(scenario)!.push(itemId);\n }\n\n const impressions = Array.from(grouped.entries()).map(([scenarioSlug, itemIds]) => ({\n userId: this.getUserId() || undefined,\n sessionId: this.getSessionId() || undefined,\n itemIds,\n scenarioSlug: scenarioSlug || undefined,\n timestamp: new Date().toISOString(),\n }));\n\n this.retryQueue.enqueue({\n path: '/ingestion/impressions',\n method: 'POST',\n payload: { impressions },\n });\n\n if (urgent) void this.retryQueue.flush();\n this.flushing = false;\n }\n\n destroy(): void {\n this.flush();\n this.observer?.disconnect();\n if (this.timer) clearInterval(this.timer);\n }\n}\n", "import {\n preloadSignerKey,\n RetryQueue,\n sign as signRequest,\n type QueuedItem,\n} from '@recomnext/shared-browser-auth';\n\nimport type {\n SearchOptions,\n SearchResponse,\n SearchV2Options,\n SearchV2Response,\n SearchV2AutocompleteResponse,\n SearchV2SettingsResponse,\n CatalogItem,\n CatalogListResponse,\n ListItemsOptions,\n} from '@recomnext/shared-search-types';\n\nimport { ImpressionTracker } from './tracker';\nimport type {\n BrowserClientConfig,\n RecommendationResult,\n RecommendationOptions,\n RecommendOptions,\n ImpressionTrackerOptions,\n} from './types';\n\nconst SESSION_KEY = 'recomnext_session_id';\n\n// Synchronous retry budget for request-response calls (recommendations,\n// catalog reads, identity/merge). Mirrors the Node + Python SDK behavior:\n// 3 attempts total, ~5s aggregate, re-signs on every attempt.\nconst SYNC_RETRIES = 3;\nconst SYNC_INITIAL_BACKOFF_MS = 200;\nconst SYNC_MAX_BACKOFF_MS = 2_000;\n\n// 5xx + 429 are auto-retried. We special-case 503 +\n// errorCode=auth_replay_cache_unavailable so the SDK retries even if an\n// operator tunes 503 out of the retry set.\nconst RETRYABLE_STATUS = new Set([429, 500, 502, 503, 504]);\nconst AUTH_REPLAY_CACHE_UNAVAILABLE = 'auth_replay_cache_unavailable';\n\n// `/identity/merge` mutates the identity graph on every call. The plan\n// locks this to single-shot until the engine grows server-side\n// idempotency \u2014 never auto-retry, even on a transient 503.\nconst NEVER_RETRY_PATHS = new Set(['/identity/merge']);\n\nexport class RecomnextBrowserClient {\n private readonly baseUrl: string;\n private readonly tenantId: string;\n private readonly publicToken: string | undefined;\n private userId: string;\n private sessionId: string;\n private readonly tracker: ImpressionTracker;\n // Lazy \u2014 only constructed once for fire-and-forget queues that the\n // tracker also reuses.\n readonly fireAndForgetQueue: RetryQueue<FireAndForgetPayload>;\n\n constructor(config: BrowserClientConfig) {\n this.baseUrl = config.baseUrl.replace(/\\/+$/, '');\n this.tenantId = config.tenantId;\n this.publicToken = config.publicToken;\n this.userId = config.userId ?? '';\n this.sessionId = config.sessionId ?? this.resolveSessionId();\n\n // Eagerly import the CryptoKey so the first signed event after page\n // load doesn't pay the importKey latency on the hot path.\n if (this.publicToken) void preloadSignerKey(this.publicToken);\n\n this.fireAndForgetQueue = new RetryQueue<FireAndForgetPayload>({\n persist: config.persistRetries ?? false,\n persistKey: `rnx:${this.tenantId}:fnf`,\n onRetryDrop: config.onRetryDrop,\n signRequest: (item) => this.signFireAndForget(item),\n });\n\n this.tracker = new ImpressionTracker(\n this.baseUrl,\n this.tenantId,\n () => this.userId,\n () => this.sessionId,\n this.fireAndForgetQueue,\n );\n }\n\n private resolveSessionId(): string {\n if (typeof sessionStorage === 'undefined') return '';\n let sid = sessionStorage.getItem(SESSION_KEY);\n if (!sid) {\n sid = `sess_${Date.now()}_${Math.random().toString(36).slice(2, 9)}`;\n sessionStorage.setItem(SESSION_KEY, sid);\n }\n return sid;\n }\n\n private effectiveUserId(): string {\n return this.userId || this.sessionId;\n }\n\n private async request<T>(\n method: string,\n path: string,\n body?: unknown,\n query?: Record<string, string | number | boolean | undefined>,\n ): Promise<T> {\n const maxRetries = NEVER_RETRY_PATHS.has(path) ? 0 : SYNC_RETRIES;\n let lastErr: unknown;\n\n for (let attempt = 0; attempt <= maxRetries; attempt++) {\n if (attempt > 0) {\n const exp = Math.min(\n SYNC_MAX_BACKOFF_MS,\n SYNC_INITIAL_BACKOFF_MS * Math.pow(2, attempt - 1),\n );\n const jitter = 0.5 + Math.random() * 0.5;\n await sleep(Math.floor(exp * jitter));\n }\n\n // Always re-sign \u2014 the engine's replay cache rejects the same\n // signature twice, and the 30s signing window means a stale sig\n // would also fail.\n const { url, headers } = await this.buildSignedRequest(\n method,\n path,\n query,\n body,\n );\n\n let res: Response;\n try {\n res = await fetch(url, {\n method,\n headers: {\n 'Content-Type': 'application/json',\n ...headers,\n },\n body: body !== undefined ? JSON.stringify(body) : undefined,\n });\n } catch (err) {\n lastErr = err;\n if (attempt >= maxRetries) throw err;\n continue;\n }\n\n if (res.status === 503) {\n const code = await extractErrorCode(res.clone());\n if (code === AUTH_REPLAY_CACHE_UNAVAILABLE && attempt < maxRetries) {\n lastErr = new Error(`recomnext: ${method} ${path} ${res.status} ${code}`);\n continue;\n }\n }\n\n if (RETRYABLE_STATUS.has(res.status) && attempt < maxRetries) {\n lastErr = new Error(`recomnext: ${method} ${path} returned ${res.status}`);\n continue;\n }\n\n if (!res.ok) {\n const text = await res.text().catch(() => '');\n throw new Error(`recomnext: ${method} ${path} returned ${res.status}: ${text}`);\n }\n\n if (res.status === 204) return undefined as unknown as T;\n return res.json() as Promise<T>;\n }\n\n throw lastErr ?? new Error(`recomnext: ${method} ${path} retries exhausted`);\n }\n\n // Same shape as the RetryQueue's signRequest contract \u2014 used by the\n // queue *and* the impression tracker so both share a single signing\n // path.\n async signFireAndForget(item: QueuedItem<FireAndForgetPayload>) {\n const { url, headers } = await this.buildSignedRequest(\n item.method,\n item.path,\n undefined,\n item.payload,\n );\n return { url, headers };\n }\n\n private async buildSignedRequest(\n method: string,\n path: string,\n query: Record<string, string | number | boolean | undefined> | undefined,\n body: unknown,\n ): Promise<{ url: string; headers: Record<string, string> }> {\n const baseHeaders: Record<string, string> = {\n 'X-Tenant-Id': this.tenantId,\n };\n\n if (!this.publicToken) {\n // Legacy / no-auth mode. Engine accepts because\n // LEGACY_TENANT_AUTH=true. After Phase 7 cutover this path will be\n // rejected with 401.\n const qs = renderQs(query);\n return { url: `${this.baseUrl}${path}${qs}`, headers: baseHeaders };\n }\n\n const signed = await signRequest({\n method,\n path,\n query,\n body: body ?? null,\n tenantId: this.tenantId,\n token: this.publicToken,\n mode: 'public',\n });\n return {\n url: `${this.baseUrl}${signed.signedPath}`,\n headers: {\n ...baseHeaders,\n 'X-Idempotency-Key': signed.idempotencyKey,\n },\n };\n }\n\n private qs(\n params: Record<string, string | number | boolean | undefined>,\n ): Record<string, string | number | boolean | undefined> {\n return params;\n }\n\n private includeQuery(opts?: RecommendationOptions): {\n returnProperties?: boolean;\n includedProperties?: string;\n } {\n return {\n returnProperties: opts?.returnProperties,\n includedProperties:\n opts?.includedProperties && opts.includedProperties.length > 0\n ? opts.includedProperties.join(',')\n : undefined,\n };\n }\n\n /** Serialize override fields for GET query (comma-separated lists; '' clears). */\n private overrideQuery(opts?: RecommendationOptions): {\n filter?: string;\n boosters?: string;\n rulesSlugs?: string;\n segmentationId?: string;\n itemsPerSegment?: number | string;\n autoRelaxFilters?: boolean;\n relaxEntireFilterOnEmpty?: boolean;\n } {\n if (!opts) return {};\n const join = (v: string[] | null | undefined): string | undefined => {\n if (v === undefined) return undefined;\n if (v === null || v.length === 0) return '';\n return v.join(',');\n };\n return {\n filter:\n opts.filter === undefined\n ? undefined\n : opts.filter === null\n ? ''\n : opts.filter,\n boosters: join(opts.boosters),\n rulesSlugs: join(opts.rulesSlugs),\n segmentationId:\n opts.segmentationId === undefined\n ? undefined\n : opts.segmentationId === null\n ? ''\n : opts.segmentationId,\n // Present null \u2192 '' so the query param is sent; engine transform maps\n // '' \u2192 null \u2192 resolve to itemsPerSegment=1 (omit would keep scenario).\n itemsPerSegment:\n opts.itemsPerSegment === undefined\n ? undefined\n : opts.itemsPerSegment === null\n ? ''\n : opts.itemsPerSegment,\n autoRelaxFilters: opts.autoRelaxFilters,\n relaxEntireFilterOnEmpty: opts.relaxEntireFilterOnEmpty,\n };\n }\n\n private overrideBody(opts?: RecommendationOptions): {\n filter?: string | null;\n boosters?: string[] | null;\n rulesSlugs?: string[] | null;\n segmentationId?: string | null;\n itemsPerSegment?: number | null;\n autoRelaxFilters?: boolean;\n relaxEntireFilterOnEmpty?: boolean;\n } {\n if (!opts) return {};\n const body: {\n filter?: string | null;\n boosters?: string[] | null;\n rulesSlugs?: string[] | null;\n segmentationId?: string | null;\n itemsPerSegment?: number | null;\n autoRelaxFilters?: boolean;\n relaxEntireFilterOnEmpty?: boolean;\n } = {};\n if (opts.filter !== undefined) body.filter = opts.filter;\n if (opts.boosters !== undefined) body.boosters = opts.boosters;\n if (opts.rulesSlugs !== undefined) body.rulesSlugs = opts.rulesSlugs;\n if (opts.segmentationId !== undefined) {\n body.segmentationId = opts.segmentationId;\n }\n if (opts.itemsPerSegment !== undefined) {\n body.itemsPerSegment = opts.itemsPerSegment;\n }\n if (opts.autoRelaxFilters !== undefined) {\n body.autoRelaxFilters = opts.autoRelaxFilters;\n }\n if (opts.relaxEntireFilterOnEmpty !== undefined) {\n body.relaxEntireFilterOnEmpty = opts.relaxEntireFilterOnEmpty;\n }\n return body;\n }\n\n /* \u2500\u2500 Recommendations \u2500\u2500 */\n\n getRecommendations(opts?: RecommendationOptions): Promise<RecommendationResult> {\n return this.request(\n 'GET',\n `/recommendations/user-to-item/${this.effectiveUserId()}`,\n undefined,\n this.qs({\n count: opts?.count,\n scenario: opts?.scenario,\n ...this.includeQuery(opts),\n ...this.overrideQuery(opts),\n }),\n );\n }\n\n getRelatedItems(itemId: string, opts?: RecommendationOptions): Promise<RecommendationResult> {\n return this.request(\n 'GET',\n `/recommendations/item-to-item/${itemId}`,\n undefined,\n this.qs({\n count: opts?.count,\n scenario: opts?.scenario,\n ...this.includeQuery(opts),\n ...this.overrideQuery(opts),\n }),\n );\n }\n\n getSimilarProducts(itemId: string, opts?: RecommendationOptions): Promise<RecommendationResult> {\n return this.request(\n 'GET',\n `/recommendations/similar-products/${itemId}`,\n undefined,\n this.qs({\n count: opts?.count,\n scenario: opts?.scenario,\n ...this.includeQuery(opts),\n ...this.overrideQuery(opts),\n }),\n );\n }\n\n getRecentlyViewed(opts?: RecommendationOptions): Promise<RecommendationResult> {\n return this.request(\n 'GET',\n `/recommendations/recently-viewed/${this.effectiveUserId()}`,\n undefined,\n this.qs({\n count: opts?.count,\n scenario: opts?.scenario,\n ...this.includeQuery(opts),\n ...this.overrideQuery(opts),\n }),\n );\n }\n\n getCartRecommendations(itemIds: string[], opts?: RecommendationOptions): Promise<RecommendationResult> {\n return this.request(\n 'POST',\n '/recommendations/items-to-items',\n {\n itemIds,\n count: opts?.count ?? 10,\n returnProperties: opts?.returnProperties,\n includedProperties: opts?.includedProperties,\n ...this.overrideBody(opts),\n },\n this.qs({ scenario: opts?.scenario }),\n );\n }\n\n recommend(opts: RecommendOptions): Promise<RecommendationResult> {\n return this.request('POST', '/recommendations', {\n ...opts,\n userId: this.effectiveUserId(),\n });\n }\n\n /* \u2500\u2500 Search \u2500\u2500 */\n\n search(query: string, opts?: SearchOptions): Promise<SearchResponse> {\n return this.request(\n 'GET',\n '/recommendations/search',\n undefined,\n this.qs({\n query,\n count: opts?.count,\n category: opts?.category,\n priceRange: opts?.priceRange,\n userId: opts?.userId ?? (this.userId || undefined),\n }),\n );\n }\n\n searchV2(query: string, opts?: SearchV2Options): Promise<SearchV2Response> {\n return this.request(\n 'GET',\n '/recommendations/search/v2',\n undefined,\n this.qs({\n query,\n count: opts?.count,\n category: opts?.category,\n priceRange: opts?.priceRange,\n userId: opts?.userId ?? (this.userId || undefined),\n locale: opts?.locale,\n debugScores: opts?.debugScores === true ? true : undefined,\n algorithm: opts?.algorithm,\n }),\n );\n }\n\n autocomplete(\n query: string,\n opts?: { limit?: number },\n ): Promise<SearchV2AutocompleteResponse> {\n return this.request(\n 'GET',\n '/recommendations/search/v2/autocomplete',\n undefined,\n this.qs({\n query,\n limit: opts?.limit,\n }),\n );\n }\n\n getSearchV2Settings(): Promise<SearchV2SettingsResponse> {\n return this.request('GET', '/recommendations/search/v2/settings');\n }\n\n /* \u2500\u2500 Catalog browse (public HMAC eligible) \u2500\u2500 */\n\n listItems(opts?: ListItemsOptions): Promise<CatalogListResponse> {\n return this.request(\n 'GET',\n '/catalog/items',\n undefined,\n this.qs({\n page: opts?.page,\n limit: opts?.limit,\n category: opts?.category,\n search: opts?.search,\n availability: opts?.availability,\n }),\n );\n }\n\n getCategories(): Promise<string[]> {\n return this.request('GET', '/catalog/items/categories');\n }\n\n getItem(externalId: string): Promise<CatalogItem> {\n return this.request('GET', `/catalog/items/${encodeURIComponent(externalId)}`);\n }\n\n /* \u2500\u2500 Event tracking \u2500\u2500 */\n\n trackView(itemId: string, scenarioSlug?: string): void {\n this.fireAndForgetQueue.enqueue({\n path: '/ingestion/interactions',\n method: 'POST',\n payload: {\n interactions: [{ userId: this.effectiveUserId(), itemId, type: 'view', scenarioSlug: scenarioSlug || undefined, timestamp: new Date().toISOString() }],\n },\n });\n }\n\n trackCart(itemId: string, scenarioSlug?: string): void {\n this.fireAndForgetQueue.enqueue({\n path: '/ingestion/interactions',\n method: 'POST',\n payload: {\n interactions: [{ userId: this.effectiveUserId(), itemId, type: 'cart', scenarioSlug: scenarioSlug || undefined, timestamp: new Date().toISOString() }],\n },\n });\n }\n\n trackPurchase(itemId: string, scenarioSlug?: string): void {\n this.fireAndForgetQueue.enqueue({\n path: '/ingestion/interactions',\n method: 'POST',\n payload: {\n interactions: [{ userId: this.effectiveUserId(), itemId, type: 'purchase', scenarioSlug: scenarioSlug || undefined, timestamp: new Date().toISOString() }],\n },\n });\n }\n\n /* \u2500\u2500 Impression tracking \u2500\u2500 */\n\n trackImpressions(container: Element, opts?: ImpressionTrackerOptions): () => void {\n return this.tracker.observe(container, opts);\n }\n\n /* \u2500\u2500 Identity \u2500\u2500 */\n\n setUserId(userId: string): void {\n this.userId = userId;\n }\n\n mergeIdentity(authenticatedUserId: string): Promise<unknown> {\n const anonId = this.sessionId;\n this.userId = authenticatedUserId;\n // Note: never auto-retried (see NEVER_RETRY_PATHS) \u2014 callers should\n // handle 5xx/network failures themselves to avoid duplicate merges.\n return this.request('POST', '/identity/merge', {\n userId: authenticatedUserId,\n sessionIds: [anonId],\n });\n }\n\n /* \u2500\u2500 Lifecycle \u2500\u2500 */\n\n destroy(): void {\n this.tracker.destroy();\n this.fireAndForgetQueue.destroy();\n }\n}\n\n// Shape used by both the SDK's trackView/trackCart/trackPurchase calls\n// and the ImpressionTracker \u2014 they all funnel through the same queue.\nexport type FireAndForgetPayload = {\n interactions?: unknown[];\n impressions?: unknown[];\n};\n\nfunction renderQs(params?: Record<string, string | number | boolean | undefined>): string {\n if (!params) return '';\n const parts: string[] = [];\n for (const [k, v] of Object.entries(params)) {\n // Preserve empty string (GET clear sentinels). Drop only omit/null.\n if (v === undefined || v === null) continue;\n parts.push(`${encodeURIComponent(k)}=${encodeURIComponent(String(v))}`);\n }\n return parts.length ? `?${parts.join('&')}` : '';\n}\n\nasync function extractErrorCode(res: Response): Promise<string | undefined> {\n try {\n const body = (await res.json()) as { errorCode?: unknown };\n return typeof body.errorCode === 'string' ? body.errorCode : undefined;\n } catch {\n return undefined;\n }\n}\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((r) => setTimeout(r, ms));\n}\n"],
5
+ "mappings": "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",
6
6
  "names": ["index_exports", "__export", "ImpressionTracker", "RecomnextBrowserClient", "canonicalJson", "value", "stringify", "keys", "parts", "k", "v", "KEY_CACHE", "sign", "input", "verb", "path", "effectivePath", "timestamp", "bodyHash", "sha256Hex", "canonicalJson", "others", "k", "v", "sortedOthers", "a", "b", "tsParam", "sigParam", "trailing", "queryForSigning", "signingString", "signature", "hmacSha256Hex", "makeIdempotencyKey", "bytes", "toHex", "token", "payload", "key", "getKey", "sig", "cached", "preloadSignerKey", "out", "i", "stripped", "c", "rand", "hex", "DEFAULTS", "RetryQueue", "cfg", "openIndexedDb", "item", "enriched", "makeId", "now", "ready", "i", "url", "headers", "res", "err", "exp", "jitter", "id", "db", "tx", "txDone", "req", "data", "resolve", "reject", "c", "ImpressionTracker", "_baseUrl", "_tenantId", "getUserId", "getSessionId", "retryQueue", "container", "opts", "selector", "threshold", "once", "scenario", "entries", "entry", "el", "itemId", "key", "urgent", "batch", "grouped", "impressions", "scenarioSlug", "itemIds", "SESSION_KEY", "SYNC_RETRIES", "SYNC_INITIAL_BACKOFF_MS", "SYNC_MAX_BACKOFF_MS", "RETRYABLE_STATUS", "AUTH_REPLAY_CACHE_UNAVAILABLE", "NEVER_RETRY_PATHS", "RecomnextBrowserClient", "config", "preloadSignerKey", "RetryQueue", "item", "ImpressionTracker", "sid", "method", "path", "body", "query", "maxRetries", "lastErr", "attempt", "exp", "jitter", "sleep", "url", "headers", "res", "err", "code", "extractErrorCode", "text", "baseHeaders", "qs", "renderQs", "signed", "sign", "params", "opts", "join", "v", "itemId", "itemIds", "externalId", "scenarioSlug", "container", "userId", "authenticatedUserId", "anonId", "parts", "k", "ms", "r"]
7
7
  }
package/dist/types.d.ts CHANGED
@@ -42,6 +42,15 @@ export interface RecommendationResult {
42
42
  items: RecommendedItem[];
43
43
  count: number;
44
44
  algorithm?: string;
45
+ filterRelaxation?: FilterRelaxation;
46
+ }
47
+ export interface FilterRelaxation {
48
+ applied: true;
49
+ originalFilter: string;
50
+ effectiveFilter: string | null;
51
+ droppedClauses: string[];
52
+ droppedEntireFilter: boolean;
53
+ resultCountAfterFilter: number;
45
54
  }
46
55
  export interface ProjectionOptions {
47
56
  returnProperties?: boolean;
@@ -54,6 +63,8 @@ export interface PostProcessOverrideOptions {
54
63
  rulesSlugs?: string[] | null;
55
64
  segmentationId?: string | null;
56
65
  itemsPerSegment?: number | null;
66
+ autoRelaxFilters?: boolean;
67
+ relaxEntireFilterOnEmpty?: boolean;
57
68
  }
58
69
  export interface RecommendationOptions extends ProjectionOptions, PostProcessOverrideOptions {
59
70
  count?: number;
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,mBAAmB;IAClC,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;;;;OAMG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;OAIG;IACH,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB;;;;OAIG;IACH,WAAW,CAAC,EAAE,CAAC,IAAI,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,OAAO,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAE,EAAE,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC;CACrG;AAED,MAAM,WAAW,eAAe;IAC9B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;OAGG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED,MAAM,WAAW,oBAAoB;IACnC,KAAK,EAAE,eAAe,EAAE,CAAC;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,iBAAiB;IAChC,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,kBAAkB,CAAC,EAAE,MAAM,EAAE,CAAC;CAC/B;AAED,sFAAsF;AACtF,MAAM,WAAW,0BAA0B;IACzC,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,QAAQ,CAAC,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC;IAC3B,UAAU,CAAC,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC;IAC7B,cAAc,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,eAAe,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACjC;AAED,MAAM,WAAW,qBACf,SAAQ,iBAAiB,EAAE,0BAA0B;IACrD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,gBACf,SAAQ,iBAAiB,EAAE,0BAA0B;IACrD,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,wBAAwB;IACvC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,mBAAmB;IAClC,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;;;;OAMG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;OAIG;IACH,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB;;;;OAIG;IACH,WAAW,CAAC,EAAE,CAAC,IAAI,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,OAAO,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAE,EAAE,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC;CACrG;AAED,MAAM,WAAW,eAAe;IAC9B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;OAGG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED,MAAM,WAAW,oBAAoB;IACnC,KAAK,EAAE,eAAe,EAAE,CAAC;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,gBAAgB,CAAC,EAAE,gBAAgB,CAAC;CACrC;AAED,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,IAAI,CAAC;IACd,cAAc,EAAE,MAAM,CAAC;IACvB,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,cAAc,EAAE,MAAM,EAAE,CAAC;IACzB,mBAAmB,EAAE,OAAO,CAAC;IAC7B,sBAAsB,EAAE,MAAM,CAAC;CAChC;AAED,MAAM,WAAW,iBAAiB;IAChC,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,kBAAkB,CAAC,EAAE,MAAM,EAAE,CAAC;CAC/B;AAED,sFAAsF;AACtF,MAAM,WAAW,0BAA0B;IACzC,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,QAAQ,CAAC,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC;IAC3B,UAAU,CAAC,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC;IAC7B,cAAc,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,eAAe,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,wBAAwB,CAAC,EAAE,OAAO,CAAC;CACpC;AAED,MAAM,WAAW,qBACf,SAAQ,iBAAiB,EAAE,0BAA0B;IACrD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,gBACf,SAAQ,iBAAiB,EAAE,0BAA0B;IACrD,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,wBAAwB;IACvC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@recomnext/browser",
3
- "version": "0.3.1-dev.cf35fcbb",
3
+ "version": "0.3.1-dev.e2cdeffb",
4
4
  "description": "Lightweight browser SDK for recomnext — recommendations, event tracking, and impression tracking",
5
5
  "type": "module",
6
6
  "main": "dist/recomnext-browser.umd.js",