@sazabi/react-native 0.0.0-stage → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,5 @@
1
+ var I=(e,t=4096)=>e.length>t?e.slice(0,t):e,be=(e,t="",r=0)=>{let n={};for(let[i,a]of Object.entries(e)){let l=t?`${t}.${i}`:i;if(a===null||a===void 0)continue;if(typeof a==="object"&&!Array.isArray(a)&&r<4){Object.assign(n,be(a,l,r+1));continue}if(typeof a==="number"||typeof a==="boolean")n[l]=a;else if(typeof a==="string")n[l]=I(a);else try{n[l]=I(JSON.stringify(a)??"")}catch{}}return n};var O=(e)=>{try{Object.defineProperty(e,"__sazabiBrowserWrapped",{value:!0})}catch{}},B=(e)=>typeof e==="function"&&e.__sazabiBrowserWrapped===!0;var Ke={error:"ERROR",warn:"WARN",info:"INFO",debug:"DEBUG"},Q=0,je=(e)=>{Q+=1;try{e()}finally{Q-=1}},ve=1000,ze=4000,Ge=(e)=>{let t;if(typeof e==="string")t=e;else if(e instanceof Error)t=`${e.name}: ${e.message}`;else try{t=JSON.stringify(e)??String(e)}catch{t=String(e)}return t.length>ve?`${t.slice(0,ve)}…`:t},Qe=(e)=>{let t=[],r=!1;for(let n of e.levels){let i=console[n];if(typeof i!=="function"||B(i))continue;let a=function(...l){if(i.apply(this??console,l),r||Q>0)return;try{if(typeof l[0]==="string"&&l[0].startsWith(e.selfPrefix))return;r=!0;let h=l.map(Ge).join(" ").slice(0,ze);e.emit({type:"log",body:h||`console.${n}`,severity:Ke[n],timeUnixMs:Date.now(),attributes:{"app.console.level":n}})}catch{}finally{r=!1}};O(a);try{console[n]=a}catch{continue}t.push(()=>{if(console[n]===a)console[n]=i})}return{teardown(){for(let n of t)try{n()}catch{}}}};var Je=/^([a-z][a-z0-9+.-]*):\/\/([^/?#]*)([^?#]*)(\?[^#]*)?(#.*)?$/i,Ze={"https:":":443","http:":":80"},et=({host:e,protocol:t})=>{let r=Ze[t];return r&&e.endsWith(r)?e.slice(0,-r.length):e},k=(e)=>{let t=Je.exec(e.trim());if(!t)return;let[,r,n,i,a="",l=""]=t;if(!r||n===void 0)return;let h=n.lastIndexOf("@"),p=`${r.toLowerCase()}:`,c=et({host:(h===-1?n:n.slice(h+1)).toLowerCase(),protocol:p});if(!c)return;let x=c.startsWith("[")?c.slice(0,c.indexOf("]")+1):c.replace(/:\d*$/,""),A=i||"/",S=`${p}//${c}`;return{protocol:p,host:c,hostname:x,pathname:A,search:a,hash:l,origin:S,href:`${S}${A}${a}${l}`}},J=({url:e,base:t})=>{let r=k(e),n=k(t);if(!r||!n||r.origin!==n.origin)return!1;let i=n.pathname.replace(/\/+$/,"");return!i||r.pathname===i||r.pathname.startsWith(`${i}/`)};var tt=/^([0-9a-f]{32})\./,rt=/^[a-z0-9-]+?-([0-9a-f]{32})\./,Se=(e)=>{let t=k(e)?.hostname;if(!t)return;let r=(tt.exec(t)??rt.exec(t))?.[1];return r?`sazabi_public_${r}`:void 0},xe="/v1/logs",we=(e)=>{let t=k(e);if(!t)return;if(t.protocol!=="https:"&&t.protocol!=="http:")return;let r=t.pathname.replace(/\/+$/,"");if(r.endsWith(xe))r=r.slice(0,-xe.length);return`${t.origin}${r}`},nt=(e)=>{let t=e.intakeUrl??e.intakeHost;if(!t)return;let r=we(t);if(!r)return;let n=e.publicKey??Se(t);if(!n)return;return{baseUrl:r,publicKey:n}};var T=()=>{let e=globalThis.performance;return typeof e?.now==="function"?e.now():Date.now()};var M,te=(e)=>{M=e},re=()=>{M=void 0};var Ae=(e,t)=>{let r=k(e);if(!r)return e;return t!==void 0&&r.origin===t?r.pathname:`${r.host}${r.pathname}`},Te=(e)=>{if(e===0||e>=500)return"ERROR";if(e>=400)return"WARN";return"INFO"},ot=new Set(["token","access_token","refresh_token","id_token","api_key","apikey","key","secret","password","signature","sig","x-amz-signature","x-amz-credential","x-goog-signature","sentry_key","authorization","auth","session","sid","code"]),Ee=(e,t)=>{if(e.global||e.sticky)e.lastIndex=0;return e.test(t)},_e=(e,t=[])=>{let r=e.search(/[?#]/);if(r===-1)return e;let n=(i)=>{let a=i;try{a=decodeURIComponent(i)}catch{}return a=a.toLowerCase(),ot.has(a)||t.some((l)=>typeof l==="string"?l.toLowerCase()===a:Ee(l,a))};return e.slice(0,r)+e.slice(r).replace(/([?#&])([^=&#?]+)=([^&#]*)/g,(i,a,l)=>n(l)?`${a}${l}=[redacted]`:i)},st=(e,t)=>{if(typeof t!=="string")return Ee(t,e);if(t.includes("://"))return J({url:e,base:t});let r=k(e);if(!r)return!1;let n=t.toLowerCase();if(n.startsWith("."))return r.hostname===n.slice(1)||r.hostname.endsWith(n);return(n.includes(":")?r.host:r.hostname)===n},Ce=(e,t)=>{if(e.allowlist.length===0)try{return e.isDefaultInjectionTarget(t)}catch{return!1}return e.allowlist.some((r)=>st(t,r))};var Re=300,P=[],Ie=(e)=>{if(P.push({url:e,startTime:T()}),P.length>Re)P=P.slice(-Re)};var ne=()=>{P=[]},Oe=(e,t)=>{try{let r=Ae(t.resolvedUrl,e.displayOrigin),n=t.status>0?String(t.status):"failed",i={type:"network",body:`${t.method} ${r} → ${n}`,severity:t.severity??Te(t.status),timeUnixMs:Date.now(),attributes:{"http.request.method":t.method,"http.response.status_code":t.status>0?t.status:void 0,"url.full":_e(t.resolvedUrl,e.redactQueryParams),"app.network.kind":t.kind,"app.network.duration_ms":Math.round(t.durationMs),"app.network.error":t.failureMessage,"app.network.request_id":t.requestId,"app.network.request_id_header":t.requestIdHeader}};if(t.injected)i.traceId=t.injected.traceId,i.spanId=t.injected.spanId;e.emit(i)}catch{}},Me=(e,t)=>{try{for(let r of e.requestIdHeaders){let n=t(r);if(n)return{requestId:n,requestIdHeader:r}}}catch{}return{}},Pe=(e,t,r)=>{if(t||r)return!0;let n=e;return n?.name==="AbortError"||typeof n?.message==="string"&&/abort|cancel/i.test(n.message)},Ne=4,Z=new WeakMap,Le=(e,t)=>({status:e.status,end:T(),requestId:Me(t,(r)=>e.getResponseHeader(r))}),ee=new WeakMap,ke=(e,t)=>{let r=e?.owner;if(!e||!r||r!==M)return;try{let{status:n,end:i,requestId:a}=t(r),l=n===0&&(e.aborted||Pe(void 0,!1,r.isOffline?.()));Oe(r,{kind:"xhr",method:e.method,resolvedUrl:e.resolvedUrl,status:n,durationMs:i-e.start,severity:l?"DEBUG":void 0,injected:e.injected,...a})}catch{}},it=(e)=>{let t=ee.get(e);if(t)return t;let r={};return e.addEventListener("abort",()=>{if(r.current)r.current.aborted=!0}),e.addEventListener("loadend",()=>{let n=r.finished;if(r.finished=void 0,n){if(ke(n.tracked,()=>n.outcome),e.readyState!==Ne)return}let i=r.current;r.current=void 0,ke(i,(a)=>Le(e,a))}),ee.set(e,r),r},E,at=(e)=>{let{open:t,send:r,setRequestHeader:n}=e;return{originalOpen:t,originalSend:r,originalSetRequestHeader:n,wrappedOpen:function(...h){let p=M;if(p)try{let[c,x]=h;Z.set(this,{method:String(c).toUpperCase(),resolvedUrl:p.resolveUrl(String(x)),customTraceparent:!1,start:0,processed:!1})}catch{}try{let c=ee.get(this),x=c?.current;if(c&&x?.owner&&this.readyState===Ne)c.finished={tracked:x,outcome:Le(this,x.owner)},c.current=void 0}catch{}t.apply(this,h)},wrappedSend:function(h){let p=M;if(p)try{let c=Z.get(this);if(c?.resolvedUrl&&!c.processed){c.processed=!0;let x=p.isInternalUrl(c.resolvedUrl);if(!x&&p.propagateTraceContext&&!c.customTraceparent&&Ce(p,c.resolvedUrl))try{let A=p.traceContext.forRequest();for(let[S,o]of Object.entries(A.headers))n.call(this,S,o);c.injected=A}catch{c.injected=void 0}if(!x)Ie(c.resolvedUrl);if(!x&&p.capture)c.start=T(),c.owner=p,it(this).current=c}}catch{}r.call(this,h)},wrappedSetRequestHeader:function(h,p){if(M)try{if(h.toLowerCase()==="traceparent"){let c=Z.get(this);if(c)c.customTraceparent=!0}}catch{}n.call(this,h,p)}}},De=()=>globalThis.XMLHttpRequest?.prototype,oe=()=>{let e=De();if(!e||B(e.open))return;let t=at(e);O(t.wrappedOpen),O(t.wrappedSetRequestHeader),O(t.wrappedSend);try{e.open=t.wrappedOpen,e.setRequestHeader=t.wrappedSetRequestHeader,e.send=t.wrappedSend}catch{try{e.open=t.originalOpen,e.setRequestHeader=t.originalSetRequestHeader,e.send=t.originalSend}catch{}return}E=t},se=()=>{let e=De();if(e&&E){if(e.open===E.wrappedOpen)e.open=E.originalOpen;if(e.setRequestHeader===E.wrappedSetRequestHeader)e.setRequestHeader=E.originalSetRequestHeader;if(e.send===E.wrappedSend)e.send=E.originalSend}E=void 0};var ct=()=>{let e=globalThis.crypto,t=e?.getRandomValues;if(typeof t==="function")return(i)=>{let a=new Uint8Array(i);return t.call(e,a),a};let r=(Date.now()^2654435769)>>>0,n=Math.random()*4294967295>>>0;return(i)=>{let a=new Uint8Array(i);for(let l=0;l<i;l+=1){let h=r,p=n;r=p,h^=h<<23,h^=h>>>17,h^=p^p>>>26,n=h>>>0,a[l]=r+n&255}return a}},V=(e)=>Array.from(e,(t)=>t.toString(16).padStart(2,"0")).join(""),Ue=(e,t)=>V(e(t)),C=(e,t)=>`${t}_${Date.now().toString(36)}${Ue(e,8)}`;var dt=1000,ut=1e4,pt=10,lt=500,He=1000,qe=30,ft=4,mt=10,ie=(e,t,r,n)=>Math.hypot(r-e,n-t),Xe=(e)=>({"app.widget.id":e?.id,"app.widget.name":e?.name,"app.widget.tag":e?.tag}),gt=(e)=>{let t=e.sampleIntervalMs??dt,r=e.partialIntervalMs??ut,n=new Map,i=[],a=Number.NEGATIVE_INFINITY,l,h=(o,f)=>{o.path.push(`${Math.round(f-o.startPerf)}:${Math.round(o.lastX)},${Math.round(o.lastY)}`),o.sampleCount+=1,o.lastSamplePerf=f},p=(o,f,v)=>{let{contact:b}=o,y=v===void 0,R=Math.round(f-o.startPerf),_=ie(b.x,b.y,o.lastX,o.lastY);try{e.emit({type:"pointer",body:`pointer ${b.type} ${R}ms ${Math.round(_)}px${y?" (ongoing)":""}`,severity:"INFO",timeUnixMs:y?Date.now():o.startUnixMs,attributes:{"app.pointer.id":String(b.id),"app.pointer.type":b.type,"app.pointer.primary":b.primary,"app.pointer.gesture_id":o.gestureId,"app.screen.coordinate.x":Math.round(b.x),"app.screen.coordinate.y":Math.round(b.y),"app.pointer.end.x":Math.round(o.lastX),"app.pointer.end.y":Math.round(o.lastY),"app.pointer.distance":Math.round(_),"app.pointer.duration_ms":R,"app.pointer.pressure":o.maxPressure,"app.pointer.concurrent":o.maxConcurrent,"app.pointer.path":o.path.join(";")||void 0,"app.pointer.sample_count":o.sampleCount,"app.pointer.partial":y?!0:void 0,"app.pointer.ended_by":v,...Xe(b.target)}})}catch{}o.path=[]},c=(o)=>{o.partialTimer=setTimeout(()=>{o.partialTimer=void 0;try{let f=T();if(f-o.lastSamplePerf>=t)h(o,f);p(o,f,void 0),c(o)}catch{}},r)},x=(o,f)=>{let v=o.target?.name,b=v?`"${v}"`:o.target?.id??`${Math.round(o.x)},${Math.round(o.y)}`,y={...Xe(o.target),"app.pointer.type":o.pointerType,"app.pointer.gesture_id":o.gestureId,"app.screen.coordinate.x":Math.round(o.x),"app.screen.coordinate.y":Math.round(o.y)};try{e.emit({type:"click",body:`click ${b}`,severity:"INFO",timeUnixMs:o.timeUnixMs,attributes:y})}catch{}i.push({time:f,x:o.x,y:o.y});while(i.length>0&&f-i[0].time>He)i.shift();let R=i.filter((_)=>Math.abs(_.x-o.x)<=qe&&Math.abs(_.y-o.y)<=qe);if(R.length>=ft&&f-a>He){a=f;try{e.emit({type:"rage_click",body:`rage click ${b}`,severity:"WARN",timeUnixMs:Date.now(),attributes:{...y,"app.pointer.tap_count":R.length}})}catch{}}try{e.onTap?.({x:o.x,y:o.y,target:o.target,gestureId:o.gestureId})}catch{}},A=(o,f)=>{let{contact:v}=o;if(o.maxConcurrent!==1||(v.button??0)!==0||f-o.startPerf>lt||o.maxDistance>pt)return;x({x:o.lastX,y:o.lastY,target:v.target,pointerType:v.type,gestureId:o.gestureId,timeUnixMs:o.startUnixMs},f)},S=(o,f,v,b)=>{let y=T();if(n.delete(o.contact.id),o.partialTimer!==void 0)clearTimeout(o.partialTimer),o.partialTimer=void 0;if(o.lastX=f,o.lastY=v,o.maxDistance=Math.max(o.maxDistance,ie(o.contact.x,o.contact.y,f,v)),p(o,y,b),b==="up")A(o,y);if(n.size===0)l=void 0};return{down(o){try{let f=n.get(o.id);if(f)S(f,f.lastX,f.lastY,"cancel");if(n.size>=mt)return;let v=T();if(n.size===0||l===void 0)l=C(e.randomBytes,"gest");let b=n.size+1;for(let R of n.values())R.maxConcurrent=Math.max(R.maxConcurrent,b);let y={contact:o,gestureId:l,startUnixMs:Date.now(),startPerf:v,lastSamplePerf:v,lastX:o.x,lastY:o.y,maxDistance:0,maxPressure:o.pressure,maxConcurrent:b,path:[],sampleCount:0};h(y,v),n.set(o.id,y),c(y)}catch{}},move(o,f,v,b){try{let y=n.get(o);if(!y)return;if(y.lastX=f,y.lastY=v,y.maxDistance=Math.max(y.maxDistance,ie(y.contact.x,y.contact.y,f,v)),b!==void 0)y.maxPressure=Math.max(y.maxPressure??0,b);let R=T();if(R-y.lastSamplePerf>=t)h(y,R)}catch{}},up(o,f,v){try{let b=n.get(o);if(b)S(b,f,v,"up")}catch{}},cancel(o){try{let f=n.get(o);if(f)S(f,f.lastX,f.lastY,"cancel")}catch{}},tap(o){try{x({x:o.x,y:o.y,target:o.target,pointerType:"none",timeUnixMs:Date.now()},T())}catch{}},teardown(){for(let o of Array.from(n.values()))try{S(o,o.lastX,o.lastY,"teardown")}catch{}n.clear(),i.length=0}}};var W="sazabi.session",ae="sazabi.windowId",F="sazabi.distinctId",ht=1800000,yt=86400000,bt=60000,vt=5000,xt=()=>{let e=new Map;return{get:(t)=>e.get(t)??null,set:(t,r)=>{e.set(t,r)},remove:(t)=>{e.delete(t)}}},St=(e)=>{if(!e)return;try{let t=JSON.parse(e);if(typeof t==="object"&&t!==null&&typeof t.id==="string"&&typeof t.startedAt==="number"&&typeof t.lastActivityAt==="number")return t}catch{}return},wt=(e)=>{let{store:t,windowStore:r,randomBytes:n}=e,i=0,a=()=>{let p=Date.now(),c=St(t.get(W));if(c&&p-c.lastActivityAt<=ht&&p-c.lastActivityAt>=-bt&&p-c.startedAt<=yt)return c;let x={id:C(n,"sess"),startedAt:p,lastActivityAt:p};return t.set(W,JSON.stringify(x)),i=p,x},l=r.get(ae);if(!l)l=C(n,"win"),r.set(ae,l);let h=()=>{let p=Date.now();t.set(W,JSON.stringify({id:C(n,"sess"),startedAt:p,lastActivityAt:p})),i=p,l=C(n,"win"),r.set(ae,l)};return{getSessionId(){return a().id},getWindowId(){return l},getDistinctId(){return t.get(F)??void 0},setDistinctId(p){let c=t.get(F)??void 0;if(c!==void 0&&c!==p)h();t.set(F,p)},reset(){t.remove(F),h()},touch(){let p=Date.now(),c=a();if(p-i>=vt)t.set(W,JSON.stringify({...c,lastActivityAt:p})),i=p}}};var Rt=1,kt=/^[0-9a-f]{32}$/,At=/^[0-9a-f]{16}$/,Tt="0".repeat(32),Et="0".repeat(16),$e=(e,t)=>{let r;do r=e(t);while(r.every((n)=>n===0));return V(r)},_t=(e)=>{if(typeof e!=="object"||e===null)return!1;let{traceId:t,spanId:r,traceFlags:n}=e;return typeof t==="string"&&kt.test(t)&&t!==Tt&&typeof r==="string"&&At.test(r)&&r!==Et&&typeof n==="number"},Ct=Symbol.for("opentelemetry.js.api.1"),It=Symbol.for("OpenTelemetry Context Key SPAN"),Be=()=>{try{let n=globalThis[Ct]?.context?.active?.()?.getValue?.(It)?.spanContext?.();return _t(n)?n:void 0}catch{return}},Ot=(e)=>{let t=(e.traceFlags&255).toString(16).padStart(2,"0");return`00-${e.traceId}-${e.spanId}-${t}`},Mt=(e)=>{try{return e.traceState?.serialize()||void 0}catch{return}},Pt=(e)=>{let t=()=>({traceId:$e(e.randomBytes,16),spanId:$e(e.randomBytes,8),traceFlags:e.sampledFlag?Rt:0});return{forRequest(){let r=Be()??t(),n={traceparent:Ot(r)},i=Mt(r);if(i)n.tracestate=i;return{headers:n,traceId:r.traceId,spanId:r.spanId}}}};var Nt=(e)=>{let t=new Set;return(r)=>{if(t.has(r.diagnostic))return;t.add(r.diagnostic);try{e({type:"sdk_health",body:`sdk_health ${r.diagnostic}`,severity:"INFO",timeUnixMs:Date.now(),attributes:{["app.sdk.diagnostic"]:r.diagnostic,...r.attributes}})}catch{}}};var Lt={DEBUG:5,INFO:9,WARN:13,ERROR:17},Dt=1024,Ut=(e)=>{if(typeof e==="boolean")return{boolValue:e};if(typeof e==="number")return Number.isInteger(e)?{intValue:String(e)}:{doubleValue:e};return{stringValue:I(e)}},Y=(e)=>{let t=[];for(let[r,n]of Object.entries(e)){if(n===void 0)continue;t.push({key:r,value:Ut(n)})}return t},Ht=(e,t)=>{let r={timeUnixNano:`${e.timeUnixMs}000000`,severityNumber:Lt[e.severity],severityText:e.severity,body:{stringValue:I(e.body,Dt)},attributes:Y({...t,...e.attributes})};if(e.traceId)r.traceId=e.traceId;if(e.spanId)r.spanId=e.spanId;return r},ce=(e)=>{let t=e.length;for(let r=0;r<e.length;r+=1){let n=e.charCodeAt(r);if(n<128)continue;if(n<2048)t+=1;else if(n>=55296&&n<=56319&&(e.charCodeAt(r+1)&64512)===56320)t+=2,r+=1;else t+=2}return t},qt=4,Ve=1048576,Xt=30000,$t=1000,Bt=60000,Vt=300000,Wt="app.sdk.dropped_events",Ft="x-sazabi-sdk",de={kind:"retry",network:!0},Yt=(e)=>{try{let t=e.headers?.get?.("retry-after");if(!t)return;let r=Number(t),n=Number.isFinite(r)?r*1000:Date.parse(t)-Date.now();return n>0?Math.min(n,Vt):void 0}catch{return}},Kt=(e)=>{let t=e?.status;if(typeof t!=="number"||t>=200&&t<300)return{kind:"sent"};if(t===408||t===429||t>=500)return{kind:"retry",retryAfterMs:Yt(e)};return{kind:t===413?"split":"drop",status:t}},jt=(e)=>{let t=[],r=0,n=0,i,a=!1,l=!1,h=new Set,p=new Set,c=!1,x=0,A=0,S=0,o=0,f=new Set,v=(s)=>{try{e.onHealth?.(s)}catch{}},b=(s,u)=>{if(f.has(s))return;f.add(s);try{e.warn?.(u)}catch{}},y=(s)=>JSON.stringify({resourceLogs:[{resource:{attributes:Y(e.resourceAttributes)},scopeLogs:[{scope:{name:e.scope.name,version:e.scope.version},logRecords:s}]}]}),R=ce(y([]))+64,_=Math.max(4096,e.maxBatchBytes-R),K=()=>{if(i!==void 0)clearTimeout(i),i=void 0},D=()=>{if(i!==void 0||a)return;let s=S-Date.now();i=setTimeout(()=>{i=void 0,q()},s>0?s:e.flushIntervalMs)},ue=()=>{while(t.length>e.maxQueuedEvents||r>Ve){let s=t.shift();if(r-=s.bytes,x+=1,s.saved)c=!0;b("full","the event queue is full (is the device offline?); dropping the oldest events.")}},pe=(s,u)=>{let g=[];if(u<=0)return g;let d=[],m=0;for(let w of s){if(d.length>0&&m+w.bytes>_){if(g.push(d),d=[],m=0,g.length>=u)break}d.push(w),m+=w.bytes}if(d.length>0)g.push(d);return g},j=(s)=>{let u=pe(t,s),g=0;for(let d of u)g+=d.length;for(let d of t.splice(0,g))r-=d.bytes;return u},le=(s,u)=>u.length===0?s:{...s,attributes:[...s.attributes.filter((g)=>!u.some(({key:d})=>d===g.key)),...u]},fe=(s)=>{let u=s.map((m)=>m.record),g=x,d=u[0];if(g>0&&d)x=0,u[0]={...d,attributes:[...d.attributes,{key:Wt,value:{intValue:String(g)}}]};return{records:u,count:d?g:0}},me=(s,u=!1)=>{let g=globalThis.AbortController,d,m;try{d=g?new g:void 0,m=e.fetchImpl(e.url,{method:"POST",headers:{"content-type":"application/json",authorization:`Bearer ${e.publicKey}`,[Ft]:`${e.scope.name}/${e.scope.version}`},body:s,keepalive:u,signal:d?.signal})}catch{return Promise.resolve(de)}return new Promise((w)=>{let X=setTimeout(()=>{try{d?.abort?.()}catch{}w(de)},Xt);Promise.resolve(m).then(Kt,()=>de).then((Ye)=>{clearTimeout(X),w(Ye)})})},U=()=>Date.now()<S,ge=(s)=>{t.push(...s),t.sort((u,g)=>u.seq-g.seq);for(let u of s)r+=u.bytes;ue()},We=(s,u)=>{let g=Date.now();if(g>=S){if(A+=1,u)o+=1}let d=Math.min(Bt,$t*2**(A-1)),m=Math.max(s??0,d/2+Math.random()*d/2);S=Math.max(S,g+m),K(),D()},he=async(s,u)=>{let{records:g,count:d}=fe(s),m=await me(y(g),u);if(o>0&&(m.kind!=="retry"||m.network!==!0)){let w=o;o=0,v({diagnostic:"intake_unreachable",attributes:{"app.sdk.attempts":w}})}if(m.kind==="sent"){A=0;return}if(x+=d,m.kind==="split"&&s.length>1){let w=Math.ceil(s.length/2);for(let X of[s.slice(0,w),s.slice(w)])if(!u&&!a&&U())ge(X),D();else await he(X,u);return}if(m.kind==="retry"){if(a){if(s.some((w)=>w.saved))c=!0;return}ge(s),We(m.retryAfterMs,m.network===!0);return}x+=s.length,b(String(m.status),`the intake refused a batch with HTTP ${m.status}; dropped ${s.length} event(s).`),v({diagnostic:"intake_rejected",attributes:{"app.sdk.http_status":m.status}})},z=0,G=new Map,ye=(s,u)=>{let g=z;z+=1;let d=he(s,u).then(()=>{if(h.delete(d),p.delete(s),G.delete(g),t.length===0&&h.size===0&&!c)try{e.onIdle?.()}catch{}});return h.add(d),p.add(s),G.set(g,d),d},H=null,Fe=async()=>{while(!U()){for(let s of j(qt-h.size))ye(s,!1);if(t.length===0)break;await Promise.race(h)}},q=async(s)=>{K();let u=s?.keepalive===!0;if(u||a){await Promise.all(j(Number.POSITIVE_INFINITY).map((m)=>ye(m,u)));return}let g=z;while(t.length>0&&!U()){if(H===null)H=Fe().finally(()=>{H=null});await H}if(t.length>0)D();let d=[];for(let[m,w]of G)if(m>=g)d.push(w);await Promise.all(d)};return{enqueue(s){if(a)return;let u=ce(JSON.stringify(s))+1;if(u>Ve){x+=1,b("oversized","dropped an event larger than the 1 MiB queue limit.");return}if(t.push({record:s,bytes:u,seq:n}),n+=1,r+=u,ue(),l)return;if(r>=_&&!U())q();else D()},flush:q,drainEnvelopes(s){K();let u=s?Y(s):[];return j(Number.POSITIVE_INFINITY).map((g)=>{let{records:d}=fe(g);return y(d.map((m)=>le(m,u)))})},snapshotEnvelopes(s){let u=Y(s);c=!1;for(let d of t){d.record=le(d.record,u);let m=ce(JSON.stringify(d.record))+1;r+=m-d.bytes,d.bytes=m,d.saved=!0}let g=new Set(t);for(let d of p)for(let m of d)if(m.saved)g.add(m);return pe([...g].sort((d,m)=>d.seq-m.seq),Number.POSITIVE_INFINITY).map((d)=>y(d.map((m)=>m.record)))},holdFlushes(s){l=s},async sendEnvelope(s){let u=await me(s);if(u.kind==="retry")return!1;if(u.kind!=="sent")b(String(u.status),`the intake refused a stored crash report with HTTP ${u.status}; dropped it.`),v({diagnostic:"intake_rejected",attributes:{"app.sdk.http_status":u.status}});return!0},async shutdown(){a=!0,await q()}}};import{NativeModules as zt,Platform as L,TurboModuleRegistry as Gt}from"react-native";var Yr=()=>L.OS==="web",N=(e)=>typeof e==="string"&&e.length>0?e:void 0,Kr=()=>{let e=L.constants??{},t=L.OS==="ios";return{"os.name":L.OS,"os.version":N(String(L.Version??"")),"device.manufacturer":t?"Apple":N(e.Brand)??N(e.Manufacturer),"device.model.identifier":N(e.Model),"device.type":t?N(e.interfaceIdiom):void 0}},Qt=()=>globalThis.__DEV__===!0,jr=()=>{if(!Qt())return;try{let e=Gt?.get?.("SourceCode")??zt?.SourceCode,t=e?.getConstants?.().scriptURL??e?.scriptURL,r=typeof t==="string"?k(t):void 0;return r&&r.protocol.startsWith("http")?`${r.origin}/`:void 0}catch{return}},zr=(e)=>k(e)?.href;var Jt=()=>{oe()},Jr=()=>{se(),ne()},Zr=(e)=>(Jt(),te(e),{teardown(){re()}});
2
+ export{I as a,be as b,je as c,Qe as d,J as e,nt as f,_e as g,ct as h,Ue as i,gt as j,xt as k,wt as l,Pt as m,Nt as n,Ht as o,jt as p,Yr as q,Kr as r,Qt as s,jr as t,zr as u,Jt as v,Jr as w,Zr as x};
3
+
4
+ //# debugId=59FDD26FD27CC55664756E2164756E21
5
+ //# sourceMappingURL=chunk-c8j7sr2j.js.map
@@ -0,0 +1,24 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../client-sdk-core/src/attributes.ts", "../../client-sdk-core/src/patch-marker.ts", "../../client-sdk-core/src/console.ts", "../../client-sdk-core/src/url.ts", "../../client-sdk-core/src/intake-url.ts", "../../client-sdk-core/src/clock.ts", "../../client-sdk-core/src/network-capture.ts", "../../client-sdk-core/src/random.ts", "../../client-sdk-core/src/pointer.ts", "../../client-sdk-core/src/session.ts", "../../client-sdk-core/src/trace-context.ts", "../../client-sdk-core/src/sdk-health.ts", "../../client-sdk-core/src/transport.ts", "../src/host.ts", "../src/network.ts"],
4
+ "sourcesContent": [
5
+ "import type { EventAttributes } from \"./types\";\n\nconst MAX_VALUE_LENGTH = 4096;\nconst MAX_FLATTEN_DEPTH = 4;\n\nexport const truncateValue = (value: string, max = MAX_VALUE_LENGTH): string =>\n value.length > max ? value.slice(0, max) : value;\n\n/**\n * Flatten nested metadata into dot-keyed scalar attributes. Non-scalar leaves\n * are JSON-stringified; strings are length-capped so a hostile or buggy app\n * cannot inflate rows.\n */\nexport const flattenAttributes = (\n input: Record<string, unknown>,\n prefix = \"\",\n depth = 0,\n): EventAttributes => {\n const result: EventAttributes = {};\n\n for (const [key, rawValue] of Object.entries(input)) {\n const fullKey = prefix ? `${prefix}.${key}` : key;\n\n if (rawValue === null || rawValue === undefined) {\n continue;\n }\n\n if (\n typeof rawValue === \"object\" &&\n !Array.isArray(rawValue) &&\n depth < MAX_FLATTEN_DEPTH\n ) {\n Object.assign(\n result,\n flattenAttributes(\n rawValue as Record<string, unknown>,\n fullKey,\n depth + 1,\n ),\n );\n continue;\n }\n\n if (typeof rawValue === \"number\" || typeof rawValue === \"boolean\") {\n result[fullKey] = rawValue;\n } else if (typeof rawValue === \"string\") {\n result[fullKey] = truncateValue(rawValue);\n } else {\n try {\n result[fullKey] = truncateValue(JSON.stringify(rawValue) ?? \"\");\n } catch {\n // Circular or unserializable: drop rather than throw into the host.\n }\n }\n }\n\n return result;\n};\n",
6
+ "/**\n * Shared across every Sazabi client SDK so two SDKs bundled into one app\n * recognize each other's wrappers and never double-wrap a primitive.\n */\nexport const WRAPPED_MARKER = \"__sazabiBrowserWrapped\";\n\n/** Best-effort idempotency marker on wrapper functions. */\nexport const markWrapped = (fn: object): void => {\n try {\n Object.defineProperty(fn, WRAPPED_MARKER, { value: true });\n } catch {\n // Non-extensible function object: the idempotency guard degrades gracefully.\n }\n};\n\nexport const isWrapped = (fn: unknown): boolean =>\n typeof fn === \"function\" &&\n (fn as unknown as Record<string, unknown>)[WRAPPED_MARKER] === true;\n",
7
+ "import { isWrapped, markWrapped } from \"./patch-marker\";\nimport type { ClientEvent, ConsoleCaptureLevel, EventSeverity } from \"./types\";\n\nexport interface ConsoleCaptureOptions {\n emit(event: ClientEvent): void;\n levels: ConsoleCaptureLevel[];\n /** Lines the SDK itself prints start with this and are never re-captured. */\n selfPrefix: string;\n}\n\nexport interface ConsoleCaptureHandle {\n teardown(): void;\n}\n\nconst SEVERITY_BY_LEVEL: Record<ConsoleCaptureLevel, EventSeverity> = {\n error: \"ERROR\",\n warn: \"WARN\",\n info: \"INFO\",\n debug: \"DEBUG\",\n};\n\n/** Nonzero while `withoutConsoleCapture` runs: those lines are never mirrored. */\nlet uncaptured = 0;\n\n/**\n * Print through the console without mirroring the line into the event\n * stream: for a host-facing message the SDK prints on another component's\n * behalf (the dev-mode rejection warning it relays for React Native).\n */\nexport const withoutConsoleCapture = (print: () => void): void => {\n uncaptured += 1;\n try {\n print();\n } finally {\n uncaptured -= 1;\n }\n};\n\nconst MAX_ARG_CHARS = 1_000;\nconst MAX_BODY_CHARS = 4_000;\n\nconst stringifyArg = (value: unknown): string => {\n let text: string;\n if (typeof value === \"string\") {\n text = value;\n } else if (value instanceof Error) {\n text = `${value.name}: ${value.message}`;\n } else {\n try {\n text = JSON.stringify(value) ?? String(value);\n } catch {\n text = String(value);\n }\n }\n return text.length > MAX_ARG_CHARS\n ? `${text.slice(0, MAX_ARG_CHARS)}…`\n : text;\n};\n\n/**\n * Mirror selected console levels into the event stream as `log` events.\n * The original console method always runs first (devtools behavior is never\n * altered) and capture failures are swallowed. Lines the SDK itself prints\n * (prefixed with `selfPrefix`) are skipped to avoid self-capture.\n */\nexport const installConsoleCapture = (\n options: ConsoleCaptureOptions,\n): ConsoleCaptureHandle => {\n const restores: Array<() => void> = [];\n let emitting = false;\n\n for (const level of options.levels) {\n const original = console[level];\n if (typeof original !== \"function\" || isWrapped(original)) {\n continue;\n }\n\n const wrapped = function (this: unknown, ...args: unknown[]): void {\n original.apply(this ?? console, args);\n if (emitting || uncaptured > 0) return;\n try {\n if (\n typeof args[0] === \"string\" &&\n args[0].startsWith(options.selfPrefix)\n ) {\n return;\n }\n emitting = true;\n const body = args.map(stringifyArg).join(\" \").slice(0, MAX_BODY_CHARS);\n options.emit({\n type: \"log\",\n body: body || `console.${level}`,\n severity: SEVERITY_BY_LEVEL[level],\n timeUnixMs: Date.now(),\n attributes: {\n \"app.console.level\": level,\n },\n });\n } catch {\n // Capture failures must never surface to the host.\n } finally {\n emitting = false;\n }\n };\n\n markWrapped(wrapped);\n try {\n console[level] = wrapped as typeof original;\n } catch {\n // A frozen console, or a non-writable method, stays unwrapped: that\n // level is not mirrored and init() carries on.\n continue;\n }\n restores.push(() => {\n if (console[level] === wrapped) {\n console[level] = original;\n }\n });\n }\n\n return {\n teardown() {\n for (const restore of restores) {\n try {\n restore();\n } catch {\n // Best-effort restore.\n }\n }\n },\n };\n};\n",
8
+ "/**\n * Absolute-URL parsing with no `URL` global. React Native's built-in `URL`\n * throws \"not implemented\" for `hostname`, `pathname`, and `origin`, and the\n * core must work there unpolyfilled, so the few fields the SDKs read are\n * parsed here. Only absolute http(s)-style URLs resolve; a binding resolves\n * relative references against its own base before calling in.\n */\n\nexport interface ParsedUrl {\n /** e.g. `https:` */\n protocol: string;\n /** host with port, no credentials */\n host: string;\n hostname: string;\n pathname: string;\n search: string;\n hash: string;\n origin: string;\n /** Normalized absolute URL with credentials stripped. */\n href: string;\n}\n\nconst ABSOLUTE_URL =\n /^([a-z][a-z0-9+.-]*):\\/\\/([^/?#]*)([^?#]*)(\\?[^#]*)?(#.*)?$/i;\n\nconst DEFAULT_PORTS: Record<string, string> = {\n \"https:\": \":443\",\n \"http:\": \":80\",\n};\n\n/**\n * `https://host:443` and `https://host` are one origin to every URL parser,\n * so they must be one here too: the browser binding compares a request URL\n * the host normalized against an intake base parsed here, and a default port\n * left in place would let the SDK's own uploads pass as app traffic.\n */\nconst withoutDefaultPort = ({\n host,\n protocol,\n}: {\n host: string;\n protocol: string;\n}): string => {\n const suffix = DEFAULT_PORTS[protocol];\n return suffix && host.endsWith(suffix) ? host.slice(0, -suffix.length) : host;\n};\n\nexport const parseAbsoluteUrl = (raw: string): ParsedUrl | undefined => {\n const match = ABSOLUTE_URL.exec(raw.trim());\n if (!match) return undefined;\n const [, scheme, authority, path, search = \"\", hash = \"\"] = match;\n if (!scheme || authority === undefined) return undefined;\n const at = authority.lastIndexOf(\"@\");\n const protocol = `${scheme.toLowerCase()}:`;\n const host = withoutDefaultPort({\n host: (at === -1 ? authority : authority.slice(at + 1)).toLowerCase(),\n protocol,\n });\n if (!host) return undefined;\n const hostname = host.startsWith(\"[\")\n ? host.slice(0, host.indexOf(\"]\") + 1)\n : host.replace(/:\\d*$/, \"\");\n const pathname = path || \"/\";\n const origin = `${protocol}//${host}`;\n return {\n protocol,\n host,\n hostname,\n pathname,\n search,\n hash,\n origin,\n href: `${origin}${pathname}${search}${hash}`,\n };\n};\n\n/**\n * Whether `url` is `base` or lies under it: the same origin (scheme, host,\n * port) and, when the base has a path, that path as whole segments. A string\n * prefix would also match `https://intake.example.com.evil.test/`.\n */\nexport const isUnderBase = ({\n url,\n base,\n}: {\n url: string;\n base: string;\n}): boolean => {\n const target = parseAbsoluteUrl(url);\n const root = parseAbsoluteUrl(base);\n if (!target || !root || target.origin !== root.origin) return false;\n const prefix = root.pathname.replace(/\\/+$/, \"\");\n return (\n !prefix ||\n target.pathname === prefix ||\n target.pathname.startsWith(`${prefix}/`)\n );\n};\n",
9
+ "/**\n * Resolving the intake endpoint from a single URL.\n *\n * Every Sazabi log source is configured with one intake URL, and public keys\n * stay a low-level detail customers never handle. The key is already in the\n * URL: the intake edge reconstructs it from the hostname\n * (terraform/main/cloudfront-functions/intake-router.js), so the SDK does the\n * same reconstruction locally and sends the Bearer header itself.\n *\n * Two keyed shapes exist, matching the edge's two grammars:\n * {hex}.{region}.intake.{domain} prefixless (what log sources issue)\n * {adapter}-{hex}.{region}.intake.{domain} keyed-adapter (legacy, still served)\n *\n * A URL in neither shape is not an error: it may be a proxy or a custom\n * endpoint. The SDK then requires an explicit `publicKey`, so a\n * misconfiguration surfaces as a clear warning rather than silent no-auth.\n */\n\nimport { parseAbsoluteUrl } from \"./url\";\n\nconst KEYED_HOST = /^([0-9a-f]{32})\\./;\nconst KEYED_ADAPTER_HOST = /^[a-z0-9-]+?-([0-9a-f]{32})\\./;\n\nexport interface ResolvedIntakeEndpoint {\n /**\n * Base to POST to: origin plus any proxy path prefix, no trailing slash.\n * The transport appends `/v1/logs`.\n */\n baseUrl: string;\n /** Credential for the Authorization header, derived or explicit. */\n publicKey: string;\n}\n\n/** Public key embedded in a keyed intake hostname, if it carries one. */\nexport const publicKeyFromIntakeUrl = (url: string): string | undefined => {\n const host = parseAbsoluteUrl(url)?.hostname;\n if (!host) return undefined;\n const hex = (KEYED_HOST.exec(host) ?? KEYED_ADAPTER_HOST.exec(host))?.[1];\n return hex ? `sazabi_public_${hex}` : undefined;\n};\n\n/**\n * Normalize a configured intake URL to a base the transport can suffix with\n * `/v1/logs`: drop a terminal `/v1/logs` so both the bare host and the full\n * OTLP endpoint work (the two forms customers copy from setup instructions),\n * and drop query/hash, which the OTLP POST has no use for.\n *\n * A path prefix is preserved. Reducing this to `origin` would break the\n * documented proxy escape hatch two ways: records for a proxy mounted at\n * `https://example.com/otel` would go to `/v1/logs`, and the internal-request\n * filter (a `startsWith` on this base) would suppress network capture for\n * every request sharing that origin, including the customer's own API traffic.\n *\n * Only `http:` and `https:` resolve. Any other scheme would leave the SDK\n * looking initialized while `fetch` rejects the protocol and the transport\n * swallows the error, dropping every event; failing closed here surfaces the\n * misconfiguration as the same init warning as a missing URL.\n */\nconst SIGNAL_PATH = \"/v1/logs\";\n\nexport const intakeBaseFromUrl = (url: string): string | undefined => {\n const parsed = parseAbsoluteUrl(url);\n if (!parsed) {\n return undefined;\n }\n if (parsed.protocol !== \"https:\" && parsed.protocol !== \"http:\") {\n return undefined;\n }\n let path = parsed.pathname.replace(/\\/+$/, \"\");\n if (path.endsWith(SIGNAL_PATH)) {\n path = path.slice(0, -SIGNAL_PATH.length);\n }\n return `${parsed.origin}${path}`;\n};\n\n/**\n * Resolve the endpoint from config. An explicit `publicKey` always wins, so a\n * non-keyed or proxied endpoint stays configurable.\n */\nexport const resolveIntakeEndpoint = (config: {\n intakeUrl?: string;\n intakeHost?: string;\n publicKey?: string;\n}): ResolvedIntakeEndpoint | undefined => {\n const configured = config.intakeUrl ?? config.intakeHost;\n if (!configured) return undefined;\n\n const baseUrl = intakeBaseFromUrl(configured);\n if (!baseUrl) return undefined;\n\n const publicKey = config.publicKey ?? publicKeyFromIntakeUrl(configured);\n if (!publicKey) return undefined;\n\n return { baseUrl, publicKey };\n};\n",
10
+ "/**\n * Monotonic milliseconds for durations and ordering. `performance.now()` where\n * the host has it (every browser, React Native on Hermes); `Date.now()` as the\n * fallback so the core never assumes a global.\n */\nexport const monotonicNow = (): number => {\n const perf = (globalThis as { performance?: { now?: () => number } })\n .performance;\n return typeof perf?.now === \"function\" ? perf.now() : Date.now();\n};\n",
11
+ "import { monotonicNow } from \"./clock\";\nimport { isWrapped, markWrapped } from \"./patch-marker\";\nimport type { RequestTraceContext, TraceContextManager } from \"./trace-context\";\nimport type { ClientEvent, EventSeverity } from \"./types\";\nimport { isUnderBase, parseAbsoluteUrl } from \"./url\";\n\n/**\n * Everything the network patches need at request time. A binding builds one\n * at init and activates it; the patches themselves install dormant (possibly\n * before init, via a register entrypoint) and consult the active slot per\n * call. Undefined slot = transparent passthrough.\n */\nexport interface NetworkCaptureOptions {\n emit(event: ClientEvent): void;\n traceContext: TraceContextManager;\n /** Emit network events. */\n capture: boolean;\n /** Inject `traceparent` into allowlisted requests. */\n propagateTraceContext: boolean;\n /** Injection allowlist; empty defers to `isDefaultInjectionTarget`. */\n allowlist: (string | RegExp)[];\n /** Query parameter names redacted from `url.full` beside the default list. */\n redactQueryParams?: (string | RegExp)[];\n /** Response headers probed for a platform request id, in priority order. */\n requestIdHeaders: string[];\n /** SDK-internal endpoints: never captured, never injected. */\n isInternalUrl(url: string): boolean;\n /**\n * Absolute URL for a request target as the host resolves it, with embedded\n * credentials stripped; undefined when the input cannot be parsed.\n */\n resolveUrl(raw: string): string | undefined;\n /** Injection policy when the allowlist is empty (same-origin on the web). */\n isDefaultInjectionTarget(resolved: string): boolean;\n /** Origin elided from the display path in event bodies, if the host has one. */\n displayOrigin?: string;\n /** Host-reported offline state; a failure while offline reads as an abort. */\n isOffline?(): boolean;\n}\n\nlet active: NetworkCaptureOptions | undefined;\n\nexport const activateNetworkCapture = (\n options: NetworkCaptureOptions,\n): void => {\n active = options;\n};\n\nexport const deactivateNetworkCapture = (): void => {\n active = undefined;\n};\n\nexport const getActiveNetworkCapture = (): NetworkCaptureOptions | undefined =>\n active;\n\n// --------------------------------------------------------------- display --\n\n/** Compact display path: pathname when on the display origin, host+path otherwise. */\nexport const urlPath = (resolved: string, displayOrigin?: string): string => {\n const url = parseAbsoluteUrl(resolved);\n if (!url) return resolved;\n return displayOrigin !== undefined && url.origin === displayOrigin\n ? url.pathname\n : `${url.host}${url.pathname}`;\n};\n\n/** Status 0 means transport failure. */\nexport const severityForStatus = (status: number): EventSeverity => {\n if (status === 0 || status >= 500) return \"ERROR\";\n if (status >= 400) return \"WARN\";\n return \"INFO\";\n};\n\n/**\n * Query and fragment parameters whose values never leave the device: bearer\n * tokens, OAuth codes, API keys, and presigned-URL signatures. Matched by\n * name, case-insensitively; `network.redactQueryParams` adds to the list.\n */\nconst REDACTED_QUERY_PARAMS = new Set([\n \"token\",\n \"access_token\",\n \"refresh_token\",\n \"id_token\",\n \"api_key\",\n \"apikey\",\n \"key\",\n \"secret\",\n \"password\",\n \"signature\",\n \"sig\",\n \"x-amz-signature\",\n \"x-amz-credential\",\n \"x-goog-signature\",\n \"sentry_key\",\n \"authorization\",\n \"auth\",\n \"session\",\n \"sid\",\n \"code\",\n]);\n\n/**\n * `RegExp#test` from the start of `value` every time. A global or sticky\n * pattern resumes from the `lastIndex` its previous match left, so an app's\n * `/^tenant$/g` would match one parameter and let the next one through.\n * Reset only for those flags: a frozen plain RegExp stays untouched.\n */\nconst testPattern = (pattern: RegExp, value: string): boolean => {\n if (pattern.global || pattern.sticky) pattern.lastIndex = 0;\n return pattern.test(value);\n};\n\n/**\n * A recorded URL: the value of every sensitive parameter after the path\n * replaced with `[redacted]`, in the query and in the fragment. `?` separates\n * inside the fragment as well, so a hash router's `#/reset?token=…` is\n * covered. Credentials in the authority are already gone (`resolveUrl`\n * strips them). Applied to `url.full` of requests, resource rows and\n * navigations, and to the page URL the browser SDK stamps on every event.\n */\nexport const redactUrl = (\n url: string,\n extra: (string | RegExp)[] = [],\n): string => {\n const start = url.search(/[?#]/);\n if (start === -1) return url;\n const sensitive = (raw: string): boolean => {\n let name = raw;\n try {\n name = decodeURIComponent(raw);\n } catch {\n // Keep the raw name.\n }\n name = name.toLowerCase();\n return (\n REDACTED_QUERY_PARAMS.has(name) ||\n extra.some((pattern) =>\n typeof pattern === \"string\"\n ? pattern.toLowerCase() === name\n : testPattern(pattern, name),\n )\n );\n };\n return (\n url.slice(0, start) +\n url\n .slice(start)\n .replace(/([?#&])([^=&#?]+)=([^&#]*)/g, (param, separator, name) =>\n sensitive(name) ? `${separator}${name}=[redacted]` : param,\n )\n );\n};\n\n// ------------------------------------------------------------- injection --\n\n/**\n * One allowlist entry against a request URL. A RegExp is tested against the\n * full URL; a string with a scheme is an origin (plus an optional path\n * prefix); any other string is a host (`api.example.com`, with a port when it\n * names one) or, with a leading dot, a domain and its subdomains\n * (`.example.com`).\n */\nconst matchesAllowlistEntry = (\n resolved: string,\n entry: string | RegExp,\n): boolean => {\n if (typeof entry !== \"string\") return testPattern(entry, resolved);\n if (entry.includes(\"://\")) {\n return isUnderBase({ url: resolved, base: entry });\n }\n const url = parseAbsoluteUrl(resolved);\n if (!url) return false;\n const host = entry.toLowerCase();\n if (host.startsWith(\".\")) {\n return url.hostname === host.slice(1) || url.hostname.endsWith(host);\n }\n return (host.includes(\":\") ? url.host : url.hostname) === host;\n};\n\nexport const isAllowedForInjection = (\n options: NetworkCaptureOptions,\n resolved: string,\n): boolean => {\n if (options.allowlist.length === 0) {\n try {\n return options.isDefaultInjectionTarget(resolved);\n } catch {\n return false;\n }\n }\n return options.allowlist.some((entry) =>\n matchesAllowlistEntry(resolved, entry),\n );\n};\n\n/** Injection eligibility for the active config; used by the browser's resource\n * observer to decide whether a bypassed request is a diagnosable gap. */\nexport const isActiveInjectionTarget = (resolved: string): boolean => {\n const options = active;\n if (!options?.propagateTraceContext) return false;\n if (options.isInternalUrl(resolved)) return false;\n return isAllowedForInjection(options, resolved);\n};\n\n// ----------------------------------------------------- wrapper-seen ledger --\n// Every request processed by an active wrapper is recorded so a passive\n// observer (resource timing in the browser) can tell wrapper-captured traffic\n// from traffic that bypassed the patches entirely.\n\ninterface SeenRequest {\n url: string;\n /** monotonicNow() timebase, comparable to PerformanceEntry.startTime. */\n startTime: number;\n}\n\nconst SEEN_MATCH_WINDOW_MS = 5_000;\nconst SEEN_MAX_ENTRIES = 300;\nlet seenByWrapper: SeenRequest[] = [];\n\nexport const recordWrapperRequest = (url: string): void => {\n seenByWrapper.push({ url, startTime: monotonicNow() });\n if (seenByWrapper.length > SEEN_MAX_ENTRIES) {\n seenByWrapper = seenByWrapper.slice(-SEEN_MAX_ENTRIES);\n }\n};\n\n/** Consume a wrapper-seen record matching this request, if any. */\nexport const consumeWrapperSeen = (url: string, startTime: number): boolean => {\n const index = seenByWrapper.findIndex(\n (seen) =>\n seen.url === url &&\n Math.abs(seen.startTime - startTime) <= SEEN_MATCH_WINDOW_MS,\n );\n if (index === -1) return false;\n seenByWrapper.splice(index, 1);\n return true;\n};\n\nexport const resetWrapperLedger = (): void => {\n seenByWrapper = [];\n};\n\n// ---------------------------------------------------------------- events --\n\nexport interface NetworkEventDetails {\n kind: \"fetch\" | \"xhr\";\n method: string;\n resolvedUrl: string;\n status: number;\n durationMs: number;\n severity?: EventSeverity;\n failureMessage?: string;\n injected?: RequestTraceContext;\n requestId?: string;\n requestIdHeader?: string;\n}\n\nexport const emitNetworkEvent = (\n options: NetworkCaptureOptions,\n details: NetworkEventDetails,\n): void => {\n try {\n const path = urlPath(details.resolvedUrl, options.displayOrigin);\n const outcome = details.status > 0 ? String(details.status) : \"failed\";\n const event: ClientEvent = {\n type: \"network\",\n body: `${details.method} ${path} → ${outcome}`,\n severity: details.severity ?? severityForStatus(details.status),\n timeUnixMs: Date.now(),\n attributes: {\n \"http.request.method\": details.method,\n \"http.response.status_code\":\n details.status > 0 ? details.status : undefined,\n \"url.full\": redactUrl(details.resolvedUrl, options.redactQueryParams),\n \"app.network.kind\": details.kind,\n \"app.network.duration_ms\": Math.round(details.durationMs),\n \"app.network.error\": details.failureMessage,\n \"app.network.request_id\": details.requestId,\n \"app.network.request_id_header\": details.requestIdHeader,\n },\n };\n if (details.injected) {\n event.traceId = details.injected.traceId;\n event.spanId = details.injected.spanId;\n }\n options.emit(event);\n } catch {\n // Capture failures must never surface to the host.\n }\n};\n\nexport const readRequestId = (\n options: NetworkCaptureOptions,\n getHeader: (name: string) => string | null | undefined,\n): { requestId?: string; requestIdHeader?: string } => {\n try {\n for (const name of options.requestIdHeaders) {\n const value = getHeader(name);\n if (value) return { requestId: value, requestIdHeader: name };\n }\n } catch {}\n return {};\n};\n\nexport const isAborted = (\n error: unknown,\n signalAborted?: boolean,\n offline?: boolean,\n): boolean => {\n if (signalAborted || offline) {\n return true;\n }\n const e = error as { name?: string; message?: string } | undefined;\n return (\n e?.name === \"AbortError\" ||\n (typeof e?.message === \"string\" && /abort|cancel/i.test(e.message))\n );\n};\n\n// ------------------------------------------------------------------ XHR --\n// The one network patch every host shares: browsers have XMLHttpRequest\n// natively, and React Native's fetch is a polyfill over its own\n// XMLHttpRequest class, so this patch alone sees every mobile request.\n\ninterface XhrRequestState {\n method: string;\n resolvedUrl?: string;\n customTraceparent: boolean;\n injected?: RequestTraceContext;\n start: number;\n /** Set by the outermost of our send wrappers in a third-party sandwich. */\n processed: boolean;\n aborted?: boolean;\n /** The capture instance that sent the request; only it may emit the row. */\n owner?: NetworkCaptureOptions;\n}\n\n/**\n * The XMLHttpRequest surface the patch touches, declared structurally so the\n * same source typechecks against the DOM lib (browser SDK) and against no\n * lib at all (this package).\n */\ninterface XhrLike {\n open(...args: unknown[]): void;\n send(body?: unknown): void;\n setRequestHeader(name: string, value: string): void;\n getResponseHeader(name: string): string | null;\n addEventListener(type: string, listener: () => void): void;\n readonly status: number;\n readonly readyState: number;\n}\n\nconst XHR_DONE = 4;\n\nconst xhrState = new WeakMap<XhrLike, XhrRequestState>();\n\ninterface XhrOutcome {\n status: number;\n end: number;\n requestId: { requestId?: string; requestIdHeader?: string };\n}\n\nconst outcomeOf = (xhr: XhrLike, owner: NetworkCaptureOptions): XhrOutcome => ({\n status: xhr.status,\n end: monotonicNow(),\n requestId: readRequestId(owner, (name) => xhr.getResponseHeader(name)),\n});\n\n/**\n * One `abort`/`loadend` listener pair per XHR object, reading the request the\n * object is carrying now. An object reused for a second request (open, send,\n * open, send) gets no second pair, so a finished request's listener never\n * emits again with its stale state.\n */\ninterface XhrListenerSlot {\n current?: XhrRequestState;\n /**\n * A finished request whose `loadend` had not reached our listener when the\n * app opened the next request on the object: an app listener added before\n * ours may start that request from inside the event. Its outcome is read\n * before `open()` resets the status and response headers.\n */\n finished?: { tracked: XhrRequestState; outcome: XhrOutcome };\n}\n\nconst xhrListeners = new WeakMap<XhrLike, XhrListenerSlot>();\n\nconst emitXhr = (\n tracked: XhrRequestState | undefined,\n outcome: (owner: NetworkCaptureOptions) => XhrOutcome,\n): void => {\n const owner = tracked?.owner;\n // A request belongs to the instance that sent it: one that completes\n // after shutdown(), or after a later init(), emits nowhere.\n if (!tracked || !owner || owner !== active) {\n return;\n }\n try {\n const { status, end, requestId } = outcome(owner);\n const aborted =\n status === 0 &&\n (tracked.aborted || isAborted(undefined, false, owner.isOffline?.()));\n emitNetworkEvent(owner, {\n kind: \"xhr\",\n method: tracked.method,\n resolvedUrl: tracked.resolvedUrl as string,\n status,\n durationMs: end - tracked.start,\n severity: aborted ? \"DEBUG\" : undefined,\n injected: tracked.injected,\n ...requestId,\n });\n } catch {\n // Capture failures must never surface to the host.\n }\n};\n\nconst listenTo = (xhr: XhrLike): XhrListenerSlot => {\n const existing = xhrListeners.get(xhr);\n if (existing) {\n return existing;\n }\n const slot: XhrListenerSlot = {};\n xhr.addEventListener(\"abort\", () => {\n if (slot.current) {\n slot.current.aborted = true;\n }\n });\n xhr.addEventListener(\"loadend\", () => {\n const finished = slot.finished;\n slot.finished = undefined;\n if (finished) {\n emitXhr(finished.tracked, () => finished.outcome);\n // The request in `current` was opened during this event and its own\n // `loadend` is still to come.\n if (xhr.readyState !== XHR_DONE) {\n return;\n }\n }\n const tracked = slot.current;\n slot.current = undefined;\n emitXhr(tracked, (owner) => outcomeOf(xhr, owner));\n });\n xhrListeners.set(xhr, slot);\n return slot;\n};\n\ninterface XhrPatch {\n originalOpen: XhrLike[\"open\"];\n originalSend: XhrLike[\"send\"];\n originalSetRequestHeader: XhrLike[\"setRequestHeader\"];\n wrappedOpen: XhrLike[\"open\"];\n wrappedSend: XhrLike[\"send\"];\n wrappedSetRequestHeader: XhrLike[\"setRequestHeader\"];\n}\n\nlet xhrPatch: XhrPatch | undefined;\n\nconst makeXhrPatch = (proto: XhrLike): XhrPatch => {\n const originalOpen = proto.open;\n const originalSend = proto.send;\n const originalSetRequestHeader = proto.setRequestHeader;\n\n // Forward `open` arguments with exact arity: `open(m, u, undefined)` would\n // coerce async to false (sync XHR), which is not equivalent to the 2-arg call.\n const wrappedOpen = function (this: XhrLike, ...args: unknown[]): void {\n const options = active;\n if (options) {\n try {\n const [method, url] = args;\n xhrState.set(this, {\n method: String(method).toUpperCase(),\n resolvedUrl: options.resolveUrl(String(url)),\n customTraceparent: false,\n start: 0,\n processed: false,\n });\n } catch {\n // Tracking failure must not affect the request.\n }\n }\n try {\n // A tracked request on a finished object is inside its completion\n // events, before our `loadend` listener ran: keep its outcome.\n const slot = xhrListeners.get(this);\n const tracked = slot?.current;\n if (slot && tracked?.owner && this.readyState === XHR_DONE) {\n slot.finished = { tracked, outcome: outcomeOf(this, tracked.owner) };\n slot.current = undefined;\n }\n } catch {\n // Tracking failure must not affect the request.\n }\n originalOpen.apply(this, args);\n };\n\n const wrappedSetRequestHeader = function (\n this: XhrLike,\n name: string,\n value: string,\n ): void {\n if (active) {\n try {\n if (name.toLowerCase() === \"traceparent\") {\n const tracked = xhrState.get(this);\n if (tracked) {\n tracked.customTraceparent = true;\n }\n }\n } catch {\n // Ignore.\n }\n }\n originalSetRequestHeader.call(this, name, value);\n };\n\n const wrappedSend = function (this: XhrLike, body?: unknown): void {\n const options = active;\n if (options) {\n try {\n const tracked = xhrState.get(this);\n // `processed` = an outer copy of our wrapper already handled this\n // send (third-party sandwich); inner copies must not re-inject or\n // attach a second loadend emitter.\n if (tracked?.resolvedUrl && !tracked.processed) {\n tracked.processed = true;\n const internal = options.isInternalUrl(tracked.resolvedUrl);\n\n if (\n !internal &&\n options.propagateTraceContext &&\n !tracked.customTraceparent &&\n isAllowedForInjection(options, tracked.resolvedUrl)\n ) {\n try {\n const requestContext = options.traceContext.forRequest();\n for (const [name, value] of Object.entries(\n requestContext.headers,\n )) {\n originalSetRequestHeader.call(this, name, value);\n }\n tracked.injected = requestContext;\n } catch {\n tracked.injected = undefined;\n }\n }\n\n if (!internal) {\n recordWrapperRequest(tracked.resolvedUrl);\n }\n\n if (!internal && options.capture) {\n tracked.start = monotonicNow();\n tracked.owner = options;\n listenTo(this).current = tracked;\n }\n }\n } catch {\n // Fall through to the untouched send.\n }\n }\n originalSend.call(this, body);\n };\n\n return {\n originalOpen,\n originalSend,\n originalSetRequestHeader,\n wrappedOpen,\n wrappedSend,\n wrappedSetRequestHeader,\n };\n};\n\nconst xhrPrototype = (): XhrLike | undefined => {\n const ctor = (globalThis as { XMLHttpRequest?: { prototype: XhrLike } })\n .XMLHttpRequest;\n return ctor?.prototype;\n};\n\n/**\n * Install the dormant XMLHttpRequest patch around whatever implementation is\n * currently live. Idempotent, and re-wraps when a later actor replaced the\n * prototype methods after an earlier install (the displaced wrapper is an\n * inert passthrough, so single-processing holds).\n */\nexport const ensureXhrPatch = (): void => {\n const proto = xhrPrototype();\n if (!proto || isWrapped(proto.open)) {\n return;\n }\n const patch = makeXhrPatch(proto);\n markWrapped(patch.wrappedOpen);\n markWrapped(patch.wrappedSetRequestHeader);\n markWrapped(patch.wrappedSend);\n try {\n proto.open = patch.wrappedOpen;\n proto.setRequestHeader = patch.wrappedSetRequestHeader;\n proto.send = patch.wrappedSend;\n } catch {\n // A frozen prototype stays native: put back whatever was assigned\n // before the refusal, capture no requests, and let init() carry on.\n try {\n proto.open = patch.originalOpen;\n proto.setRequestHeader = patch.originalSetRequestHeader;\n proto.send = patch.originalSend;\n } catch {\n // Nothing was assigned.\n }\n return;\n }\n xhrPatch = patch;\n};\n\n/**\n * Restore the natives if the current implementations are still ours; if\n * someone patched on top of us, restoring would clobber them, and the\n * inactive wrappers pass through.\n */\nexport const uninstallXhrPatch = (): void => {\n const proto = xhrPrototype();\n if (proto && xhrPatch) {\n if (proto.open === xhrPatch.wrappedOpen) {\n proto.open = xhrPatch.originalOpen;\n }\n if (proto.setRequestHeader === xhrPatch.wrappedSetRequestHeader) {\n proto.setRequestHeader = xhrPatch.originalSetRequestHeader;\n }\n if (proto.send === xhrPatch.wrappedSend) {\n proto.send = xhrPatch.originalSend;\n }\n }\n xhrPatch = undefined;\n};\n",
12
+ "export type RandomBytes = (length: number) => Uint8Array;\n\n/**\n * Random bytes for session, trace, and gesture ids. Web Crypto where the host\n * has it; a seeded fallback where it does not (React Native without a\n * `crypto.getRandomValues` polyfill). The ids need uniqueness, not secrecy,\n * so the fallback is acceptable there and is never used where crypto exists.\n */\nexport const createRandomBytes = (): RandomBytes => {\n const cryptoApi = (\n globalThis as { crypto?: { getRandomValues?: (a: Uint8Array) => unknown } }\n ).crypto;\n const getRandomValues = cryptoApi?.getRandomValues;\n if (typeof getRandomValues === \"function\") {\n return (length) => {\n const bytes = new Uint8Array(length);\n getRandomValues.call(cryptoApi, bytes);\n return bytes;\n };\n }\n\n // xorshift128+ seeded from the clock and Math.random: enough entropy to\n // keep ids unique across launches, with no claim beyond that.\n let s0 = (Date.now() ^ 0x9e3779b9) >>> 0;\n let s1 = (Math.random() * 0xffffffff) >>> 0;\n return (length) => {\n const bytes = new Uint8Array(length);\n for (let i = 0; i < length; i += 1) {\n let x = s0;\n const y = s1;\n s0 = y;\n x ^= x << 23;\n x ^= x >>> 17;\n x ^= y ^ (y >>> 26);\n s1 = x >>> 0;\n bytes[i] = (s0 + s1) & 0xff;\n }\n return bytes;\n };\n};\n\nexport const toHex = (bytes: Uint8Array): string =>\n Array.from(bytes, (b) => b.toString(16).padStart(2, \"0\")).join(\"\");\n\nexport const randomHex = (\n randomBytes: RandomBytes,\n byteLength: number,\n): string => toHex(randomBytes(byteLength));\n\nexport const newPrefixedId = (\n randomBytes: RandomBytes,\n prefix: string,\n): string => `${prefix}_${Date.now().toString(36)}${randomHex(randomBytes, 8)}`;\n",
13
+ "import { monotonicNow } from \"./clock\";\nimport { newPrefixedId, type RandomBytes } from \"./random\";\nimport type { ClientEvent, EventAttributes } from \"./types\";\n\n/**\n * Pointer episodes: one row per contact from down to up, for mouse, touch,\n * and pen alike. A binding feeds raw contacts in (DOM pointer events in the\n * browser, the responder's touch arrays on mobile) and the tracker emits the\n * episode rows plus the derived `click` and `rage_click` rows. Movement is\n * sampled, never streamed: a position sample every `sampleIntervalMs` is\n * packed into the row, and a contact that outlives `partialIntervalMs` emits\n * a partial row at that cadence so a long scroll is not a blind spot.\n */\n\nexport type PointerType = \"mouse\" | \"touch\" | \"pen\";\n\nexport interface PointerTarget {\n /** Developer-authored name for the widget under the pointer, if any. */\n name?: string;\n /** Stable-ish identifier: a selector on the web, a component path on mobile. */\n id?: string;\n /** Element tag or component kind. */\n tag?: string;\n /** Whether a tap here is on something that should respond. */\n interactive: boolean;\n}\n\nexport interface PointerContact {\n id: number | string;\n type: PointerType;\n primary: boolean;\n x: number;\n y: number;\n pressure?: number;\n /**\n * The button that started a mouse contact (DOM `button`: 0 primary, 1\n * auxiliary, 2 secondary). Absent for touch and pen. Only a primary-button\n * contact can derive a click: a short right or middle press is an episode\n * without a tap, matching the host's `contextmenu` and `auxclick`.\n */\n button?: number;\n target?: PointerTarget;\n}\n\nexport interface DerivedTap {\n x: number;\n y: number;\n target?: PointerTarget;\n /** Absent for a tap no contact produced (keyboard activation, script). */\n gestureId?: string;\n}\n\nexport interface PointerTrackerOptions {\n emit(event: ClientEvent): void;\n randomBytes: RandomBytes;\n /** Spacing of packed position samples. Default 1000. */\n sampleIntervalMs?: number;\n /** Cadence of partial rows for long contacts. Default 10000. */\n partialIntervalMs?: number;\n /** Every derived tap, so a binding can run its own dead-tap detection. */\n onTap?(tap: DerivedTap): void;\n}\n\nexport interface PointerTracker {\n down(contact: PointerContact): void;\n move(id: number | string, x: number, y: number, pressure?: number): void;\n up(id: number | string, x: number, y: number): void;\n cancel(id: number | string): void;\n /**\n * A click that no pointer contact produced: keyboard activation or a\n * script calling `click()`. Emits the `click` row and runs the same rage\n * and dead-tap derivation, with no episode behind it.\n */\n tap(tap: { x: number; y: number; target?: PointerTarget }): void;\n teardown(): void;\n}\n\nconst DEFAULT_SAMPLE_INTERVAL_MS = 1_000;\nconst DEFAULT_PARTIAL_INTERVAL_MS = 10_000;\nconst TAP_MAX_DISTANCE_PX = 10;\nconst TAP_MAX_DURATION_MS = 500;\nconst RAGE_WINDOW_MS = 1_000;\nconst RAGE_RADIUS_PX = 30;\nconst RAGE_TAP_COUNT = 4;\nconst MAX_OPEN_CONTACTS = 10;\n\n/**\n * `cancel` means the contact stopped reaching us before release: the host\n * took the gesture for itself (a native or browser scroll, a pager, a\n * drawer, a system gesture) or the release was lost. The finger may still be\n * moving, so the row ends where the host took over, not where it lifted.\n */\ntype EndedBy = \"up\" | \"cancel\" | \"teardown\";\n\ninterface OpenContact {\n contact: PointerContact;\n gestureId: string;\n startUnixMs: number;\n startPerf: number;\n lastSamplePerf: number;\n lastX: number;\n lastY: number;\n /** Farthest the contact strayed from its start, so a drag that returns is still a drag. */\n maxDistance: number;\n maxPressure?: number;\n maxConcurrent: number;\n /** Samples since the last emitted row, as `t:x,y`. */\n path: string[];\n sampleCount: number;\n partialTimer?: ReturnType<typeof setTimeout>;\n}\n\ninterface RecentTap {\n time: number;\n x: number;\n y: number;\n}\n\ninterface TapDetails {\n x: number;\n y: number;\n target?: PointerTarget;\n /** `none` when no contact produced the click. */\n pointerType: PointerType | \"none\";\n gestureId?: string;\n timeUnixMs: number;\n}\n\nconst distance = (x0: number, y0: number, x1: number, y1: number): number =>\n Math.hypot(x1 - x0, y1 - y0);\n\nconst targetAttributes = (target?: PointerTarget): EventAttributes => ({\n \"app.widget.id\": target?.id,\n \"app.widget.name\": target?.name,\n \"app.widget.tag\": target?.tag,\n});\n\nexport const createPointerTracker = (\n options: PointerTrackerOptions,\n): PointerTracker => {\n const sampleIntervalMs =\n options.sampleIntervalMs ?? DEFAULT_SAMPLE_INTERVAL_MS;\n const partialIntervalMs =\n options.partialIntervalMs ?? DEFAULT_PARTIAL_INTERVAL_MS;\n const open = new Map<number | string, OpenContact>();\n const recentTaps: RecentTap[] = [];\n // Not 0: an early first-second burst must still report.\n let rageReportedAt = Number.NEGATIVE_INFINITY;\n let currentGestureId: string | undefined;\n\n const sample = (entry: OpenContact, now: number): void => {\n entry.path.push(\n `${Math.round(now - entry.startPerf)}:${Math.round(entry.lastX)},${Math.round(entry.lastY)}`,\n );\n entry.sampleCount += 1;\n entry.lastSamplePerf = now;\n };\n\n const emitRow = (\n entry: OpenContact,\n now: number,\n endedBy: EndedBy | undefined,\n ): void => {\n const { contact } = entry;\n const partial = endedBy === undefined;\n const durationMs = Math.round(now - entry.startPerf);\n const travelled = distance(contact.x, contact.y, entry.lastX, entry.lastY);\n try {\n options.emit({\n type: \"pointer\",\n body: `pointer ${contact.type} ${durationMs}ms ${Math.round(travelled)}px${partial ? \" (ongoing)\" : \"\"}`,\n severity: \"INFO\",\n // Final rows sit at the contact's start so the timeline reads\n // causally; partial rows are progress marks and sit where they fire.\n timeUnixMs: partial ? Date.now() : entry.startUnixMs,\n attributes: {\n \"app.pointer.id\": String(contact.id),\n \"app.pointer.type\": contact.type,\n \"app.pointer.primary\": contact.primary,\n \"app.pointer.gesture_id\": entry.gestureId,\n \"app.screen.coordinate.x\": Math.round(contact.x),\n \"app.screen.coordinate.y\": Math.round(contact.y),\n \"app.pointer.end.x\": Math.round(entry.lastX),\n \"app.pointer.end.y\": Math.round(entry.lastY),\n \"app.pointer.distance\": Math.round(travelled),\n \"app.pointer.duration_ms\": durationMs,\n \"app.pointer.pressure\": entry.maxPressure,\n \"app.pointer.concurrent\": entry.maxConcurrent,\n \"app.pointer.path\": entry.path.join(\";\") || undefined,\n \"app.pointer.sample_count\": entry.sampleCount,\n \"app.pointer.partial\": partial ? true : undefined,\n \"app.pointer.ended_by\": endedBy,\n ...targetAttributes(contact.target),\n },\n });\n } catch {\n // Emitting must never throw into the host.\n }\n entry.path = [];\n };\n\n const schedulePartial = (entry: OpenContact): void => {\n entry.partialTimer = setTimeout(() => {\n entry.partialTimer = undefined;\n try {\n const now = monotonicNow();\n if (now - entry.lastSamplePerf >= sampleIntervalMs) {\n sample(entry, now);\n }\n emitRow(entry, now, undefined);\n schedulePartial(entry);\n } catch {\n // Never throw from a timer into the host.\n }\n }, partialIntervalMs);\n };\n\n const emitTap = (tap: TapDetails, now: number): void => {\n const name = tap.target?.name;\n const label = name\n ? `\"${name}\"`\n : (tap.target?.id ?? `${Math.round(tap.x)},${Math.round(tap.y)}`);\n const tapAttributes: EventAttributes = {\n ...targetAttributes(tap.target),\n \"app.pointer.type\": tap.pointerType,\n \"app.pointer.gesture_id\": tap.gestureId,\n \"app.screen.coordinate.x\": Math.round(tap.x),\n \"app.screen.coordinate.y\": Math.round(tap.y),\n };\n\n try {\n options.emit({\n type: \"click\",\n body: `click ${label}`,\n severity: \"INFO\",\n timeUnixMs: tap.timeUnixMs,\n attributes: tapAttributes,\n });\n } catch {\n // Never throw into the host.\n }\n\n // Rage tap: repeated taps in a tight radius within the window.\n recentTaps.push({ time: now, x: tap.x, y: tap.y });\n while (\n recentTaps.length > 0 &&\n now - (recentTaps[0] as RecentTap).time > RAGE_WINDOW_MS\n ) {\n recentTaps.shift();\n }\n const clustered = recentTaps.filter(\n (recent) =>\n Math.abs(recent.x - tap.x) <= RAGE_RADIUS_PX &&\n Math.abs(recent.y - tap.y) <= RAGE_RADIUS_PX,\n );\n if (\n clustered.length >= RAGE_TAP_COUNT &&\n now - rageReportedAt > RAGE_WINDOW_MS\n ) {\n rageReportedAt = now;\n try {\n options.emit({\n type: \"rage_click\",\n body: `rage click ${label}`,\n severity: \"WARN\",\n timeUnixMs: Date.now(),\n attributes: {\n ...tapAttributes,\n \"app.pointer.tap_count\": clustered.length,\n },\n });\n } catch {\n // Never throw into the host.\n }\n }\n\n try {\n options.onTap?.({\n x: tap.x,\n y: tap.y,\n target: tap.target,\n gestureId: tap.gestureId,\n });\n } catch {\n // A binding's tap hook must never break capture.\n }\n };\n\n const deriveTap = (entry: OpenContact, now: number): void => {\n const { contact } = entry;\n if (\n entry.maxConcurrent !== 1 ||\n (contact.button ?? 0) !== 0 ||\n now - entry.startPerf > TAP_MAX_DURATION_MS ||\n entry.maxDistance > TAP_MAX_DISTANCE_PX\n ) {\n return;\n }\n emitTap(\n {\n x: entry.lastX,\n y: entry.lastY,\n target: contact.target,\n pointerType: contact.type,\n gestureId: entry.gestureId,\n timeUnixMs: entry.startUnixMs,\n },\n now,\n );\n };\n\n const end = (\n entry: OpenContact,\n x: number,\n y: number,\n endedBy: EndedBy,\n ): void => {\n const now = monotonicNow();\n open.delete(entry.contact.id);\n if (entry.partialTimer !== undefined) {\n clearTimeout(entry.partialTimer);\n entry.partialTimer = undefined;\n }\n entry.lastX = x;\n entry.lastY = y;\n entry.maxDistance = Math.max(\n entry.maxDistance,\n distance(entry.contact.x, entry.contact.y, x, y),\n );\n emitRow(entry, now, endedBy);\n if (endedBy === \"up\") {\n deriveTap(entry, now);\n }\n if (open.size === 0) {\n currentGestureId = undefined;\n }\n };\n\n return {\n down(contact) {\n try {\n const stale = open.get(contact.id);\n if (stale) {\n // A new down for an open id means its up or cancel was lost (a\n // release outside the window, a native context menu): close it, or\n // the id is blocked forever and its partial timer never stops.\n end(stale, stale.lastX, stale.lastY, \"cancel\");\n }\n if (open.size >= MAX_OPEN_CONTACTS) {\n return;\n }\n const now = monotonicNow();\n // A contact that lands while others are down joins their gesture.\n if (open.size === 0 || currentGestureId === undefined) {\n currentGestureId = newPrefixedId(options.randomBytes, \"gest\");\n }\n const concurrent = open.size + 1;\n for (const other of open.values()) {\n other.maxConcurrent = Math.max(other.maxConcurrent, concurrent);\n }\n const entry: OpenContact = {\n contact,\n gestureId: currentGestureId,\n startUnixMs: Date.now(),\n startPerf: now,\n lastSamplePerf: now,\n lastX: contact.x,\n lastY: contact.y,\n maxDistance: 0,\n maxPressure: contact.pressure,\n maxConcurrent: concurrent,\n path: [],\n sampleCount: 0,\n };\n sample(entry, now);\n open.set(contact.id, entry);\n schedulePartial(entry);\n } catch {\n // Capture failures must never surface to the host.\n }\n },\n move(id, x, y, pressure) {\n try {\n const entry = open.get(id);\n if (!entry) return;\n entry.lastX = x;\n entry.lastY = y;\n entry.maxDistance = Math.max(\n entry.maxDistance,\n distance(entry.contact.x, entry.contact.y, x, y),\n );\n if (pressure !== undefined) {\n entry.maxPressure = Math.max(entry.maxPressure ?? 0, pressure);\n }\n const now = monotonicNow();\n if (now - entry.lastSamplePerf >= sampleIntervalMs) {\n sample(entry, now);\n }\n } catch {\n // Ignore.\n }\n },\n up(id, x, y) {\n try {\n const entry = open.get(id);\n if (entry) end(entry, x, y, \"up\");\n } catch {\n // Ignore.\n }\n },\n cancel(id) {\n try {\n const entry = open.get(id);\n if (entry) end(entry, entry.lastX, entry.lastY, \"cancel\");\n } catch {\n // Ignore.\n }\n },\n tap(tap) {\n try {\n emitTap(\n {\n x: tap.x,\n y: tap.y,\n target: tap.target,\n pointerType: \"none\",\n timeUnixMs: Date.now(),\n },\n monotonicNow(),\n );\n } catch {\n // Ignore.\n }\n },\n teardown() {\n for (const entry of Array.from(open.values())) {\n try {\n end(entry, entry.lastX, entry.lastY, \"teardown\");\n } catch {\n // Best-effort.\n }\n }\n open.clear();\n recentTaps.length = 0;\n },\n };\n};\n",
14
+ "import { newPrefixedId, type RandomBytes } from \"./random\";\n\nconst SESSION_STORAGE_KEY = \"sazabi.session\";\nconst WINDOW_STORAGE_KEY = \"sazabi.windowId\";\nconst DISTINCT_STORAGE_KEY = \"sazabi.distinctId\";\n\nconst SESSION_IDLE_MS = 30 * 60 * 1000;\nconst SESSION_MAX_MS = 24 * 60 * 60 * 1000;\n/**\n * How far the wall clock may run behind the stored activity time before the\n * stored session no longer counts. A clock set back by hours would otherwise\n * keep a session alive past its idle and age limits, since both are measured\n * as `now` minus a time that now lies in the future.\n */\nconst CLOCK_SKEW_MS = 60 * 1000;\n/** Throttle activity writes; click bursts must not spam storage. */\nconst ACTIVITY_PERSIST_INTERVAL_MS = 5_000;\n\ninterface StoredSession {\n id: string;\n startedAt: number;\n lastActivityAt: number;\n}\n\n/**\n * Synchronous key-value access the session machine reads through on every\n * call. A browser binding backs it with `localStorage` (so sibling tabs\n * converge) and `sessionStorage`; a mobile binding backs it with an\n * in-memory mirror hydrated from its storage adapter and written through.\n */\nexport interface SessionStore {\n get(key: string): string | null;\n set(key: string, value: string): void;\n remove(key: string): void;\n}\n\n/** A store that lives for the process only: the fallback on every host. */\nexport const createMemoryStore = (): SessionStore => {\n const memory = new Map<string, string>();\n return {\n get: (key) => memory.get(key) ?? null,\n set: (key, value) => {\n memory.set(key, value);\n },\n remove: (key) => {\n memory.delete(key);\n },\n };\n};\n\nexport interface SessionManager {\n getSessionId(): string;\n getWindowId(): string;\n getDistinctId(): string | undefined;\n /**\n * Set the client-asserted identity. Switching from one identity directly\n * to a different one rotates the session and window ids so two users on a\n * shared device never thread into one timeline.\n */\n setDistinctId(distinctId: string): void;\n /** Logout: clear identity and rotate both session and window ids. */\n reset(): void;\n /** Record user activity; may rotate the session on idle/max-age expiry. */\n touch(): void;\n}\n\nexport interface SessionManagerOptions {\n /** Visit-scoped state: session id and identity. */\n store: SessionStore;\n /** Window-scoped state: one browser tab, or one app launch. */\n windowStore: SessionStore;\n randomBytes: RandomBytes;\n}\n\nconst parseStoredSession = (raw: string | null): StoredSession | undefined => {\n if (!raw) return undefined;\n try {\n const parsed: unknown = JSON.parse(raw);\n if (\n typeof parsed === \"object\" &&\n parsed !== null &&\n typeof (parsed as StoredSession).id === \"string\" &&\n typeof (parsed as StoredSession).startedAt === \"number\" &&\n typeof (parsed as StoredSession).lastActivityAt === \"number\"\n ) {\n return parsed as StoredSession;\n }\n } catch {\n // Corrupt value: treat as absent and rotate.\n }\n return undefined;\n};\n\n/**\n * Session identity. `session.id` is the visit (last-writer-wins on rotation\n * races between windows; a split session is cosmetic, not data loss).\n * `session.window_id` is this window. `session.distinct_id` is read through\n * the store so identify/reset in one window is visible to siblings on their\n * next event.\n */\nexport const createSessionManager = (\n options: SessionManagerOptions,\n): SessionManager => {\n const { store, windowStore, randomBytes } = options;\n\n let lastPersistedActivity = 0;\n\n const readOrRotate = (): StoredSession => {\n const now = Date.now();\n const stored = parseStoredSession(store.get(SESSION_STORAGE_KEY));\n\n if (\n stored &&\n now - stored.lastActivityAt <= SESSION_IDLE_MS &&\n now - stored.lastActivityAt >= -CLOCK_SKEW_MS &&\n now - stored.startedAt <= SESSION_MAX_MS\n ) {\n return stored;\n }\n\n const fresh: StoredSession = {\n id: newPrefixedId(randomBytes, \"sess\"),\n startedAt: now,\n lastActivityAt: now,\n };\n store.set(SESSION_STORAGE_KEY, JSON.stringify(fresh));\n lastPersistedActivity = now;\n return fresh;\n };\n\n let windowId = windowStore.get(WINDOW_STORAGE_KEY);\n if (!windowId) {\n windowId = newPrefixedId(randomBytes, \"win\");\n windowStore.set(WINDOW_STORAGE_KEY, windowId);\n }\n\n const rotate = (): void => {\n const now = Date.now();\n store.set(\n SESSION_STORAGE_KEY,\n JSON.stringify({\n id: newPrefixedId(randomBytes, \"sess\"),\n startedAt: now,\n lastActivityAt: now,\n }),\n );\n lastPersistedActivity = now;\n windowId = newPrefixedId(randomBytes, \"win\");\n windowStore.set(WINDOW_STORAGE_KEY, windowId);\n };\n\n return {\n getSessionId() {\n return readOrRotate().id;\n },\n getWindowId() {\n return windowId as string;\n },\n getDistinctId() {\n return store.get(DISTINCT_STORAGE_KEY) ?? undefined;\n },\n setDistinctId(id: string) {\n const current = store.get(DISTINCT_STORAGE_KEY) ?? undefined;\n if (current !== undefined && current !== id) {\n // Identity switch without reset(): defensive rotation so the new\n // user's events never join the previous user's session.\n rotate();\n }\n store.set(DISTINCT_STORAGE_KEY, id);\n },\n reset() {\n store.remove(DISTINCT_STORAGE_KEY);\n rotate();\n },\n touch() {\n const now = Date.now();\n const current = readOrRotate();\n if (now - lastPersistedActivity >= ACTIVITY_PERSIST_INTERVAL_MS) {\n store.set(\n SESSION_STORAGE_KEY,\n JSON.stringify({ ...current, lastActivityAt: now }),\n );\n lastPersistedActivity = now;\n }\n },\n };\n};\n",
15
+ "import { type RandomBytes, toHex } from \"./random\";\n\nexport interface RequestTraceContext {\n /** Headers to inject (`traceparent`, and `tracestate` when present). */\n headers: Record<string, string>;\n traceId: string;\n spanId: string;\n}\n\nexport interface TraceContextManager {\n /** Trace context for one outgoing request, minted at request time. */\n forRequest(): RequestTraceContext;\n}\n\n/** The subset of an OTel SpanContext this module consumes. */\nexport interface HostSpanContext {\n traceId: string;\n spanId: string;\n traceFlags: number;\n traceState?: { serialize(): string };\n}\n\nconst SAMPLED_FLAG = 0x1;\nconst TRACE_ID_PATTERN = /^[0-9a-f]{32}$/;\nconst SPAN_ID_PATTERN = /^[0-9a-f]{16}$/;\nconst ALL_ZERO_TRACE_ID = \"0\".repeat(32);\nconst ALL_ZERO_SPAN_ID = \"0\".repeat(16);\n\nconst randomHexId = (randomBytes: RandomBytes, byteLength: number): string => {\n let bytes: Uint8Array;\n do {\n bytes = randomBytes(byteLength);\n } while (bytes.every((b) => b === 0)); // all-zero ids are invalid per W3C spec\n return toHex(bytes);\n};\n\nconst isValidHostSpanContext = (\n candidate: unknown,\n): candidate is HostSpanContext => {\n if (typeof candidate !== \"object\" || candidate === null) return false;\n const { traceId, spanId, traceFlags } = candidate as HostSpanContext;\n return (\n typeof traceId === \"string\" &&\n TRACE_ID_PATTERN.test(traceId) &&\n traceId !== ALL_ZERO_TRACE_ID &&\n typeof spanId === \"string\" &&\n SPAN_ID_PATTERN.test(spanId) &&\n spanId !== ALL_ZERO_SPAN_ID &&\n typeof traceFlags === \"number\"\n );\n};\n\n// ------------------------------------------------- host OTel interop --------\n// Dependency-free read of the host app's active span, through the\n// @opentelemetry/api package's own cross-copy compatibility contract: every\n// api copy interoperates via the global delegate registered at\n// Symbol.for(\"opentelemetry.js.api.1\"), and context keys are Symbol.for(...)\n// strings. That registry IS the mechanism by which independent api copies\n// find each other, which is what makes it load-bearing rather than private.\n// An integration test against the real api package (devDependency of the\n// browser SDK) pins the contract; any shape drift degrades here to \"no\n// active span\" and we mint our own roots instead.\n\nconst OTEL_GLOBAL_KEY = Symbol.for(\"opentelemetry.js.api.1\");\nconst OTEL_SPAN_CONTEXT_KEY = Symbol.for(\"OpenTelemetry Context Key SPAN\");\n\ninterface OtelGlobalShape {\n context?: {\n active?: () => { getValue?: (key: symbol) => unknown } | undefined;\n };\n}\n\nexport const readHostSpanContext = (): HostSpanContext | undefined => {\n try {\n const otelApi = (globalThis as Record<symbol, unknown>)[OTEL_GLOBAL_KEY] as\n | OtelGlobalShape\n | undefined;\n const activeContext = otelApi?.context?.active?.();\n const span = activeContext?.getValue?.(OTEL_SPAN_CONTEXT_KEY) as\n | { spanContext?: () => unknown }\n | undefined;\n const candidate = span?.spanContext?.();\n return isValidHostSpanContext(candidate) ? candidate : undefined;\n } catch {\n return undefined;\n }\n};\n\nconst formatTraceparent = (spanContext: HostSpanContext): string => {\n const flags = (spanContext.traceFlags & 0xff).toString(16).padStart(2, \"0\");\n return `00-${spanContext.traceId}-${spanContext.spanId}-${flags}`;\n};\n\nconst readTraceState = (spanContext: HostSpanContext): string | undefined => {\n try {\n const serialized = spanContext.traceState?.serialize();\n return serialized || undefined;\n } catch {\n return undefined;\n }\n};\n\n/**\n * W3C trace context for outgoing requests: hand-rolled header formatting\n * (equivalence-tested against OTel's W3CTraceContextPropagator), zero\n * runtime dependencies. If the host app runs its own OTel and has an active\n * span, that span's context (including its flags and tracestate) is reused\n * so we join the customer's trace instead of forking a new one; otherwise a\n * fresh root is minted with the sampled flag defaulting to off (`-00`).\n */\nexport const createTraceContextManager = (options: {\n sampledFlag: boolean;\n randomBytes: RandomBytes;\n}): TraceContextManager => {\n const mintRootSpanContext = (): HostSpanContext => ({\n traceId: randomHexId(options.randomBytes, 16),\n spanId: randomHexId(options.randomBytes, 8),\n traceFlags: options.sampledFlag ? SAMPLED_FLAG : 0,\n });\n\n return {\n forRequest() {\n const spanContext = readHostSpanContext() ?? mintRootSpanContext();\n const headers: Record<string, string> = {\n traceparent: formatTraceparent(spanContext),\n };\n const traceState = readTraceState(spanContext);\n if (traceState) {\n headers.tracestate = traceState;\n }\n return {\n headers,\n traceId: spanContext.traceId,\n spanId: spanContext.spanId,\n };\n },\n };\n};\n",
16
+ "import type { ClientEvent, EventAttributes } from \"./types\";\n\n/**\n * What an `sdk_health` row reports: the SDK's own delivery trouble, so a\n * misconfigured or unreachable app is visible to Sazabi before the customer\n * reports it. Each value is a bounded enumeration, never free text.\n *\n * - `intake_rejected`: the intake refused a batch for good (a 4xx other\n * than 408 and 429); `app.sdk.http_status` carries the status.\n * - `intake_unreachable`: uploads failed on the network (a dropped\n * connection, a timeout) and the intake later answered; `app.sdk.attempts`\n * is how many backoff windows the outage spanned (one per failed try,\n * with concurrent uploads failing together counted once).\n * - `outbox_discarded`: stored crash reports were dropped, by the caps or\n * because they were not valid reports; `app.sdk.outbox_discarded` counts\n * what is known lost, and `app.sdk.outbox_unsaved` what a saved queue had\n * left out at the caps (possible losses: their live upload may have\n * finished before the app was killed) (mobile only).\n * - `outbox_persist_failed`: the storage adapter refused a write of the\n * crash outbox or the saved queue (mobile only).\n */\nexport type SdkHealthDiagnostic =\n | \"intake_rejected\"\n | \"intake_unreachable\"\n | \"outbox_discarded\"\n | \"outbox_persist_failed\";\n\nexport interface SdkHealthReport {\n diagnostic: SdkHealthDiagnostic;\n /** The counters of the condition (`app.sdk.http_status`, `app.sdk.attempts`, ...). */\n attributes?: EventAttributes;\n}\n\nexport type SdkHealthReporter = (report: SdkHealthReport) => void;\n\nexport const SDK_DIAGNOSTIC_KEY = \"app.sdk.diagnostic\";\n\n/**\n * Turns health reports into `sdk_health` rows, at most one per diagnostic\n * for the life of the reporter (one SDK session). The bound is what keeps\n * the SDK from storming its own intake: a health row the intake refuses is\n * a rejection like any other and is never reported again, and a flapping\n * network produces one row per launch, not one per outage. Never throws\n * into the host.\n */\nexport const createSdkHealthReporter = (\n emit: (event: ClientEvent) => void,\n): SdkHealthReporter => {\n const reported = new Set<SdkHealthDiagnostic>();\n return (report) => {\n if (reported.has(report.diagnostic)) return;\n reported.add(report.diagnostic);\n try {\n emit({\n type: \"sdk_health\",\n body: `sdk_health ${report.diagnostic}`,\n severity: \"INFO\",\n timeUnixMs: Date.now(),\n attributes: {\n [SDK_DIAGNOSTIC_KEY]: report.diagnostic,\n ...report.attributes,\n },\n });\n } catch {\n // The SDK's own health must never throw into the host.\n }\n };\n};\n",
17
+ "import { truncateValue } from \"./attributes\";\nimport type { SdkHealthReport } from \"./sdk-health\";\nimport type {\n AttributeValue,\n ClientEvent,\n EventAttributes,\n EventSeverity,\n} from \"./types\";\n\ntype OtlpAnyValue =\n | { stringValue: string }\n | { intValue: string }\n | { doubleValue: number }\n | { boolValue: boolean };\n\ninterface OtlpKeyValue {\n key: string;\n value: OtlpAnyValue;\n}\n\nexport interface OtlpLogRecord {\n timeUnixNano: string;\n severityNumber: number;\n severityText: EventSeverity;\n body: { stringValue: string };\n attributes: OtlpKeyValue[];\n /** OTLP/JSON encodes trace/span ids as hex (unlike proto3 JSON's base64). */\n traceId?: string;\n spanId?: string;\n}\n\nconst SEVERITY_NUMBERS: Record<EventSeverity, number> = {\n DEBUG: 5,\n INFO: 9,\n WARN: 13,\n ERROR: 17,\n};\n\nconst MAX_BODY_LENGTH = 1024;\n\nconst encodeValue = (value: AttributeValue): OtlpAnyValue => {\n if (typeof value === \"boolean\") {\n return { boolValue: value };\n }\n if (typeof value === \"number\") {\n // proto3 JSON maps int64 to string; doubles stay numeric.\n return Number.isInteger(value)\n ? { intValue: String(value) }\n : { doubleValue: value };\n }\n return { stringValue: truncateValue(value) };\n};\n\nconst encodeAttributes = (attributes: EventAttributes): OtlpKeyValue[] => {\n const encoded: OtlpKeyValue[] = [];\n for (const [key, value] of Object.entries(attributes)) {\n if (value === undefined) {\n continue;\n }\n encoded.push({ key, value: encodeValue(value) });\n }\n return encoded;\n};\n\nexport const encodeLogRecord = (\n event: ClientEvent,\n baseAttributes: EventAttributes,\n): OtlpLogRecord => {\n const record: OtlpLogRecord = {\n timeUnixNano: `${event.timeUnixMs}000000`,\n severityNumber: SEVERITY_NUMBERS[event.severity],\n severityText: event.severity,\n body: { stringValue: truncateValue(event.body, MAX_BODY_LENGTH) },\n attributes: encodeAttributes({ ...baseAttributes, ...event.attributes }),\n };\n if (event.traceId) {\n record.traceId = event.traceId;\n }\n if (event.spanId) {\n record.spanId = event.spanId;\n }\n return record;\n};\n\n/**\n * The slice of `fetch` the transport uses. Declared structurally so the core\n * needs no DOM lib; every host's real `fetch` satisfies it.\n */\nexport type FetchLike = (\n url: string,\n init: {\n method: \"POST\";\n headers: Record<string, string>;\n body: string;\n keepalive: boolean;\n /** An `AbortSignal` where the host has `AbortController`; aborts a timed-out upload. */\n signal?: unknown;\n },\n) => Promise<unknown>;\n\n/**\n * UTF-8 byte length of a string, without `TextEncoder` (Hermes has none).\n * Request budgets are bytes on the wire; a string's `length` counts UTF-16\n * units and undercounts non-ASCII text up to threefold.\n */\nexport const utf8Length = (text: string): number => {\n let bytes = text.length;\n for (let index = 0; index < text.length; index += 1) {\n const code = text.charCodeAt(index);\n if (code < 0x80) continue;\n if (code < 0x800) {\n bytes += 1;\n } else if (\n code >= 0xd800 &&\n code <= 0xdbff &&\n (text.charCodeAt(index + 1) & 0xfc00) === 0xdc00\n ) {\n // A surrogate pair: two units, four bytes.\n bytes += 2;\n index += 1;\n } else {\n bytes += 2;\n }\n }\n return bytes;\n};\n\n/**\n * Queued-batch uploads allowed in flight at once. A hung intake holds at most\n * this many requests; later batches wait in the capped queue.\n */\nconst MAX_IN_FLIGHT_UPLOADS = 4;\n/** Hard cap on queued bytes, beside `maxQueuedEvents`; the oldest go first. */\nconst MAX_QUEUED_BYTES = 1_048_576;\n/** An upload with no answer after this long counts as a network failure. */\nconst REQUEST_TIMEOUT_MS = 30_000;\n/** Retry pacing: exponential from the base, capped, with jitter. */\nconst RETRY_BASE_MS = 1_000;\nconst RETRY_MAX_MS = 60_000;\n/** The longest `Retry-After` honored; a longer one waits this long. */\nconst RETRY_AFTER_MAX_MS = 300_000;\n/** Stamped on the first row delivered after events were dropped: how many. */\nexport const DROPPED_EVENTS_KEY = \"app.sdk.dropped_events\";\n/**\n * Names the sending SDK on every upload, `<package>/<version>`: the input\n * for the intake gateway's per-SDK request label (a separate change), which\n * can then count a request by SDK even when it rejects it before the key is\n * known. The gateway's CORS allow-list admits it, so a browser preflight\n * passes.\n */\nexport const SDK_HEADER = \"x-sazabi-sdk\";\n\nexport interface TransportOptions {\n /** Full OTLP logs endpoint, e.g. `https://<key>.<region>.intake.<domain>/v1/logs`. */\n url: string;\n publicKey: string;\n resourceAttributes: EventAttributes;\n /** OTLP instrumentation scope: the emitting SDK's name and version. */\n scope: { name: string; version: string };\n /**\n * The fetch to deliver with, captured before the SDK's network patch\n * installs, so exporter traffic can never recurse through our own wrapper\n * or appear in the captured event stream.\n */\n fetchImpl: FetchLike;\n flushIntervalMs: number;\n maxBatchBytes: number;\n maxQueuedEvents: number;\n /** Reports a dropped batch or a full queue; called once per reason. */\n warn?(message: string): void;\n /**\n * The SDK's own delivery health: a batch the intake refused for good\n * (`intake_rejected`, with the status) and uploads that failed on the\n * network before one got through (`intake_unreachable`, with the number\n * of failed tries). The binding turns these into `sdk_health` rows, once\n * per condition per session.\n */\n onHealth?(report: SdkHealthReport): void;\n /**\n * Called when an upload settles with nothing queued, nothing in flight, and\n * every row of the last `snapshotEnvelopes` delivered or refused for good:\n * a saved row evicted from the full queue, or given up by `shutdown()`\n * while the intake was unreachable, keeps it from firing until the next\n * snapshot, so the host's saved copy stays.\n */\n onIdle?(): void;\n}\n\nexport interface Transport {\n enqueue(record: OtlpLogRecord): void;\n /**\n * Deliver everything queued. `keepalive: true` is for browser unload paths;\n * each request body must stay inside the browser's ~64 KiB keepalive\n * budget, which enqueue-side chunking guarantees via `maxBatchBytes`. Hosts\n * without keepalive ignore the flag.\n */\n flush(options?: { keepalive?: boolean }): Promise<void>;\n /**\n * Encode everything queued into standalone request bodies without sending,\n * leaving the queue empty. The mobile crash outbox persists these and posts\n * them on the next launch; `recordAttributes` are appended to every record\n * (the outbox stamps its replay id there).\n */\n drainEnvelopes(recordAttributes?: EventAttributes): string[];\n /**\n * Encode everything queued into standalone request bodies and keep it\n * queued. `recordAttributes` are stamped on the queued records themselves,\n * so the live upload and the saved copy carry the same values (the mobile\n * SDK saves its queue this way when the app leaves the foreground). Rows an\n * earlier snapshot saved whose upload is still in flight are included as\n * they were sent: the copy this one replaces held them.\n */\n snapshotEnvelopes(recordAttributes: EventAttributes): string[];\n /**\n * While held, `enqueue` never flushes on its own, neither on the byte\n * budget nor by scheduling the timer: the mobile crash path holds the\n * transport while it stamps the fatal row and everything buffered before\n * it, then drains the whole batch into the outbox. Without this, a fatal\n * row that tips the queue over the budget would be flushed out of the queue\n * into a request the dying process never completes.\n */\n holdFlushes(hold: boolean): void;\n /**\n * Deliver a previously drained envelope. Resolves `true` once the envelope\n * is settled: the intake accepted it (2xx) or refused it for good (a 4xx\n * other than 408 and 429, dropped with a warning). Resolves `false` when it\n * is worth sending again (408, 429, 5xx, a network failure, a timeout), and\n * never rejects, so a persisted envelope is deleted only once settled.\n */\n sendEnvelope(body: string): Promise<boolean>;\n shutdown(): Promise<void>;\n}\n\ninterface QueuedRecord {\n record: OtlpLogRecord;\n bytes: number;\n /** Enqueue order: failed batches merge back by it, so eviction stays oldest-first. */\n seq: number;\n /** Stamped by `snapshotEnvelopes`: the host holds a saved copy of this row. */\n saved?: boolean;\n}\n\ntype Outcome =\n | { kind: \"sent\" }\n /** `network` marks a failure with no answer at all: a dropped connection or a timeout. */\n | { kind: \"retry\"; retryAfterMs?: number; network?: true }\n | { kind: \"drop\" | \"split\"; status: number };\n\nconst RETRY: Outcome = { kind: \"retry\", network: true };\n\n/** `Retry-After` as delta-seconds or an HTTP date, in ms; undefined when absent. */\nconst retryAfterMs = (response: unknown): number | undefined => {\n try {\n const raw = (\n response as { headers?: { get?(name: string): string | null } }\n ).headers?.get?.(\"retry-after\");\n if (!raw) return undefined;\n const seconds = Number(raw);\n const ms = Number.isFinite(seconds)\n ? seconds * 1_000\n : Date.parse(raw) - Date.now();\n return ms > 0 ? Math.min(ms, RETRY_AFTER_MAX_MS) : undefined;\n } catch {\n return undefined;\n }\n};\n\n/**\n * A host fetch answers with a Response-like whose `status` tells what the\n * intake did with the batch; a host that answers with something else (a bare\n * promise from an unusual fetch) is trusted, as it always was.\n */\nconst classify = (response: unknown): Outcome => {\n const status = (response as { status?: unknown } | null)?.status;\n if (typeof status !== \"number\" || (status >= 200 && status < 300)) {\n return { kind: \"sent\" };\n }\n if (status === 408 || status === 429 || status >= 500) {\n return { kind: \"retry\", retryAfterMs: retryAfterMs(response) };\n }\n return { kind: status === 413 ? \"split\" : \"drop\", status };\n};\n\nexport const createTransport = (options: TransportOptions): Transport => {\n const queue: QueuedRecord[] = [];\n let queuedBytes = 0;\n let nextRecordSeq = 0;\n let timer: ReturnType<typeof setTimeout> | undefined;\n let shutDown = false;\n let held = false;\n // Queued-batch uploads still open; each removes itself when it settles.\n const inFlight = new Set<Promise<void>>();\n // The rows of those uploads, for the next snapshot.\n const sending = new Set<QueuedRecord[]>();\n // A row of the last snapshot was lost undelivered: onIdle stays quiet.\n let savedLost = false;\n // Events dropped since the last delivered row said so.\n let dropped = 0;\n // Backoff after a retryable failure: nothing queued is sent before\n // `retryAt` except by shutdown() or an unload flush.\n let attempt = 0;\n let retryAt = 0;\n // Backoff windows opened by a failure with no answer from the intake\n // since it last answered anything (concurrent failures count once).\n let unreachableAttempts = 0;\n const warned = new Set<string>();\n\n const report = (health: SdkHealthReport): void => {\n try {\n options.onHealth?.(health);\n } catch {\n // Never throw into the host.\n }\n };\n\n const warnOnce = (reason: string, message: string): void => {\n if (warned.has(reason)) return;\n warned.add(reason);\n try {\n options.warn?.(message);\n } catch {\n // Never throw into the host.\n }\n };\n\n const buildEnvelope = (records: OtlpLogRecord[]): string =>\n JSON.stringify({\n resourceLogs: [\n {\n resource: {\n attributes: encodeAttributes(options.resourceAttributes),\n },\n scopeLogs: [\n {\n scope: {\n name: options.scope.name,\n version: options.scope.version,\n },\n logRecords: records,\n },\n ],\n },\n ],\n });\n\n const envelopeOverhead = utf8Length(buildEnvelope([])) + 64;\n const chunkBudget = Math.max(4096, options.maxBatchBytes - envelopeOverhead);\n\n const clearTimer = (): void => {\n if (timer !== undefined) {\n clearTimeout(timer);\n timer = undefined;\n }\n };\n\n // The flush interval, or the end of a backoff when one is running.\n const scheduleFlush = (): void => {\n if (timer !== undefined || shutDown) {\n return;\n }\n const wait = retryAt - Date.now();\n timer = setTimeout(\n () => {\n timer = undefined;\n void flush();\n },\n wait > 0 ? wait : options.flushIntervalMs,\n );\n };\n\n // Oldest first, counted: a long offline period keeps the newest events\n // within both caps and says how many it lost. enqueue() refuses a record\n // larger than the byte cap, so this never empties the queue on bytes.\n const evict = (): void => {\n while (\n queue.length > options.maxQueuedEvents ||\n queuedBytes > MAX_QUEUED_BYTES\n ) {\n const removed = queue.shift() as QueuedRecord;\n queuedBytes -= removed.bytes;\n dropped += 1;\n if (removed.saved) savedLost = true;\n warnOnce(\n \"full\",\n \"the event queue is full (is the device offline?); dropping the oldest events.\",\n );\n }\n };\n\n // At most `limit` chunks of `rows`, oldest records first.\n const chunksOf = (rows: QueuedRecord[], limit: number): QueuedRecord[][] => {\n const chunks: QueuedRecord[][] = [];\n if (limit <= 0) {\n return chunks;\n }\n let current: QueuedRecord[] = [];\n let currentBytes = 0;\n\n for (const queued of rows) {\n if (current.length > 0 && currentBytes + queued.bytes > chunkBudget) {\n chunks.push(current);\n current = [];\n currentBytes = 0;\n if (chunks.length >= limit) {\n break;\n }\n }\n current.push(queued);\n currentBytes += queued.bytes;\n }\n if (current.length > 0) {\n chunks.push(current);\n }\n return chunks;\n };\n\n // The same chunks, taken out of the queue; whatever does not fit stays.\n const takeChunks = (limit: number): QueuedRecord[][] => {\n const chunks = chunksOf(queue, limit);\n let taken = 0;\n for (const chunk of chunks) taken += chunk.length;\n for (const removed of queue.splice(0, taken)) {\n queuedBytes -= removed.bytes;\n }\n return chunks;\n };\n\n // A record with `extra` set, replacing any value it already had for a key.\n const stamped = (\n record: OtlpLogRecord,\n extra: OtlpLogRecord[\"attributes\"],\n ): OtlpLogRecord =>\n extra.length === 0\n ? record\n : {\n ...record,\n attributes: [\n ...record.attributes.filter(\n (attribute) => !extra.some(({ key }) => key === attribute.key),\n ),\n ...extra,\n ],\n };\n\n // The records of a chunk, the first carrying the drop count when there is\n // one; `count` is what the caller must give back if the rows never land.\n const recordsOf = (\n chunk: QueuedRecord[],\n ): { records: OtlpLogRecord[]; count: number } => {\n const records = chunk.map((queued) => queued.record);\n const count = dropped;\n const first = records[0];\n if (count > 0 && first) {\n dropped = 0;\n records[0] = {\n ...first,\n attributes: [\n ...first.attributes,\n { key: DROPPED_EVENTS_KEY, value: { intValue: String(count) } },\n ],\n };\n }\n return { records, count: first ? count : 0 };\n };\n\n const post = (body: string, keepalive = false): Promise<Outcome> => {\n const Abort = (globalThis as { AbortController?: new () => unknown })\n .AbortController;\n let controller: { signal?: unknown; abort?(): void } | undefined;\n let request: Promise<unknown>;\n try {\n controller = Abort ? (new Abort() as typeof controller) : undefined;\n request = options.fetchImpl(options.url, {\n method: \"POST\",\n headers: {\n \"content-type\": \"application/json\",\n authorization: `Bearer ${options.publicKey}`,\n [SDK_HEADER]: `${options.scope.name}/${options.scope.version}`,\n },\n body,\n keepalive,\n signal: controller?.signal,\n });\n } catch {\n // A fetch that throws synchronously must not reject out of flush():\n // the SDK would capture its own unhandled rejection, enqueue it, and\n // fail again on the next flush.\n return Promise.resolve(RETRY);\n }\n return new Promise((resolve) => {\n // A hung intake frees the slot: the batch is retried like a failure.\n const timeout = setTimeout(() => {\n try {\n controller?.abort?.();\n } catch {\n // Best-effort.\n }\n resolve(RETRY);\n }, REQUEST_TIMEOUT_MS);\n void Promise.resolve(request)\n .then(classify, () => RETRY)\n .then((outcome) => {\n clearTimeout(timeout);\n resolve(outcome);\n });\n });\n };\n\n const backingOff = (): boolean => Date.now() < retryAt;\n\n // Rows that did not land go back in enqueue order, wherever they sit\n // among rows restored by other uploads, so evict() still drops the oldest.\n const restore = (chunk: QueuedRecord[]): void => {\n queue.push(...chunk);\n queue.sort((a, b) => a.seq - b.seq);\n for (const queued of chunk) queuedBytes += queued.bytes;\n evict();\n };\n\n const backOff = (retryAfter: number | undefined, network: boolean): void => {\n const now = Date.now();\n // Concurrent uploads failing together are one failure, not four.\n if (now >= retryAt) {\n attempt += 1;\n if (network) unreachableAttempts += 1;\n }\n const ceiling = Math.min(RETRY_MAX_MS, RETRY_BASE_MS * 2 ** (attempt - 1));\n const delay = Math.max(\n retryAfter ?? 0,\n ceiling / 2 + (Math.random() * ceiling) / 2,\n );\n retryAt = Math.max(retryAt, now + delay);\n clearTimer();\n scheduleFlush();\n };\n\n /**\n * One delivery attempt and what its answer means: 2xx is done; 408, 429,\n * 5xx, a network failure, and a timeout put the rows back in the queue in\n * enqueue order behind a backoff (the caps still apply); 413 halves the batch\n * and tries each half, putting a half back in the queue instead when a\n * backoff started meanwhile (outside an unload flush or shutdown); any\n * other 4xx, or a single row too large to send, drops the rows with one\n * warning per status. Nothing is retried after shutdown().\n */\n const deliver = async (\n chunk: QueuedRecord[],\n keepalive: boolean,\n ): Promise<void> => {\n const { records, count } = recordsOf(chunk);\n const outcome = await post(buildEnvelope(records), keepalive);\n // Any answer, a refusal included, ends an outage: what it took to get\n // through is reported now, not merged into a later one.\n if (\n unreachableAttempts > 0 &&\n (outcome.kind !== \"retry\" || outcome.network !== true)\n ) {\n const attempts = unreachableAttempts;\n unreachableAttempts = 0;\n report({\n diagnostic: \"intake_unreachable\",\n attributes: { \"app.sdk.attempts\": attempts },\n });\n }\n if (outcome.kind === \"sent\") {\n attempt = 0;\n return;\n }\n dropped += count;\n if (outcome.kind === \"split\" && chunk.length > 1) {\n const half = Math.ceil(chunk.length / 2);\n for (const part of [chunk.slice(0, half), chunk.slice(half)]) {\n // A 429 on the first half (or on any other upload) means wait: the\n // rest goes back to the queue for the timer to send.\n if (!keepalive && !shutDown && backingOff()) {\n restore(part);\n scheduleFlush();\n } else {\n await deliver(part, keepalive);\n }\n }\n return;\n }\n if (outcome.kind === \"retry\") {\n if (shutDown) {\n if (chunk.some((queued) => queued.saved)) savedLost = true;\n return;\n }\n restore(chunk);\n backOff(outcome.retryAfterMs, outcome.network === true);\n return;\n }\n dropped += chunk.length;\n warnOnce(\n String(outcome.status),\n `the intake refused a batch with HTTP ${outcome.status}; dropped ${chunk.length} event(s).`,\n );\n report({\n diagnostic: \"intake_rejected\",\n attributes: { \"app.sdk.http_status\": outcome.status },\n });\n };\n\n // Every upload gets a sequence number so a flush() can wait for exactly\n // the uploads started while it waited, and no others: an older upload\n // that never answers holds its slot, not every later caller.\n let nextUploadSeq = 0;\n const uploadsBySeq = new Map<number, Promise<void>>();\n const startUpload = (chunk: QueuedRecord[], keepalive: boolean) => {\n const seq = nextUploadSeq;\n nextUploadSeq += 1;\n const upload: Promise<void> = deliver(chunk, keepalive).then(() => {\n inFlight.delete(upload);\n sending.delete(chunk);\n uploadsBySeq.delete(seq);\n if (queue.length === 0 && inFlight.size === 0 && !savedLost) {\n try {\n options.onIdle?.();\n } catch {\n // Never throw into the host.\n }\n }\n });\n inFlight.add(upload);\n sending.add(chunk);\n uploadsBySeq.set(seq, upload);\n return upload;\n };\n\n // One shared loop drains the queue: it keeps at most MAX_IN_FLIGHT_UPLOADS\n // requests open, refills each slot as soon as any upload settles (so one\n // hung upload holds only its own slot), and ends once everything queued\n // has been started or a failure started a backoff. Every flush() while it\n // runs joins this loop instead of attaching another waiter to the same\n // hung uploads, so a stalled intake never grows memory beyond the capped\n // queue.\n let draining: Promise<void> | null = null;\n const drainQueue = async (): Promise<void> => {\n while (!backingOff()) {\n for (const chunk of takeChunks(MAX_IN_FLIGHT_UPLOADS - inFlight.size)) {\n startUpload(chunk, false);\n }\n if (queue.length === 0) break;\n // A non-empty queue always fills a free slot, so `inFlight` is never\n // empty here.\n await Promise.race(inFlight);\n }\n };\n\n const flush = async (flushOptions?: {\n keepalive?: boolean;\n }): Promise<void> => {\n clearTimer();\n const keepalive = flushOptions?.keepalive === true;\n\n // An unload flush is the page's last chance and goes out whole, backoff\n // or not, and so does the one shutdown() makes; any other flush joins\n // the shared drain loop and resolves once the uploads started during its\n // wait have settled. During a backoff it sends nothing: the timer\n // retries when the backoff ends.\n if (keepalive || shutDown) {\n await Promise.all(\n takeChunks(Number.POSITIVE_INFINITY).map((chunk) =>\n startUpload(chunk, keepalive),\n ),\n );\n return;\n }\n const firstSeq = nextUploadSeq;\n while (queue.length > 0 && !backingOff()) {\n if (draining === null) {\n draining = drainQueue().finally(() => {\n draining = null;\n });\n }\n await draining;\n }\n if (queue.length > 0) scheduleFlush();\n const mine: Promise<void>[] = [];\n for (const [seq, upload] of uploadsBySeq) {\n if (seq >= firstSeq) mine.push(upload);\n }\n await Promise.all(mine);\n };\n\n return {\n enqueue(record) {\n if (shutDown) {\n return;\n }\n const bytes = utf8Length(JSON.stringify(record)) + 1;\n // One record with hundreds of long attributes can outgrow the byte cap\n // alone: it is dropped and counted, never buffered above the cap.\n if (bytes > MAX_QUEUED_BYTES) {\n dropped += 1;\n warnOnce(\n \"oversized\",\n \"dropped an event larger than the 1 MiB queue limit.\",\n );\n return;\n }\n queue.push({ record, bytes, seq: nextRecordSeq });\n nextRecordSeq += 1;\n queuedBytes += bytes;\n evict();\n\n if (held) {\n return;\n }\n if (queuedBytes >= chunkBudget && !backingOff()) {\n void flush();\n } else {\n scheduleFlush();\n }\n },\n flush,\n drainEnvelopes(recordAttributes) {\n clearTimer();\n const extra = recordAttributes ? encodeAttributes(recordAttributes) : [];\n return takeChunks(Number.POSITIVE_INFINITY).map((chunk) => {\n const { records } = recordsOf(chunk);\n return buildEnvelope(records.map((record) => stamped(record, extra)));\n });\n },\n snapshotEnvelopes(recordAttributes) {\n const extra = encodeAttributes(recordAttributes);\n savedLost = false;\n for (const queued of queue) {\n queued.record = stamped(queued.record, extra);\n const bytes = utf8Length(JSON.stringify(queued.record)) + 1;\n queuedBytes += bytes - queued.bytes;\n queued.bytes = bytes;\n queued.saved = true;\n }\n // A second snapshot while the first one's uploads are out (iOS goes\n // inactive, then background) must not drop their rows from the copy.\n const rows = new Set(queue);\n for (const chunk of sending) {\n for (const queued of chunk) if (queued.saved) rows.add(queued);\n }\n return chunksOf(\n [...rows].sort((a, b) => a.seq - b.seq),\n Number.POSITIVE_INFINITY,\n ).map((chunk) => buildEnvelope(chunk.map((queued) => queued.record)));\n },\n holdFlushes(hold) {\n held = hold;\n },\n async sendEnvelope(body) {\n const outcome = await post(body);\n if (outcome.kind === \"retry\") return false;\n if (outcome.kind !== \"sent\") {\n warnOnce(\n String(outcome.status),\n `the intake refused a stored crash report with HTTP ${outcome.status}; dropped it.`,\n );\n report({\n diagnostic: \"intake_rejected\",\n attributes: { \"app.sdk.http_status\": outcome.status },\n });\n }\n return true;\n },\n async shutdown() {\n shutDown = true;\n await flush();\n },\n };\n};\n",
18
+ "import {\n type EventAttributes,\n parseAbsoluteUrl,\n} from \"@sazabi/client-sdk-core\";\nimport { NativeModules, Platform, TurboModuleRegistry } from \"react-native\";\n\n/**\n * react-native-web renders in a real browser, where `@sazabi/browser` owns\n * capture. This SDK installs nothing there.\n */\nexport const isWebHost = (): boolean => Platform.OS === \"web\";\n\nconst stringOrUndefined = (value: unknown): string | undefined =>\n typeof value === \"string\" && value.length > 0 ? value : undefined;\n\n/**\n * Resource attributes the host can report without a native module: OTEL\n * `os.*` from `Platform`, the `device.*` fields Android's\n * `Platform.constants` carries (`Brand`, `Manufacturer`, `Model`), and on iOS\n * the manufacturer plus `device.type` from `interfaceIdiom` (`phone`, `pad`,\n * `tv`, `carplay`, `mac`, `vision`). iOS reports no model through JavaScript.\n * `os.name` stays `Platform.OS` (`ios`, `android`) on both, so queries match\n * one casing; iPadOS shows as `device.type: \"pad\"`.\n */\nexport const hostResourceAttributes = (): EventAttributes => {\n const constants = (Platform.constants ?? {}) as Record<string, unknown>;\n const ios = Platform.OS === \"ios\";\n return {\n \"os.name\": Platform.OS,\n \"os.version\": stringOrUndefined(String(Platform.Version ?? \"\")),\n \"device.manufacturer\": ios\n ? \"Apple\"\n : (stringOrUndefined(constants.Brand) ??\n stringOrUndefined(constants.Manufacturer)),\n \"device.model.identifier\": stringOrUndefined(constants.Model),\n \"device.type\": ios\n ? stringOrUndefined(constants.interfaceIdiom)\n : undefined,\n };\n};\n\nexport const isDevHost = (): boolean =>\n (globalThis as { __DEV__?: unknown }).__DEV__ === true;\n\n/**\n * The Metro (or Expo) development server this bundle was loaded from, as\n * `<origin>/`, read from `SourceCode.scriptURL` the way React Native's own\n * `getDevServer()` does. Only under `__DEV__`, and only for an http(s)\n * bundle URL: a release bundle loads from a file.\n */\nexport const devServerOrigin = (): string | undefined => {\n if (!isDevHost()) return undefined;\n try {\n type SourceCode = {\n scriptURL?: unknown;\n getConstants?(): { scriptURL?: unknown };\n };\n const module = (TurboModuleRegistry?.get?.(\"SourceCode\") ??\n NativeModules?.SourceCode) as SourceCode | null | undefined;\n const scriptURL = module?.getConstants?.().scriptURL ?? module?.scriptURL;\n const url =\n typeof scriptURL === \"string\" ? parseAbsoluteUrl(scriptURL) : undefined;\n return url && url.protocol.startsWith(\"http\")\n ? `${url.origin}/`\n : undefined;\n } catch {\n return undefined;\n }\n};\n\n/**\n * A request URL as the core expects it: absolute, credentials stripped.\n * Relative references are not requests on a native host, so they resolve to\n * nothing and are neither captured nor injected.\n */\nexport const resolveUrl = (raw: string): string | undefined =>\n parseAbsoluteUrl(raw)?.href;\n",
19
+ "import {\n activateNetworkCapture,\n deactivateNetworkCapture,\n ensureXhrPatch,\n type NetworkCaptureOptions,\n resetWrapperLedger,\n uninstallXhrPatch,\n} from \"@sazabi/client-sdk-core\";\n\nexport interface NetworkCaptureHandle {\n teardown(): void;\n}\n\n/**\n * React Native's `fetch` is a polyfill over its own `XMLHttpRequest`, so the\n * core's XHR patch alone sees every request this host makes (`fetch`,\n * `axios`, direct XHR) and a `fetch` wrapper would emit each one twice.\n */\nexport const ensureNetworkPatches = (): void => {\n ensureXhrPatch();\n};\n\nexport const uninstallNetworkPatches = (): void => {\n uninstallXhrPatch();\n resetWrapperLedger();\n};\n\nexport const installNetworkCapture = (\n options: NetworkCaptureOptions,\n): NetworkCaptureHandle => {\n ensureNetworkPatches();\n activateNetworkCapture(options);\n return {\n teardown() {\n deactivateNetworkCapture();\n },\n };\n};\n"
20
+ ],
21
+ "mappings": "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",
22
+ "debugId": "59FDD26FD27CC55664756E2164756E21",
23
+ "names": []
24
+ }
@@ -0,0 +1,8 @@
1
+ import type { EventAttributes } from "./types.js";
2
+ export declare const truncateValue: (value: string, max?: number) => string;
3
+ /**
4
+ * Flatten nested metadata into dot-keyed scalar attributes. Non-scalar leaves
5
+ * are JSON-stringified; strings are length-capped so a hostile or buggy app
6
+ * cannot inflate rows.
7
+ */
8
+ export declare const flattenAttributes: (input: Record<string, unknown>, prefix?: string, depth?: number) => EventAttributes;
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Monotonic milliseconds for durations and ordering. `performance.now()` where
3
+ * the host has it (every browser, React Native on Hermes); `Date.now()` as the
4
+ * fallback so the core never assumes a global.
5
+ */
6
+ export declare const monotonicNow: () => number;
@@ -0,0 +1,23 @@
1
+ import type { ClientEvent, ConsoleCaptureLevel } from "./types.js";
2
+ export interface ConsoleCaptureOptions {
3
+ emit(event: ClientEvent): void;
4
+ levels: ConsoleCaptureLevel[];
5
+ /** Lines the SDK itself prints start with this and are never re-captured. */
6
+ selfPrefix: string;
7
+ }
8
+ export interface ConsoleCaptureHandle {
9
+ teardown(): void;
10
+ }
11
+ /**
12
+ * Print through the console without mirroring the line into the event
13
+ * stream: for a host-facing message the SDK prints on another component's
14
+ * behalf (the dev-mode rejection warning it relays for React Native).
15
+ */
16
+ export declare const withoutConsoleCapture: (print: () => void) => void;
17
+ /**
18
+ * Mirror selected console levels into the event stream as `log` events.
19
+ * The original console method always runs first (devtools behavior is never
20
+ * altered) and capture failures are swallowed. Lines the SDK itself prints
21
+ * (prefixed with `selfPrefix`) are skipped to avoid self-capture.
22
+ */
23
+ export declare const installConsoleCapture: (options: ConsoleCaptureOptions) => ConsoleCaptureHandle;
@@ -0,0 +1,21 @@
1
+ /**
2
+ * @sazabi/client-sdk-core: the platform-neutral half of the Sazabi client
3
+ * SDKs. Private and bundled into @sazabi/browser and @sazabi/react-native at
4
+ * build time; never published, never imported by anything else. Compiles
5
+ * without the DOM lib (see host-globals.d.ts), so a browser global here is a
6
+ * typecheck error.
7
+ */
8
+ export { flattenAttributes, truncateValue } from "./attributes.js";
9
+ export { monotonicNow } from "./clock.js";
10
+ export { type ConsoleCaptureHandle, type ConsoleCaptureOptions, installConsoleCapture, withoutConsoleCapture, } from "./console.js";
11
+ export { intakeBaseFromUrl, publicKeyFromIntakeUrl, type ResolvedIntakeEndpoint, resolveIntakeEndpoint, } from "./intake-url.js";
12
+ export { activateNetworkCapture, consumeWrapperSeen, deactivateNetworkCapture, emitNetworkEvent, ensureXhrPatch, getActiveNetworkCapture, isAborted, isActiveInjectionTarget, isAllowedForInjection, type NetworkCaptureOptions, type NetworkEventDetails, readRequestId, recordWrapperRequest, redactUrl, resetWrapperLedger, severityForStatus, uninstallXhrPatch, urlPath, } from "./network-capture.js";
13
+ export { isWrapped, markWrapped, WRAPPED_MARKER } from "./patch-marker.js";
14
+ export { createPointerTracker, type DerivedTap, type PointerContact, type PointerTarget, type PointerTracker, type PointerTrackerOptions, type PointerType, } from "./pointer.js";
15
+ export { createRandomBytes, newPrefixedId, type RandomBytes, randomHex, toHex, } from "./random.js";
16
+ export { createMemoryStore, createSessionManager, type SessionManager, type SessionManagerOptions, type SessionStore, } from "./session.js";
17
+ export { createTraceContextManager, type HostSpanContext, readHostSpanContext, type RequestTraceContext, type TraceContextManager, } from "./trace-context.js";
18
+ export { createSdkHealthReporter, SDK_DIAGNOSTIC_KEY, type SdkHealthDiagnostic, type SdkHealthReport, type SdkHealthReporter, } from "./sdk-health.js";
19
+ export { createTransport, encodeLogRecord, type FetchLike, type OtlpLogRecord, type Transport, type TransportOptions, } from "./transport.js";
20
+ export { isUnderBase, type ParsedUrl, parseAbsoluteUrl } from "./url.js";
21
+ export type { AttributeValue, ClientEvent, ClientEventType, ConsoleCaptureConfig, ConsoleCaptureLevel, EventAttributes, EventSeverity, NetworkCaptureConfig, PointerCaptureConfig, } from "./types.js";
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Resolving the intake endpoint from a single URL.
3
+ *
4
+ * Every Sazabi log source is configured with one intake URL, and public keys
5
+ * stay a low-level detail customers never handle. The key is already in the
6
+ * URL: the intake edge reconstructs it from the hostname
7
+ * (terraform/main/cloudfront-functions/intake-router.js), so the SDK does the
8
+ * same reconstruction locally and sends the Bearer header itself.
9
+ *
10
+ * Two keyed shapes exist, matching the edge's two grammars:
11
+ * {hex}.{region}.intake.{domain} prefixless (what log sources issue)
12
+ * {adapter}-{hex}.{region}.intake.{domain} keyed-adapter (legacy, still served)
13
+ *
14
+ * A URL in neither shape is not an error: it may be a proxy or a custom
15
+ * endpoint. The SDK then requires an explicit `publicKey`, so a
16
+ * misconfiguration surfaces as a clear warning rather than silent no-auth.
17
+ */
18
+ export interface ResolvedIntakeEndpoint {
19
+ /**
20
+ * Base to POST to: origin plus any proxy path prefix, no trailing slash.
21
+ * The transport appends `/v1/logs`.
22
+ */
23
+ baseUrl: string;
24
+ /** Credential for the Authorization header, derived or explicit. */
25
+ publicKey: string;
26
+ }
27
+ /** Public key embedded in a keyed intake hostname, if it carries one. */
28
+ export declare const publicKeyFromIntakeUrl: (url: string) => string | undefined;
29
+ export declare const intakeBaseFromUrl: (url: string) => string | undefined;
30
+ /**
31
+ * Resolve the endpoint from config. An explicit `publicKey` always wins, so a
32
+ * non-keyed or proxied endpoint stays configurable.
33
+ */
34
+ export declare const resolveIntakeEndpoint: (config: {
35
+ intakeUrl?: string;
36
+ intakeHost?: string;
37
+ publicKey?: string;
38
+ }) => ResolvedIntakeEndpoint | undefined;
@@ -0,0 +1,90 @@
1
+ import type { RequestTraceContext, TraceContextManager } from "./trace-context.js";
2
+ import type { ClientEvent, EventSeverity } from "./types.js";
3
+ /**
4
+ * Everything the network patches need at request time. A binding builds one
5
+ * at init and activates it; the patches themselves install dormant (possibly
6
+ * before init, via a register entrypoint) and consult the active slot per
7
+ * call. Undefined slot = transparent passthrough.
8
+ */
9
+ export interface NetworkCaptureOptions {
10
+ emit(event: ClientEvent): void;
11
+ traceContext: TraceContextManager;
12
+ /** Emit network events. */
13
+ capture: boolean;
14
+ /** Inject `traceparent` into allowlisted requests. */
15
+ propagateTraceContext: boolean;
16
+ /** Injection allowlist; empty defers to `isDefaultInjectionTarget`. */
17
+ allowlist: (string | RegExp)[];
18
+ /** Query parameter names redacted from `url.full` beside the default list. */
19
+ redactQueryParams?: (string | RegExp)[];
20
+ /** Response headers probed for a platform request id, in priority order. */
21
+ requestIdHeaders: string[];
22
+ /** SDK-internal endpoints: never captured, never injected. */
23
+ isInternalUrl(url: string): boolean;
24
+ /**
25
+ * Absolute URL for a request target as the host resolves it, with embedded
26
+ * credentials stripped; undefined when the input cannot be parsed.
27
+ */
28
+ resolveUrl(raw: string): string | undefined;
29
+ /** Injection policy when the allowlist is empty (same-origin on the web). */
30
+ isDefaultInjectionTarget(resolved: string): boolean;
31
+ /** Origin elided from the display path in event bodies, if the host has one. */
32
+ displayOrigin?: string;
33
+ /** Host-reported offline state; a failure while offline reads as an abort. */
34
+ isOffline?(): boolean;
35
+ }
36
+ export declare const activateNetworkCapture: (options: NetworkCaptureOptions) => void;
37
+ export declare const deactivateNetworkCapture: () => void;
38
+ export declare const getActiveNetworkCapture: () => NetworkCaptureOptions | undefined;
39
+ /** Compact display path: pathname when on the display origin, host+path otherwise. */
40
+ export declare const urlPath: (resolved: string, displayOrigin?: string) => string;
41
+ /** Status 0 means transport failure. */
42
+ export declare const severityForStatus: (status: number) => EventSeverity;
43
+ /**
44
+ * A recorded URL: the value of every sensitive parameter after the path
45
+ * replaced with `[redacted]`, in the query and in the fragment. `?` separates
46
+ * inside the fragment as well, so a hash router's `#/reset?token=…` is
47
+ * covered. Credentials in the authority are already gone (`resolveUrl`
48
+ * strips them). Applied to `url.full` of requests, resource rows and
49
+ * navigations, and to the page URL the browser SDK stamps on every event.
50
+ */
51
+ export declare const redactUrl: (url: string, extra?: (string | RegExp)[]) => string;
52
+ export declare const isAllowedForInjection: (options: NetworkCaptureOptions, resolved: string) => boolean;
53
+ /** Injection eligibility for the active config; used by the browser's resource
54
+ * observer to decide whether a bypassed request is a diagnosable gap. */
55
+ export declare const isActiveInjectionTarget: (resolved: string) => boolean;
56
+ export declare const recordWrapperRequest: (url: string) => void;
57
+ /** Consume a wrapper-seen record matching this request, if any. */
58
+ export declare const consumeWrapperSeen: (url: string, startTime: number) => boolean;
59
+ export declare const resetWrapperLedger: () => void;
60
+ export interface NetworkEventDetails {
61
+ kind: "fetch" | "xhr";
62
+ method: string;
63
+ resolvedUrl: string;
64
+ status: number;
65
+ durationMs: number;
66
+ severity?: EventSeverity;
67
+ failureMessage?: string;
68
+ injected?: RequestTraceContext;
69
+ requestId?: string;
70
+ requestIdHeader?: string;
71
+ }
72
+ export declare const emitNetworkEvent: (options: NetworkCaptureOptions, details: NetworkEventDetails) => void;
73
+ export declare const readRequestId: (options: NetworkCaptureOptions, getHeader: (name: string) => string | null | undefined) => {
74
+ requestId?: string;
75
+ requestIdHeader?: string;
76
+ };
77
+ export declare const isAborted: (error: unknown, signalAborted?: boolean, offline?: boolean) => boolean;
78
+ /**
79
+ * Install the dormant XMLHttpRequest patch around whatever implementation is
80
+ * currently live. Idempotent, and re-wraps when a later actor replaced the
81
+ * prototype methods after an earlier install (the displaced wrapper is an
82
+ * inert passthrough, so single-processing holds).
83
+ */
84
+ export declare const ensureXhrPatch: () => void;
85
+ /**
86
+ * Restore the natives if the current implementations are still ours; if
87
+ * someone patched on top of us, restoring would clobber them, and the
88
+ * inactive wrappers pass through.
89
+ */
90
+ export declare const uninstallXhrPatch: () => void;
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Shared across every Sazabi client SDK so two SDKs bundled into one app
3
+ * recognize each other's wrappers and never double-wrap a primitive.
4
+ */
5
+ export declare const WRAPPED_MARKER = "__sazabiBrowserWrapped";
6
+ /** Best-effort idempotency marker on wrapper functions. */
7
+ export declare const markWrapped: (fn: object) => void;
8
+ export declare const isWrapped: (fn: unknown) => boolean;