@sendoracloud/sdk-web 3.11.0 → 3.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +1 -1
- package/dist/index.d.cts +229 -9
- package/dist/index.d.ts +229 -9
- package/dist/index.js +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";var ae=Object.defineProperty;var De=Object.getOwnPropertyDescriptor;var Le=Object.getOwnPropertyNames;var Ue=Object.prototype.hasOwnProperty;var xe=(s,e)=>{for(var t in e)ae(s,t,{get:e[t],enumerable:!0})},Oe=(s,e,t,r)=>{if(e&&typeof e=="object"||typeof e=="function")for(let n of Le(e))!Ue.call(s,n)&&n!==t&&ae(s,n,{get:()=>e[n],enumerable:!(r=De(e,n))||r.enumerable});return s};var Ne=s=>Oe(ae({},"__esModule",{value:!0}),s);var pt={};xe(pt,{AlreadyIdentifiedError:()=>q,Analytics:()=>P,Audiences:()=>K,Auth:()=>H,AuthError:()=>c,Chatbot:()=>x,Consent:()=>O,CredentialInUseError:()=>B,EmailAlreadyTakenError:()=>M,FeatureFlags:()=>D,InAppMessaging:()=>U,LinkError:()=>h,Links:()=>V,SendoraCloud:()=>te,Surveys:()=>L,ValidationError:()=>p,WebPush:()=>F,default:()=>ht,extractShortcodeFromUrl:()=>Ce,safeUrl:()=>A,sanitizeHtml:()=>Q});module.exports=Ne(pt);var ge=/^[a-zA-Z][a-zA-Z0-9._:-]{0,127}$/;var Me=new Set(["__proto__","constructor","prototype"]),p=class extends Error{constructor(e){super(`[SendoraCloud] ${e}`),this.name="SendoraValidationError"}};function fe(s){if(typeof s!="string"||!ge.test(s))throw new p(`Invalid event name: must match ${ge}, got "${String(s)}".`)}function me(s){if(s==null)return;if(typeof s!="object"||Array.isArray(s))throw new p("Properties must be a plain object, not an array or primitive.");let e=ye(s);if(e>5)throw new p(`Properties nested too deeply (${e} levels, max 5).`);we(s);let t;try{t=JSON.stringify(s).length}catch{throw new p("Properties contain a circular reference or unserializable value.")}if(t>32768)throw new p(`Properties too large (${t} bytes, max 32768).`)}function ye(s,e=0){if(e>6||s==null||typeof s!="object")return e;let t=e;for(let r of Object.keys(s)){let n=ye(s[r],e+1);n>t&&(t=n)}return t}function we(s){if(!(s==null||typeof s!="object"))for(let e of Object.keys(s)){if(Me.has(e))throw new p(`Forbidden property key "${e}" (prototype pollution guard).`);we(s[e])}}var He=/^pk_[A-Za-z0-9_-]{16,}$/;function ve(s){if(!s||typeof s!="string")throw new p("apiKey is required.");if(s.startsWith("sk_")||s.startsWith("sendora_secret_"))throw new p("Secret keys must NEVER be used in browser SDK. Use a publishable key (pk_...).");if(!He.test(s))throw new p("Invalid publishable key format. Expected pk_... (17+ chars).")}function ke(s){let e=s.replace(/\/$/,"");if(!(e.startsWith("http://localhost")||e.startsWith("http://127.0.0.1"))&&!e.startsWith("https://"))throw new p("apiUrl must be https:// (only http://localhost is allowed for dev).")}var Ae={name:"@sendoracloud/sdk-web",version:"3.11.0",description:"Sendora Cloud Web SDK \u2014 analytics, feature flags, surveys, in-app messaging, chatbot, auth, deep links (Branch / Firebase parity). Zero dependencies, TypeScript, SSR-safe.",type:"module",main:"./dist/index.cjs",module:"./dist/index.js",types:"./dist/index.d.ts",exports:{".":{types:"./dist/index.d.ts",import:"./dist/index.js",require:"./dist/index.cjs"}},files:["dist","README.md","LICENSE"],keywords:["sendora","analytics","feature-flags","surveys","in-app-messaging","chatbot","tracking","customer-engagement"],homepage:"https://sendoracloud.com/sdks",repository:{type:"git",url:"git+https://github.com/sendoracloud/sdk-web.git"},bugs:{url:"https://github.com/sendoracloud/sdk-web/issues"},author:"Sendora Cloud <hello@sendoracloud.com>",license:"Apache-2.0",publishConfig:{access:"public"},sideEffects:!1,scripts:{build:"tsup src/index.ts --format cjs,esm --dts --clean --minify",typecheck:"tsc --noEmit",prepublishOnly:"npm run build"},devDependencies:{typescript:"^5.4.0",tsup:"^8.0.0"}};var S=Ae.version;var Ke="https://api.sendoracloud.com",Ve=1e4,$e="@sendoracloud/sdk-web",G=class{constructor(e){let t=(e.apiUrl??Ke).replace(/\/$/,"");ke(t),this.apiKey=e.apiKey,this.baseUrl=t,this.debug=!!e.debug}get(e,t){return this.request("GET",e,void 0,t)}post(e,t,r){return this.request("POST",e,t,r)}patch(e,t,r){return this.request("PATCH",e,t,r)}delete(e,t){return this.request("DELETE",e,void 0,t)}async request(e,t,r,n){let i=`${this.baseUrl}/api/v1${t}`,o=typeof AbortController<"u"?new AbortController:null,a=o?setTimeout(()=>o.abort(),Ve):null;try{let l=await fetch(i,{method:e,headers:{"Content-Type":"application/json","X-API-Key":this.apiKey,"X-Sendora-SDK-Name":$e,"X-Sendora-SDK-Version":S,...n??{}},body:r?JSON.stringify(r):void 0,signal:o?o.signal:void 0});a&&clearTimeout(a);let u;try{u=await l.json()}catch{u={success:!1,error:{code:"PARSE_ERROR",message:`HTTP ${l.status}`}}}if(this.debug&&console.debug(`[SendoraCloud] ${e} ${t} \u2192 ${l.status}`),!l.ok)if(t.startsWith("/auth-service/")&&!this.debug)console.warn(`[SendoraCloud] ${e} ${t} \u2192 HTTP ${l.status}`);else{let f=u?.error?.code??"HTTP_ERROR",m=u?.error?.message??`HTTP ${l.status}`,w=u?.error?.details?.fieldErrors,R=w?` (${Object.entries(w).map(([v,se])=>`${v}: ${(se??[]).join(", ")}`).join("; ")})`:"";console.warn(`[SendoraCloud] ${e} ${t} \u2192 HTTP ${l.status} \u2014 ${f}: ${m}${R}`)}return u}catch(l){a&&clearTimeout(a);let u=l instanceof Error&&l.name==="AbortError"?"timeout":"error";return console.warn(`[SendoraCloud] ${e} ${t} network ${u}`),{success:!1,error:{code:"NETWORK_ERROR",message:u}}}}};var Y="sendora_",X=class{constructor(){this.memoryStore=new Map;this.useLocalStorage=this.checkLocalStorage()}get(e){let t=Y+e;if(this.useLocalStorage)try{return localStorage.getItem(t)}catch{return this.memoryStore.get(t)??null}return this.memoryStore.get(t)??null}set(e,t){let r=Y+e;if(this.useLocalStorage)try{localStorage.setItem(r,t);return}catch{}this.memoryStore.set(r,t)}remove(e){let t=Y+e;if(this.useLocalStorage)try{localStorage.removeItem(t)}catch{}this.memoryStore.delete(t)}clearPrefix(e){let t=Y+e;if(this.useLocalStorage)try{for(let r=localStorage.length-1;r>=0;r--){let n=localStorage.key(r);n&&n.startsWith(t)&&localStorage.removeItem(n)}}catch{}for(let r of Array.from(this.memoryStore.keys()))r.startsWith(t)&&this.memoryStore.delete(r)}checkLocalStorage(){try{if(typeof localStorage>"u")return!1;let e="__sendora_test__";return localStorage.setItem(e,"1"),localStorage.removeItem(e),!0}catch{return!1}}};function _(s=""){let e=new Uint8Array(16),t=typeof globalThis<"u"&&"crypto"in globalThis?globalThis.crypto:void 0;if(!t||typeof t.getRandomValues!="function")throw new Error("[SendoraCloud] crypto.getRandomValues unavailable \u2014 refusing to mint IDs without a CSPRNG");t.getRandomValues(e);let r="";for(let n of e)r+=n.toString(16).padStart(2,"0");return s+r}var Ie=500,ce=10,le=6e4,P=class{constructor(e,t,r,n){this.queue=[];this.timer=null;this.userId=null;this.identityToken=null;this.consecutiveFailures=0;this.nextFlushAfter=0;this.breakerOpenAt=0;this.breakerWarned=!1;this.http=t,this.storage=r,this.consent=n,this.projectId=e.projectId,this.flushInterval=e.flushInterval??5e3,this.maxQueueSize=e.maxQueueSize??20,this.persistUserId=!!e.persistUserId,this.stripQueryParams=e.stripQueryParams!==!1,this.appVersion=e.appVersion,this.storage.get("schema_version")===null&&this.storage.set("schema_version","1");let i=this.storage.get("anonymous_id");if(i?this.anonymousId=i:(this.anonymousId=_("anon_"),this.storage.set("anonymous_id",this.anonymousId)),this.persistUserId){let o=this.storage.get("user_id");o&&(this.userId=o)}this.timer=setInterval(()=>this.flush(),this.flushInterval),typeof window<"u"&&window.addEventListener("beforeunload",()=>this.flush()),this.consent.subscribe(o=>{o&&this.flush()})}track(e,t){fe(e),t&&me(t),this.queue.length>=Ie&&this.queue.shift(),this.queue.push({eventType:e,properties:t??{},userId:this.userId??void 0,anonymousId:this.anonymousId,timestamp:new Date().toISOString(),identityToken:this.identityToken??void 0}),this.queue.length>=this.maxQueueSize&&this.flush()}page(e){if(typeof window>"u")return;let t=this.stripQueryParams?window.location.origin+window.location.pathname:window.location.href;this.track("page.viewed",{url:t,path:window.location.pathname,title:document.title,referrer:document.referrer||void 0,...e})}identify(e,t,r){if(typeof e!="string"||e.length===0||e.length>256)throw new Error("[SendoraCloud] userId must be a 1-256 char string.");this.userId=e,this.identityToken=r?.identityToken??null,this.persistUserId&&this.storage.set("user_id",e),this.track("user.identified",{...t})}reset(){this.userId=null,this.identityToken=null,this.storage.remove("user_id"),this.anonymousId=_("anon_"),this.storage.set("anonymous_id",this.anonymousId),this.storage.clearPrefix("iam_shown_"),this.storage.clearPrefix("survey_shown_")}dropQueue(){this.queue=[]}async flush(){if(this.queue.length===0||!this.consent.isGranted()||Date.now()<this.nextFlushAfter)return;this.consecutiveFailures>ce&&Date.now()-this.breakerOpenAt>=le&&(this.consecutiveFailures=0,this.breakerOpenAt=0,this.breakerWarned=!1);let e=this.queue.splice(0,100),t=this.getSessionId(),r=e.map(a=>({projectId:this.projectId,module:"custom",eventType:a.eventType,userId:a.userId,anonymousId:a.anonymousId,identityToken:a.identityToken,timestamp:a.timestamp??new Date().toISOString(),properties:a.properties??{},context:this.getContext(),...t?{sessionId:t}:{},consent:["analytics"]}));if((await this.http.post("/events/batch",{events:r})).success){this.consecutiveFailures=0,this.breakerOpenAt=0,this.breakerWarned=!1;return}if(this.consecutiveFailures++,this.consecutiveFailures>ce){this.breakerWarned||(this.breakerOpenAt=Date.now(),this.breakerWarned=!0,console.warn(`[SendoraCloud] Analytics circuit breaker open after ${ce} consecutive failed flushes \u2014 events will be dropped until a flush succeeds or ${le}ms elapse.`));return}let i=Math.min(le,1e3*2**this.consecutiveFailures);this.nextFlushAfter=Date.now()+i;let o=Math.max(0,Ie-this.queue.length);o>0&&this.queue.unshift(...e.slice(0,o))}destroy(){this.timer&&clearInterval(this.timer),this.timer=null,this.flush()}getSessionId(){try{return typeof sessionStorage>"u"?void 0:sessionStorage.getItem("sendora_session_id")||void 0}catch{return}}getContext(){return typeof window>"u"?{sdk:{name:"@sendoracloud/sdk-web",version:S}}:{page:{url:this.stripQueryParams?window.location.origin+window.location.pathname:window.location.href,path:window.location.pathname,title:document.title},referrer:document.referrer||void 0,locale:typeof navigator<"u"?navigator.language:void 0,device:this.buildDevice(),sdk:{name:"@sendoracloud/sdk-web",version:S}}}buildDevice(){if(typeof navigator>"u")return;let e=navigator.userAgent||"",r=navigator.userAgentData?.mobile??/Mobi|Android|iPhone|iPod/i.test(e);return{type:/iPad|Tablet|(?:Android(?!.*Mobile))/i.test(e)?"tablet":r?"mobile":"desktop",os:"Web",osVersion:"",model:"",appVersion:this.resolveAppVersion(),browser:We(e)}}resolveAppVersion(){if(this.appVersion)return this.appVersion;if(typeof document<"u"){let r=document.querySelector('meta[name="sendora:app-version"]')?.getAttribute("content");if(r)return r}let e=globalThis.__SENDORA_APP_VERSION__;return typeof e=="string"?e:""}};function We(s){let e=[["Edge",/Edg\/(\d+)/],["Opera",/OPR\/(\d+)/],["Firefox",/Firefox\/(\d+)/],["Samsung Internet",/SamsungBrowser\/(\d+)/],["Chrome",/Chrome\/(\d+)/],["Safari",/Version\/(\d+)[^ ]* Safari/]];for(let[t,r]of e){let n=r.exec(s);if(n)return`${t} ${n[1]}`}return"Unknown"}var D=class{constructor(e,t,r){this.cache=new Map;this.refreshInterval=null;this.http=t,this.storage=r;let n=this.storage.get("flags");if(n)try{let i=JSON.parse(n);if(Array.isArray(i))for(let o of i)ue(o)&&this.cache.set(o.key,o)}catch{}}setContext(e){this.userId=e.userId,this.entityId=e.entityId,this.environment=e.environment}async getFlag(e){if(typeof e!="string"||e.length===0||e.length>128)return null;let t=this.cache.get(e);if(t)return t.value;let r=await this.http.post("/feature-flags/evaluate",{key:e,userId:this.userId,entityId:this.entityId,environment:this.environment});return r.success&&r.data&&ue(r.data)?(this.cache.set(e,r.data),this.persistCache(),r.data.value):null}async isEnabled(e,t=!1){let r=await this.getFlag(e);return typeof r=="boolean"?r:t}async refreshAll(){let e=await this.http.post("/feature-flags/evaluate-all",{userId:this.userId,entityId:this.entityId,environment:this.environment});if(e.success&&Array.isArray(e.data)){this.cache.clear();for(let t of e.data)ue(t)&&this.cache.set(t.key,t);this.persistCache()}}startAutoRefresh(e=6e4){this.stopAutoRefresh(),this.refreshAll(),this.refreshInterval=setInterval(()=>this.refreshAll(),e)}stopAutoRefresh(){this.refreshInterval&&clearInterval(this.refreshInterval),this.refreshInterval=null}getAllFlags(){let e={};for(let[t,r]of this.cache)e[t]=r.value;return e}reset(){this.cache.clear(),this.storage.remove("flags"),this.userId=void 0,this.entityId=void 0,this.environment=void 0}destroy(){this.stopAutoRefresh()}persistCache(){this.storage.set("flags",JSON.stringify(Array.from(this.cache.values())))}};function ue(s){if(s==null||typeof s!="object")return!1;let e=s;return Object.prototype.hasOwnProperty.call(e,"__proto__")||Object.prototype.hasOwnProperty.call(e,"constructor")?!1:typeof e.key=="string"&&"value"in e}var qe=32*1024,L=class{constructor(e,t,r){this.activeSurvey=null;this.http=t,this.storage=r}onDisplay(e){this.onSurveyDisplay=e}async showSurvey(e){if(typeof e!="string"||!/^[A-Za-z0-9_-]{6,64}$/.test(e))return null;let t=`survey_shown_${e}`;if(this.storage.get(t))return null;let r=await this.http.get(`/surveys/${encodeURIComponent(e)}/config`);return!r.success||!r.data||!Be(r.data)?null:(this.activeSurvey=r.data,this.storage.set(t,"1"),this.onSurveyDisplay&&this.onSurveyDisplay(r.data),r.data)}async submitResponse(e,t,r=!0){let n=0;try{n=JSON.stringify(t).length}catch{return!1}if(n>qe)return!1;let i=await this.http.post("/surveys/responses",{surveyId:e,answers:t,completed:r});return i.success&&(this.activeSurvey=null),i.success}dismiss(){this.activeSurvey=null}getActiveSurvey(){return this.activeSurvey}};function Be(s){if(s==null||typeof s!="object")return!1;let e=s;return typeof e.id!="string"||typeof e.name!="string"||!Array.isArray(e.questions)?!1:e.questions.every(ze)}function ze(s){if(s==null||typeof s!="object")return!1;let e=s;return typeof e.id=="string"&&typeof e.text=="string"}function A(s){if(!(!s||typeof s!="string"))try{let e=typeof window<"u"?window.location.href:"https://local/",t=new URL(s,e);return t.protocol==="http:"||t.protocol==="https:"?t.href:void 0}catch{return}}var je=new Set(["b","i","u","em","strong","a","p","br","span","ul","ol","li","h1","h2","h3","h4"]),Ge={a:new Set(["href","target","rel"])};function Q(s){if(typeof DOMParser>"u")return"";let t=new DOMParser().parseFromString(`<div>${s}</div>`,"text/html").body.firstElementChild;return t?(be(t),t.innerHTML):""}function be(s){let e=Array.from(s.children);for(let t of e){let r=t.tagName.toLowerCase();if(!je.has(r)){let i=document.createTextNode(t.textContent??"");t.replaceWith(i);continue}let n=Ge[r]??new Set;for(let i of Array.from(t.attributes)){if(!n.has(i.name.toLowerCase())){t.removeAttribute(i.name);continue}if(i.name.toLowerCase()==="href"){let o=A(i.value);o?t.setAttribute("href",o):t.removeAttribute(i.name)}}r==="a"&&(t.setAttribute("rel","noopener noreferrer"),t.setAttribute("target","_blank")),be(t)}}var U=class{constructor(e,t,r){this.messages=[];this.pollInterval=null;this.http=t,this.storage=r}onDisplay(e){this.onMessageDisplay=e}async checkMessages(){let e=await this.http.get("/in-app-messages/active");if(!e.success||!Array.isArray(e.data))return null;this.messages=e.data.map(Ye);for(let t of this.messages){let r=`iam_shown_${t.id}`;if(!this.storage.get(r))return this.storage.set(r,"1"),this.onMessageDisplay&&this.onMessageDisplay(t),t}return null}async recordAction(e,t){await this.http.post("/in-app-messages/impressions",{messageId:e,action:t})}startPolling(e=3e5){this.stopPolling(),this.checkMessages(),this.pollInterval=setInterval(()=>this.checkMessages(),e)}stopPolling(){this.pollInterval&&clearInterval(this.pollInterval),this.pollInterval=null}getMessages(){return this.messages}destroy(){this.stopPolling()}};function Ye(s){return{...s,content:{...s.content,ctaUrl:A(s.content.ctaUrl),imageUrl:A(s.content.imageUrl)}}}var Xe=4e3,x=class{constructor(e,t,r){this.chatbotId=null;this.messages=[];this.http=t,this.storage=r;let n=this.storage.get("chat_session_id");this.sessionId=n??_("chat_"),n||this.storage.set("chat_session_id",this.sessionId)}init(e){if(typeof e!="string"||!/^[A-Za-z0-9_-]{6,64}$/.test(e))throw new Error("[SendoraCloud] Invalid chatbotId.");this.chatbotId=e}onNewMessage(e){this.onMessage=e}async sendMessage(e,t){if(!this.chatbotId||typeof e!="string"||e.length===0||e.length>Xe)return null;let r={role:"user",content:e,timestamp:new Date().toISOString()};this.messages.push(r);let n=await this.http.post("/chatbot/message",{chatbotId:this.chatbotId,sessionId:this.sessionId,message:e,userId:t});if(n.success&&n.data&&typeof n.data.reply=="string"){let i={role:"assistant",content:n.data.reply,timestamp:new Date().toISOString()};return this.messages.push(i),this.onMessage&&this.onMessage(i),i}return null}getMessages(){return[...this.messages]}newSession(){this.messages=[],this.sessionId=_("chat_"),this.storage.set("chat_session_id",this.sessionId)}getSessionId(){return this.sessionId}};var O=class{constructor(e,t){this.listeners=new Set;this.granted=e,this.http=t}isGranted(){return this.granted}grant(){this.granted||(this.granted=!0,this.emit())}revoke(){this.granted&&(this.granted=!1,this.emit())}subscribe(e){return this.listeners.add(e),()=>this.listeners.delete(e)}async record(e){if(!this.http)throw new Error("consent.record requires SDK init (HttpClient unavailable)");let t=await this.http.post("/consent/record",e);if(!t.success||!t.data)throw new Error(t.error?.message??"consent.record failed");return t.data}async requestDeletion(e){if(!this.http)throw new Error("consent.requestDeletion requires SDK init (HttpClient unavailable)");let t=await this.http.post("/consent/deletion-request",e);if(!t.success||!t.data)throw new Error(t.error?.message??"consent.requestDeletion failed");return t.data}emit(){for(let e of this.listeners)try{e(this.granted)}catch{}}};var T="auth_access_token",E="auth_access_expires",g="auth_refresh_token",N="auth_user",Qe=3e4,Je=/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;function y(s){if(!s||typeof s!="object")return!1;let e=s,t=e.user,r=e.tokens;return!(!t||typeof t.id!="string"||t.id.length===0||!r||typeof r.accessToken!="string"||r.accessToken.length===0||typeof r.refreshToken!="string"||r.refreshToken.length===0||typeof r.expiresIn!="number"||r.expiresIn<=0||t.isAnonymous!==void 0&&typeof t.isAnonymous!="boolean"||t.email!==void 0&&t.email!==null&&typeof t.email!="string"||t.emailVerified!==void 0&&typeof t.emailVerified!="boolean"||t.name!==void 0&&t.name!==null&&typeof t.name!="string"||t.signupMethod!==void 0&&t.signupMethod!==null&&typeof t.signupMethod!="string"||t.lastLoginMethod!==void 0&&t.lastLoginMethod!==null&&typeof t.lastLoginMethod!="string"||e.retiredAnonUserId!==void 0&&typeof e.retiredAnonUserId!="string")}var M=class extends Error{constructor(e="An account with this email already exists"){super(e),this.name="EmailAlreadyTakenError"}},q=class extends Error{constructor(e="Already signed in with an identity; use link*() to add a credential, or sign out first"){super(e),this.name="AlreadyIdentifiedError"}},B=class extends Error{constructor(e="This credential is already linked to a different account"){super(e),this.name="CredentialInUseError"}},c=class extends Error{constructor(e,t){super(t),this.name="AuthError",this.code=e}},H=class{constructor(e,t,r,n,i,o){this.user=null;this.accessToken=null;this.accessExpiresAt=0;this.inflight=Promise.resolve();this.refreshInflight=null;this.refreshFailureCount=0;this.refreshCooldownUntil=0;this.visibilityResetUnsub=null;this.proactiveRefreshTimer=null;this.proactiveRefreshCleanup=null;this.takeoverListeners=new Set;this.lastTakeover=null;this.deletionCancelledListeners=new Set;this.lastDeletionCancelled=null;this.http=t,this.storage=r,this.analytics=n,this.flags=i,this.chatbot=o;let a=this.storage.get(N),l=this.storage.get(T),u=this.storage.get(g),b=this.storage.get(E);if(a&&(l||u))try{let f=JSON.parse(a);if(typeof f.id!="string"||f.id.length===0)throw new Error("invalid cache");this.user=f,l?(this.accessToken=l,this.accessExpiresAt=b?Number(b):0):(this.accessToken=null,this.accessExpiresAt=0),this.analytics.identify(this.user.id)}catch{this.storage.remove(N),this.storage.remove(T),this.storage.remove(E),this.storage.remove(g)}}getCurrentUser(){return this.user}onDeviceTakeover(e){return this.takeoverListeners.add(e),()=>{this.takeoverListeners.delete(e)}}getLastDeviceTakeover(){return this.lastTakeover}fireDeviceTakeover(e,t){if(!e||!Je.test(e))return;let r={retiredAnonUserId:e,identifiedUserId:t,at:new Date};this.lastTakeover=r;for(let n of this.takeoverListeners)try{n(r)}catch{}}onDeletionCancelled(e){return this.deletionCancelledListeners.add(e),()=>{this.deletionCancelledListeners.delete(e)}}getLastDeletionCancelled(){return this.lastDeletionCancelled}fireDeletionCancelled(e){let t={userId:e,at:new Date};this.lastDeletionCancelled=t;for(let r of this.deletionCancelledListeners)try{r(t)}catch{}}takeoverHint(){if(this.user?.isAnonymous)return this.storage.get(g)??void 0}async getAccessToken(){return this.accessToken&&this.accessExpiresAt&&Date.now()<this.accessExpiresAt-Qe?this.accessToken:this.storage.get(g)?this.refreshInflight?this.refreshInflight:Date.now()<this.refreshCooldownUntil?null:this.refreshAccessToken():null}getAccessTokenSync(){return this.accessToken}signInAnonymously(e){return this.serialize(async()=>{let t=await this.http.post("/auth-service/anonymous",{name:e?.name,metadata:e?.metadata});if(!t.success||!y(t.data))throw new c(t.error?.code??"AUTH_ERROR",t.error?.message??"Anonymous signup failed");return this.persist(t.data),t.data.user})}signUp(e,t,r){return this.serialize(async()=>{let n=this.storage.get(g);if(n){let o=await this.http.post("/auth-service/upgrade",{refreshToken:n,email:e,password:t,name:r?.name});if(o.success&&y(o.data))return this.persist(o.data),o.data.user;let a=o.error?.code??"",l=o.error?.status;if(a==="NOT_ANONYMOUS"||a==="FORBIDDEN_NON_ANONYMOUS")throw new q(o.error?.message);if(a==="CONFLICT"||a==="EMAIL_ALREADY_TAKEN"||l===409)throw new M(o.error?.message);if(a==="INVALID_REFRESH"||a==="UNAUTHORIZED"||a==="REFRESH_EXPIRED")await this.analytics.flush(),this.wipeLocalIdentity();else throw new c(a||"AUTH_ERROR",o.error?.message??"Upgrade failed")}let i=await this.http.post("/auth-service/signup",{email:e,password:t,name:r?.name,metadata:r?.metadata});if(!i.success||!y(i.data)){let o=i.error?.code??"";throw o==="CONFLICT"?new M(i.error?.message):new c(o||"AUTH_ERROR",i.error?.message??"Signup failed")}return this.persist(i.data),i.data.user})}signIn(e,t){return this.serialize(async()=>{let r=this.user!==null,n=this.takeoverHint();r&&(await this.analytics.flush(),this.wipeLocalIdentity());let i=await this.http.post("/auth-service/login",{email:e,password:t,...n?{prevAnonRefreshToken:n}:{}});if(!i.success||!i.data)throw new c(i.error?.code??"AUTH_ERROR",i.error?.message??"Login failed");let o=i.data;if(o.mfaRequired===!0&&typeof o.mfaChallengeToken=="string"){let a=o.user;return{mfaRequired:!0,mfaChallengeToken:o.mfaChallengeToken,userId:a?.id??""}}if(!y(i.data))throw new c(i.error?.code??"AUTH_ERROR",i.error?.message??"Login failed");return this.persist(i.data),i.data.user})}loginSocial(e){return this.serialize(async()=>{let t=this.user!==null,r=this.takeoverHint();t&&(await this.analytics.flush(),this.wipeLocalIdentity());let{link:n,...i}=e,o=await this.http.post("/auth-service/login/social",{...i,...r?{prevAnonRefreshToken:r}:{},...n?{linkAnonymous:!0}:{}});if(!o.success||!y(o.data))throw new c(o.error?.code??"SOCIAL_LOGIN_FAILED",o.error?.message??"Social login failed");return this.persist(o.data),o.data.user})}signInWithGoogle(e,t,r){return this.loginSocial({provider:"google",code:e,redirectUri:t,link:r})}signInWithGitHub(e,t){return this.loginSocial({provider:"github",code:e,redirectUri:t})}signInWithApple(e){return this.loginSocial({provider:"apple",...e})}signInWithMicrosoft(e,t){return this.loginSocial({provider:"microsoft",code:e,redirectUri:t})}signInWithLinkedIn(e,t){return this.loginSocial({provider:"linkedin",code:e,redirectUri:t})}signInWithFacebook(e,t){return this.loginSocial({provider:"facebook",code:e,redirectUri:t})}linkEmailPassword(e,t){return this.linkCredential("/auth-service/me/link/email",{email:e,password:t})}linkSocial(e){return this.linkCredential("/auth-service/me/link/social",e)}linkGoogle(e){return this.linkSocial({provider:"google",...e})}linkApple(e){return this.linkSocial({provider:"apple",...e})}async linkCredential(e,t){let r=await this.getAccessToken();if(!r)throw new c("NOT_SIGNED_IN","Sign in before linking a credential");let n=await this.http.post(e,t,{Authorization:`Bearer ${r}`});if(!n.success||!n.data?.user){let i=n.error?.code??"LINK_FAILED";throw i==="CREDENTIAL_IN_USE"?new B(n.error?.message):new c(i,n.error?.message??"Failed to link credential")}return this.updateLocalUser(n.data.user),n.data.user}updateLocalUser(e){this.user=e,this.storage.set(N,JSON.stringify(e))}async listLinkedIdentities(){let e=await this.getAccessToken();if(!e)throw new c("NOT_SIGNED_IN","Sign in before listing linked identities");let t=await this.http.get("/auth-service/me/identities",{Authorization:`Bearer ${e}`});if(!t.success||!t.data)throw new c(t.error?.code??"IDENTITIES_FAILED",t.error?.message??"Failed to list linked identities");return{identities:t.data.identities??[],hasPassword:!!t.data.hasPassword}}signInWithDiscord(e,t){return this.loginSocial({provider:"discord",code:e,redirectUri:t})}challengeMfa(e,t){return this.serialize(async()=>{let r=this.takeoverHint(),n=await this.http.post("/auth-service/mfa/challenge",{challengeToken:e,code:t,...r?{prevAnonRefreshToken:r}:{}});if(!n.success||!y(n.data))throw new c(n.error?.code??"MFA_CHALLENGE_FAILED",n.error?.message??"Invalid MFA code");return this.persist(n.data),n.data.user})}async sendMagicLink(e){let t=await this.http.post("/auth-service/magic-link/request",{email:e});if(!t.success)throw new c(t.error?.code??"MAGIC_LINK_REQUEST_FAILED",t.error?.message??"Failed to send link")}async sendEmailOtp(e){let t=await this.http.post("/auth-service/email-otp/request",{email:e});if(!t.success)throw new c(t.error?.code??"EMAIL_OTP_REQUEST_FAILED",t.error?.message??"Failed to send code")}verifyEmailOtp(e,t){return this.serialize(async()=>{let r=this.user!==null,n=this.takeoverHint();r&&(await this.analytics.flush(),this.wipeLocalIdentity());let i=await this.http.post("/auth-service/email-otp/verify",{email:e,code:t,...n?{prevAnonRefreshToken:n}:{}});if(!i.success||!y(i.data))throw new c(i.error?.code??"EMAIL_OTP_INVALID",i.error?.message??"Wrong or expired code");return this.persist(i.data),i.data.user})}async requestPasswordReset(e){let t=await this.http.post("/auth-service/password/forgot",{email:e});if(!t.success)throw new c(t.error?.code??"PASSWORD_RESET_REQUEST_FAILED",t.error?.message??"Failed to send reset email")}async resetPassword(e,t){let r=await this.http.post("/auth-service/password/reset",{token:e,newPassword:t});if(!r.success)throw new c(r.error?.code??"PASSWORD_RESET_FAILED",r.error?.message??"Reset failed")}async verifyEmail(e){let t=await this.http.post("/auth-service/email/verify",{token:e});if(!t.success)throw new c(t.error?.code??"EMAIL_VERIFICATION_FAILED",t.error?.message??"Verification failed")}async sendVerificationEmail(){let e=await this.getAccessToken();if(!e)throw new c("UNAUTHORIZED","Sign in before requesting a verification email");let t=await this.http.post("/auth-service/email/verify/resend",{},{Authorization:`Bearer ${e}`});if(!t.success)throw new c(t.error?.code??"VERIFICATION_RESEND_FAILED",t.error?.message??"Failed to send verification email")}verifyMagicLink(e){return this.serialize(async()=>{let t=this.user!==null,r=this.takeoverHint();t&&(await this.analytics.flush(),this.wipeLocalIdentity());let n=await this.http.post("/auth-service/magic-link/verify",{token:e,...r?{prevAnonRefreshToken:r}:{}});if(!n.success||!y(n.data))throw new c(n.error?.code??"MAGIC_LINK_INVALID",n.error?.message??"Invalid or expired magic link");return this.persist(n.data),n.data.user})}async enrollMfa(){let e=await this.http.post("/auth-service/mfa/enroll/start",{},await this.bearerHeaders());if(!e.success||!e.data)throw new c(e.error?.code??"MFA_ENROLL_FAILED",e.error?.message??"MFA enrollment failed");return e.data}async confirmMfa(e){let t=await this.http.post("/auth-service/mfa/enroll/confirm",{code:e},await this.bearerHeaders());return!!(t.success&&t.data?.confirmed)}async disableMfa(){await this.http.post("/auth-service/mfa/disable",{},await this.bearerHeaders())}async registerPasskey(e){if(typeof navigator>"u"||!navigator.credentials)throw new c("PASSKEY_UNSUPPORTED","WebAuthn not supported in this environment");let t=await this.http.post("/auth-service/passkeys/register/start",{},await this.bearerHeaders());if(!t.success||!t.data)throw new c(t.error?.code??"PASSKEY_START_FAILED",t.error?.message??"Passkey start failed");let r=et(t.data),n;try{let a=await navigator.credentials.create({publicKey:r});if(!a)throw new Error("create returned null");n=a}catch(a){throw new c("PASSKEY_USER_CANCELLED",a instanceof Error?a.message:"User cancelled passkey registration")}let i=rt(n),o=await this.http.post("/auth-service/passkeys/register/finish",{response:i,name:e},await this.bearerHeaders());if(!o.success||!o.data)throw new c(o.error?.code??"PASSKEY_FINISH_FAILED",o.error?.message??"Passkey verification failed");return o.data}signInWithPasskey(e){return this.serialize(async()=>{if(typeof navigator>"u"||!navigator.credentials)throw new c("PASSKEY_UNSUPPORTED","WebAuthn not supported in this environment");let t=this.user!==null,r=this.takeoverHint();t&&(await this.analytics.flush(),this.wipeLocalIdentity());let n=await this.http.post("/auth-service/passkeys/authenticate/start",{userId:e??null});if(!n.success||!n.data)throw new c(n.error?.code??"PASSKEY_START_FAILED",n.error?.message??"Passkey start failed");let i=tt(n.data),o;try{let u=await navigator.credentials.get({publicKey:i});if(!u)throw new Error("get returned null");o=u}catch(u){throw new c("PASSKEY_USER_CANCELLED",u instanceof Error?u.message:"User cancelled passkey signin")}let a=nt(o),l=await this.http.post("/auth-service/passkeys/authenticate/finish",{response:a,userId:e??null,...r?{prevAnonRefreshToken:r}:{}});if(!l.success||!y(l.data))throw new c(l.error?.code??"PASSKEY_AUTH_FAILED",l.error?.message??"Passkey signin failed");return this.persist(l.data),l.data.user})}async listPasskeys(){let e=await this.http.get("/auth-service/passkeys",await this.bearerHeaders());return!e.success||!Array.isArray(e.data)?[]:e.data}async deletePasskey(e){await this.http.delete(`/auth-service/passkeys/${e}`,await this.bearerHeaders())}async listMySessions(){let e=await this.http.get("/auth-service/sessions/me",await this.bearerHeaders());return!e.success||!Array.isArray(e.data)?[]:e.data}async revokeSession(e){await this.http.delete(`/auth-service/sessions/me/${e}`,await this.bearerHeaders())}async revokeAllSessions(){await this.http.delete("/auth-service/sessions/me",await this.bearerHeaders())}async startSso(e){let t=this.takeoverHint(),r=await this.http.post("/auth-service/sso/oidc/start",{returnTo:e,...t?{prevAnonRefreshToken:t}:{}});if(!r.success||!r.data)throw new c(r.error?.code??"SSO_START_FAILED",r.error?.message??"SSO start failed");return r.data}async signInWithSso(e){let t=e??(typeof window<"u"?window.location.origin:"");if(!t)throw new c("SSO_NO_RETURN_TO","returnTo required outside browser context");let r=await this.startSso(t);typeof window<"u"&&(window.location.href=r.authorizationUrl)}async startSaml(e){let t=this.takeoverHint(),r=await this.http.post("/auth-service/sso/saml/start",{returnTo:e,...t?{prevAnonRefreshToken:t}:{}});if(!r.success||!r.data)throw new c(r.error?.code??"SAML_START_FAILED",r.error?.message??"SAML start failed");return r.data}async signInWithSaml(e){let t=e??(typeof window<"u"?window.location.origin:"");if(!t)throw new c("SAML_NO_RETURN_TO","returnTo required outside browser context");let r=await this.startSaml(t);typeof window<"u"&&(window.location.href=r.authorizationUrl)}consumeSsoTokenFromUrl(){return this.serialize(async()=>{if(typeof window>"u")return null;let e=window.location.hash.replace(/^#/,"");if(!e)return null;let t=new URLSearchParams(e),r=t.get("sendora_oidc_error")??t.get("sendora_saml_error");if(r)throw window.history.replaceState(null,"",window.location.pathname+window.location.search),new c("SSO_FAILED",decodeURIComponent(r));let n=t.get("sendora_oidc_token")??t.get("sendora_saml_token");if(!n)return null;let i=t.get("sendora_retired_anon");window.history.replaceState(null,"",window.location.pathname+window.location.search),this.user!==null&&(await this.analytics.flush(),this.wipeLocalIdentity());let o=await this.http.post("/auth-service/token/refresh",{refreshToken:n});if(!o.success||!o.data)throw new c(o.error?.code??"SSO_EXCHANGE_FAILED",o.error?.message??"Could not exchange SSO token");let a=o.data;this.accessToken=a.accessToken,this.accessExpiresAt=Date.now()+a.expiresIn*1e3,this.storage.set(T,a.accessToken),this.storage.set(E,String(this.accessExpiresAt)),this.storage.set(g,a.refreshToken);let l=st(a.accessToken),u={id:l?.sub??"",email:l?.email??null,emailVerified:l?.email_verified??!0,name:l?.name??null,isAnonymous:!1};return this.user=u,this.storage.set(N,JSON.stringify(u)),this.analytics.identify(u.id),this.flags.setContext({userId:u.id}),i&&this.fireDeviceTakeover(i,u.id),u})}async bearerHeaders(){let e=await this.getAccessToken();if(!e)throw new c("NOT_SIGNED_IN","Not signed in");return{Authorization:`Bearer ${e}`}}signOut(){return this.serialize(async()=>{let e=this.storage.get(g);this.wipeLocalIdentity(),e&&this.http.post("/auth-service/token/revoke",{refreshToken:e}).catch(()=>{})})}serialize(e){let t=this.inflight.then(e,e);return this.inflight=t.catch(()=>{}),t}async refreshAccessToken(){if(this.refreshInflight)return this.refreshInflight;let e=this.storage.get(g);if(!e)return null;let t=typeof navigator<"u"&&"locks"in navigator,r=async()=>{let n=this.storage.get(g);if(n&&n!==e){let i=this.storage.get(T),o=this.storage.get(E);if(i&&o){let a=Number(o);if(Number.isFinite(a)&&a>Date.now())return this.accessToken=i,this.accessExpiresAt=a,i}}try{let i=await this.http.post("/auth-service/token/refresh",{refreshToken:n??e});if(!i.success)return Ze(i.error?.code)?(this.wipeLocalIdentity(),this.refreshFailureCount=0,this.refreshCooldownUntil=0,null):(this.armBackoff(),null);if(!i.data||typeof i.data!="object"||typeof i.data.accessToken!="string"||typeof i.data.refreshToken!="string"||typeof i.data.expiresIn!="number")return this.armBackoff(),null;let o=i.data;return this.accessToken=o.accessToken,this.accessExpiresAt=Date.now()+o.expiresIn*1e3,this.storage.set(T,o.accessToken),this.storage.set(E,String(this.accessExpiresAt)),this.storage.set(g,o.refreshToken),this.refreshFailureCount=0,this.refreshCooldownUntil=0,o.accessToken}catch{return this.armBackoff(),null}};return this.refreshInflight=(async()=>{try{return t?await navigator.locks.request("sendora-authservice-refresh",{mode:"exclusive"},r):await r()}finally{this.refreshInflight=null}})(),this.refreshInflight}armBackoff(){this.refreshFailureCount+=1;let e=Math.min(3e4,1e3*Math.pow(2,this.refreshFailureCount-1));this.refreshCooldownUntil=Date.now()+e}resetRefreshState(){this.refreshFailureCount=0,this.refreshCooldownUntil=0}startVisibilityRefreshReset(){if(this.visibilityResetUnsub||typeof document>"u")return;let e=()=>{document.visibilityState==="visible"&&this.resetRefreshState()};document.addEventListener("visibilitychange",e),this.visibilityResetUnsub=()=>document.removeEventListener("visibilitychange",e)}stopVisibilityRefreshReset(){this.visibilityResetUnsub&&(this.visibilityResetUnsub(),this.visibilityResetUnsub=null)}startProactiveRefreshCron(){if(typeof window>"u"||this.proactiveRefreshTimer)return;let e=.8,t=6e4,r=3e4,n=async()=>{if(!this.accessToken||!this.accessExpiresAt)return;let o=Date.now(),a=this.accessExpiresAt-o;if(a<=0)return;let u=Math.max(a,5*6e4)*(1-e)+(Math.random()*r*2-r);a<=u&&await this.refreshAccessToken()};this.proactiveRefreshTimer=window.setInterval(()=>{n()},t);let i=()=>{document.visibilityState==="visible"&&n()};document.addEventListener("visibilitychange",i),this.proactiveRefreshCleanup=()=>{this.proactiveRefreshTimer&&(window.clearInterval(this.proactiveRefreshTimer),this.proactiveRefreshTimer=null),document.removeEventListener("visibilitychange",i)}}stopProactiveRefreshCron(){this.proactiveRefreshCleanup&&(this.proactiveRefreshCleanup(),this.proactiveRefreshCleanup=null)}persist(e){this.user=e.user,this.accessToken=e.tokens.accessToken,this.accessExpiresAt=Date.now()+e.tokens.expiresIn*1e3,this.storage.set(N,JSON.stringify(e.user)),this.storage.set(T,e.tokens.accessToken),this.storage.set(E,String(this.accessExpiresAt)),this.storage.set(g,e.tokens.refreshToken),this.analytics.identify(e.user.id),this.flags.setContext({userId:e.user.id}),this.startProactiveRefreshCron(),this.startVisibilityRefreshReset(),e.retiredAnonUserId&&this.fireDeviceTakeover(e.retiredAnonUserId,e.user.id),e.reactivatedFromDeletion&&this.fireDeletionCancelled(e.user.id)}wipeLocalIdentity(){this.user=null,this.accessToken=null,this.accessExpiresAt=0,this.storage.remove(N),this.storage.remove(T),this.storage.remove(E),this.storage.remove(g),this.analytics.reset(),this.analytics.dropQueue(),this.flags.reset(),this.chatbot.newSession(),this.stopProactiveRefreshCron(),this.stopVisibilityRefreshReset(),this.refreshFailureCount=0,this.refreshCooldownUntil=0}};function Ze(s){return s?s==="INVALID_REFRESH_TOKEN"||s==="UNAUTHORIZED"||s==="HTTP_401"||s==="RATE_LIMIT_EXCEEDED"||s==="RATE_LIMIT":!1}function W(s){let e=(4-s.length%4)%4,t=(s+"===".slice(0,e)).replace(/-/g,"+").replace(/_/g,"/"),r=atob(t),n=new ArrayBuffer(r.length),i=new Uint8Array(n);for(let o=0;o<r.length;o++)i[o]=r.charCodeAt(o);return n}function I(s){let e=s instanceof Uint8Array?s:new Uint8Array(s),t="";for(let r=0;r<e.length;r++)t+=String.fromCharCode(e[r]);return btoa(t).replace(/\+/g,"-").replace(/\//g,"_").replace(/=+$/,"")}function et(s){return{challenge:W(s.challenge),rp:s.rp,user:{id:W(s.user.id),name:s.user.name,displayName:s.user.displayName},pubKeyCredParams:s.pubKeyCredParams,timeout:s.timeout,attestation:s.attestation,excludeCredentials:s.excludeCredentials?.map(e=>({id:W(e.id),type:"public-key",transports:e.transports})),authenticatorSelection:s.authenticatorSelection}}function tt(s){return{challenge:W(s.challenge),timeout:s.timeout,rpId:s.rpId,allowCredentials:s.allowCredentials?.map(e=>({id:W(e.id),type:"public-key",transports:e.transports})),userVerification:s.userVerification}}function rt(s){let e=s.response;return{id:s.id,rawId:I(s.rawId),type:s.type,response:{clientDataJSON:I(e.clientDataJSON),attestationObject:I(e.attestationObject),transports:typeof e.getTransports=="function"?e.getTransports():void 0},clientExtensionResults:s.getClientExtensionResults()}}function st(s){try{let e=s.split(".");if(e.length!==3)return null;let t=e[1].replace(/-/g,"+").replace(/_/g,"/"),r=atob(t+"===".slice(0,(4-t.length%4)%4));return JSON.parse(r)}catch{return null}}function nt(s){let e=s.response;return{id:s.id,rawId:I(s.rawId),type:s.type,response:{clientDataJSON:I(e.clientDataJSON),authenticatorData:I(e.authenticatorData),signature:I(e.signature),userHandle:e.userHandle?I(e.userHandle):null},clientExtensionResults:s.getClientExtensionResults()}}function it(s){return s===!1?{pageViews:!1,sessionStart:!1,appOpen:!1,appBackground:!1,engagement:!1}:s===!0||s===void 0?{pageViews:!0,sessionStart:!0,appOpen:!0,appBackground:!0,engagement:!0}:{pageViews:s.pageViews??!0,sessionStart:s.sessionStart??!0,appOpen:s.appOpen??!0,appBackground:s.appBackground??!0,engagement:s.engagement??!0}}var ot=250,Se=360*60*1e3;function Re(s,e){if(typeof window>"u")return()=>{};let t=it(e.autoTrack),r=e.sessionIdleMs??18e5,n=[],i="sendora_session_id",o="sendora_session_last_activity";function a(){return Te(i)??""}function l(d){Ee(i,d)}function u(){let d=Te(o);return d&&Number(d)||0}function b(d){Ee(o,String(d))}function f(){return`sess_${Date.now().toString(36)}_${Math.random().toString(36).slice(2,10)}`}function m(d){let k=Date.now(),$=u();if(!a()||$&&k-$>r){let j=f();l(j),d&&t.sessionStart&&s.track("session.start",{sessionId:j})}b(k)}m(t.sessionStart),t.appOpen&&s.track("app.opened",{sessionId:a()||void 0,url:window.location.origin+window.location.pathname,referrer:document.referrer||void 0});let w=window.location.pathname,R=0,v=document.visibilityState==="visible"?Date.now():0;function se(){v>0&&(R+=Date.now()-v,v=0)}function z(){if(!t.engagement)return;se();let d=R;if(R=0,!(d<ot)){d>Se&&(d=Se);try{s.track("app.engagement",{durationMs:Math.round(d),screen:w,sessionId:a()||void 0})}catch{}}}function _e(d){w=d,R=0,v=document.visibilityState==="visible"?Date.now():0}if(t.pageViews||t.engagement){t.pageViews&&s.page();let d=()=>{z(),m(!0),t.pageViews&&s.page(),_e(window.location.pathname)},k=()=>d();window.addEventListener("popstate",k),n.push(()=>window.removeEventListener("popstate",k));let $=window;if(!$.__sendoraHistoryPatched){$.__sendoraHistoryPatched=!0;let j=history.pushState,Pe=history.replaceState;history.pushState=function(...ie){let oe=j.apply(this,ie);return window.dispatchEvent(new Event("sendora:locationchange")),oe},history.replaceState=function(...ie){let oe=Pe.apply(this,ie);return window.dispatchEvent(new Event("sendora:locationchange")),oe}}let ne=()=>d();window.addEventListener("sendora:locationchange",ne),n.push(()=>window.removeEventListener("sendora:locationchange",ne))}if(t.appBackground||t.engagement){let d=()=>{if(document.visibilityState==="hidden"){z(),t.appBackground&&(m(!0),s.track("app.backgrounded",{sessionId:a()||void 0}));try{s.flush()}catch{}}else document.visibilityState==="visible"&&(t.appBackground&&(m(!0),s.track("app.foregrounded",{sessionId:a()||void 0})),t.engagement&&v===0&&(v=Date.now()))};if(document.addEventListener("visibilitychange",d),n.push(()=>document.removeEventListener("visibilitychange",d)),t.engagement){let k=()=>{z();try{s.flush()}catch{}};window.addEventListener("pagehide",k),n.push(()=>window.removeEventListener("pagehide",k))}}let pe=0,C=()=>{let d=Date.now();d-pe<5e3||(pe=d,m(!0))};return window.addEventListener("click",C,{passive:!0}),window.addEventListener("keydown",C,{passive:!0}),window.addEventListener("scroll",C,{passive:!0}),n.push(()=>{window.removeEventListener("click",C),window.removeEventListener("keydown",C),window.removeEventListener("scroll",C)}),()=>{z();for(let d of n)try{d()}catch{}}}function Te(s){try{return window.sessionStorage?.getItem(s)??null}catch{return null}}function Ee(s,e){try{window.sessionStorage?.setItem(s,e)}catch{}}var F=class s{constructor(e){this.http=e}static isSupported(){return typeof window>"u"?!1:"serviceWorker"in navigator&&"PushManager"in window&&"Notification"in window}static permission(){return typeof Notification>"u"?"denied":Notification.permission}async subscribe(e){if(!s.isSupported())throw new Error("[sendoracloud] Web Push not supported in this browser.");let t=Notification.permission;if(t==="default"&&(t=await Notification.requestPermission()),t!=="granted")throw new Error(`[sendoracloud] Notification permission ${t}.`);let r=await navigator.serviceWorker.getRegistration(e.scope??"/");r||(r=await navigator.serviceWorker.register(e.serviceWorkerPath,{scope:e.scope??"/"})),await navigator.serviceWorker.ready;let i=(await this.http.get("/push/vapid"))?.data?.publicKey;if(!i)throw new Error("[sendoracloud] backend returned no VAPID public key.");let a=await r.pushManager.getSubscription()??await r.pushManager.subscribe({userVisibleOnly:!0,applicationServerKey:at(i)}),l=a.toJSON(),u=l.endpoint,b=l.keys?.p256dh??"",f=l.keys?.auth??"",m=await ct(u),w=await this.http.post("/push/tokens",{platform:"web",token:m,webEndpoint:u,webP256dh:b,webAuth:f,userAgent:typeof navigator<"u"?navigator.userAgent:void 0,locale:typeof navigator<"u"?navigator.language:void 0,timezone:Intl?.DateTimeFormat?.().resolvedOptions?.().timeZone,...e.userId?{userId:e.userId}:{}});if(!w?.data?.tokenId)throw new Error("[sendoracloud] backend returned no tokenId on subscribe.");return{tokenId:w.data.tokenId,subscription:a}}async unsubscribe(e="/"){if(!s.isSupported())return!1;let t=await navigator.serviceWorker.getRegistration(e);if(!t)return!1;let r=await t.pushManager.getSubscription();return r?await r.unsubscribe():!1}async trackOpen(e,t){let r={sendId:e};t!==void 0&&(r.clickAction=t);let n=await this.http.post("/push/track-open",r);if(!n.success)throw new Error(n.error?.message??"push.trackOpen failed")}};function at(s){let e="=".repeat((4-s.length%4)%4),t=(s+e).replace(/-/g,"+").replace(/_/g,"/"),r=atob(t),n=new Uint8Array(r.length);for(let i=0;i<r.length;i++)n[i]=r.charCodeAt(i);return n}async function ct(s){let e=new TextEncoder().encode(s),t=await crypto.subtle.digest("SHA-256",e);return Array.from(new Uint8Array(t)).map(n=>n.toString(16).padStart(2,"0")).join("")}var K=class{constructor(e){}unsupported(){throw new Error("[SendoraCloud] sendora.audiences is not available in the browser SDK. Audiences require a secret (sk_*) key with the audiences:read/write scopes and must be managed from a trusted server, not the browser.")}list(){return this.unsupported()}get(e){return this.unsupported()}create(e){return this.unsupported()}update(e,t){return this.unsupported()}delete(e){return this.unsupported()}};var h=class extends Error{constructor(e,t,r){super(t),this.name="LinkError",this.code=e,this.statusCode=r}};function de(s,e){let t=(s??"").toUpperCase();return t==="NOT_FOUND"?new h("NOT_FOUND",e):t==="FORBIDDEN"||t==="UNAUTHORIZED"?new h("UNAUTHORIZED",e):t==="RATE_LIMITED"?new h("RATE_LIMITED",e):t==="ENTITLEMENT_ERROR"||/plan limit/i.test(e)?new h("PLAN_LIMIT",e):t==="VALIDATION_ERROR"?/iOS bundle|Android package/i.test(e)?new h("BUNDLE_MISMATCH",e):/2KB|10KB|linkData/i.test(e)?new h("DATA_TOO_LARGE",e):/fallbackUrl/i.test(e)&&/apps/i.test(e)?new h("FALLBACK_REQUIRED",e):new h("INVALID_INPUT",e):t==="PARSE_ERROR"||t==="HTTP_ERROR"||/HTTP \d{3}/.test(e)?new h("NETWORK",e):new h("UNKNOWN",e||`Unknown LinkError (code=${s??"none"})`)}var he=/^[a-z0-9-]{3,20}$/;function Ce(s){try{let t=new URL(s).pathname.split("/").filter(Boolean);if(t.length===0)return null;let r=t[t.length-1];return he.test(r)?r:null}catch{return null}}var lt=5*6e4,ut=50,dt=5,V=class{constructor(e){this.http=e;this.prewarmCache=new Map;this.prewarmInflight=0}cacheKey(e,t){if(t)return`k:${t}`;let r={},n=e;for(let i of Object.keys(n).sort())r[i]=n[i];return`s:${JSON.stringify(r)}`}evictExpired(){let e=Date.now();for(let[t,r]of this.prewarmCache)e-r.ts>lt&&this.prewarmCache.delete(t);for(;this.prewarmCache.size>ut;){let t=this.prewarmCache.keys().next().value;if(t===void 0)break;this.prewarmCache.delete(t)}}prewarm(e,t){this.evictExpired();let r=this.cacheKey(e,t?.key);if(this.prewarmCache.has(r)||this.prewarmInflight>=dt)return;this.prewarmInflight++;let n=this.doCreate(e).catch(i=>{throw this.prewarmCache.delete(r),i}).finally(()=>{this.prewarmInflight--});this.prewarmCache.set(r,{promise:n,ts:Date.now()})}async create(e,t){if(!e.title)throw new h("INVALID_INPUT","links.create: title is required");this.evictExpired();let r=this.cacheKey(e,t?.key),n=this.prewarmCache.get(r);return n?(this.prewarmCache.delete(r),n.promise):this.doCreate(e)}async doCreate(e){let t={title:e.title};e.fallbackUrl&&(t.fallbackUrl=e.fallbackUrl),e.iosDeepLinkPath&&(t.iosDeepLinkPath=e.iosDeepLinkPath),e.androidDeepLinkPath&&(t.androidDeepLinkPath=e.androidDeepLinkPath),e.linkData&&(t.linkData=e.linkData),e.ogTitle&&(t.ogTitle=e.ogTitle),e.ogDescription&&(t.ogDescription=e.ogDescription),e.ogImageUrl&&(t.ogImageUrl=e.ogImageUrl),e.campaign&&(t.campaign=e.campaign),e.source&&(t.source=e.source),e.medium&&(t.medium=e.medium),e.channel&&(t.channel=e.channel),e.tags&&(t.tags=e.tags),e.expiresAt&&(t.expiresAt=e.expiresAt),e.iosBundleId&&(t.iosBundleId=e.iosBundleId),e.androidPackageName&&(t.androidPackageName=e.androidPackageName);let r=await this.http.post("/sdk/links",t);if(!r.success||!r.data)throw de(r.error?.code,r.error?.message??"links.create failed");return r.data}async revoke(e){if(!he.test(e))throw new h("INVALID_INPUT",`links.revoke: '${e}' is not a valid shortcode`);let t=await this.http.post(`/sdk/links/${encodeURIComponent(e)}/revoke`,{});if(!t.success||!t.data)throw de(t.error?.code,t.error?.message??"links.revoke failed");return{shortcode:t.data.shortcode,revoked:!0}}async getStats(e){if(!he.test(e))throw new h("INVALID_INPUT",`links.getStats: '${e}' is not a valid shortcode`);let t=await this.http.get(`/sdk/links/${encodeURIComponent(e)}/stats`);if(!t.success||!t.data)throw de(t.error?.code,t.error?.message??"links.getStats failed");return t.data}};var J=class{constructor(e){this.http=e}async reportInstall(e={}){let t=await this.http.post("/attribution/install",e);if(!t.success||!t.data)throw new Error(t.error?.message??"attribution.reportInstall failed");return t.data}async checkDeferred(e={}){let t=await this.http.post("/attribution/deferred",e);if(!t.success)throw new Error(t.error?.message??"attribution.checkDeferred failed");return t.data??null}};var Z=class{constructor(e){this.http=e}async createTicket(e){let t=await this.http.post("/support/tickets",e);if(!t.success||!t.data)throw new Error(t.error?.message??"support.createTicket failed");return t.data}async submitCsat(e){let t=await this.http.post("/support/csat",e);if(!t.success||!t.data)throw new Error(t.error?.message??"support.submitCsat failed");return t.data}};var ee=class{constructor(e){this.http=e}async search(e){if(!e.query||e.query.trim().length===0)return[];let t=new URLSearchParams({query:e.query});e.limit&&t.set("limit",String(e.limit));let r=await this.http.get(`/kb/search?${t.toString()}`);if(!r.success||!Array.isArray(r.data))throw new Error(r.error?.message??"kb.search failed");return r.data}};var re=class re{constructor(e){this.autoTrackCleanup=null;this.http=new G(e),this.storage=new X,this.consent=new O(e.defaultConsent===!0,this.http),this.analytics=new P(e,this.http,this.storage,this.consent),this.flags=new D(e,this.http,this.storage),this.surveys=new L(e,this.http,this.storage),this.messages=new U(e,this.http,this.storage),this.chatbot=new x(e,this.http,this.storage),this.auth=new H(e,this.http,this.storage,this.analytics,this.flags,this.chatbot),this.webPush=new F(this.http),this.audiences=new K(this.http),this.links=new V(this.http),this.attribution=new J(this.http),this.support=new Z(this.http),this.kb=new ee(this.http),e.autoTrack!==!1&&(this.autoTrackCleanup=Re(this.analytics,e))}static init(e){return ve(e.apiKey),new re(e)}static sanitizeHtml(e){return Q(e)}static safeUrl(e){return A(e)}track(e,t){this.analytics.track(e,t)}page(e){this.analytics.page(e)}identify(e,t,r){this.analytics.identify(e,t,r),this.flags.setContext({userId:e})}reset(){this.analytics.reset(),this.flags.reset(),this.chatbot.newSession()}async flush(){await this.analytics.flush()}destroy(){this.autoTrackCleanup&&(this.autoTrackCleanup(),this.autoTrackCleanup=null),this.analytics.destroy(),this.flags.destroy(),this.messages.destroy()}};re.version=S;var te=re,ht=te;0&&(module.exports={AlreadyIdentifiedError,Analytics,Audiences,Auth,AuthError,Chatbot,Consent,CredentialInUseError,EmailAlreadyTakenError,FeatureFlags,InAppMessaging,LinkError,Links,SendoraCloud,Surveys,ValidationError,WebPush,extractShortcodeFromUrl,safeUrl,sanitizeHtml});
|
|
1
|
+
"use strict";var ce=Object.defineProperty;var Ne=Object.getOwnPropertyDescriptor;var xe=Object.getOwnPropertyNames;var Oe=Object.prototype.hasOwnProperty;var Me=(s,e)=>{for(var t in e)ce(s,t,{get:e[t],enumerable:!0})},He=(s,e,t,r)=>{if(e&&typeof e=="object"||typeof e=="function")for(let n of xe(e))!Oe.call(s,n)&&n!==t&&ce(s,n,{get:()=>e[n],enumerable:!(r=Ne(e,n))||r.enumerable});return s};var Fe=s=>He(ce({},"__esModule",{value:!0}),s);var kt={};Me(kt,{AlreadyIdentifiedError:()=>K,Analytics:()=>N,Audiences:()=>$,Auth:()=>V,AuthError:()=>u,Chatbot:()=>H,Consent:()=>F,CredentialInUseError:()=>P,EmailAlreadyTakenError:()=>C,FeatureFlags:()=>x,InAppMessaging:()=>M,LinkError:()=>h,Links:()=>B,SendoraCloud:()=>re,Surveys:()=>O,ValidationError:()=>f,WebPush:()=>W,default:()=>wt,extractShortcodeFromUrl:()=>Le,safeUrl:()=>S,sanitizeHtml:()=>J});module.exports=Fe(kt);var fe=/^[a-zA-Z][a-zA-Z0-9._:-]{0,127}$/;var Ke=new Set(["__proto__","constructor","prototype"]),f=class extends Error{constructor(e){super(`[SendoraCloud] ${e}`),this.name="SendoraValidationError"}};function me(s){if(typeof s!="string"||!fe.test(s))throw new f(`Invalid event name: must match ${fe}, got "${String(s)}".`)}function ye(s){if(s==null)return;if(typeof s!="object"||Array.isArray(s))throw new f("Properties must be a plain object, not an array or primitive.");let e=ve(s);if(e>5)throw new f(`Properties nested too deeply (${e} levels, max 5).`);we(s);let t;try{t=JSON.stringify(s).length}catch{throw new f("Properties contain a circular reference or unserializable value.")}if(t>32768)throw new f(`Properties too large (${t} bytes, max 32768).`)}function ve(s,e=0){if(e>6||s==null||typeof s!="object")return e;let t=e;for(let r of Object.keys(s)){let n=ve(s[r],e+1);n>t&&(t=n)}return t}function we(s){if(!(s==null||typeof s!="object"))for(let e of Object.keys(s)){if(Ke.has(e))throw new f(`Forbidden property key "${e}" (prototype pollution guard).`);we(s[e])}}var Ve=/^pk_[A-Za-z0-9_-]{16,}$/;function ke(s){if(!s||typeof s!="string")throw new f("apiKey is required.");if(s.startsWith("sk_")||s.startsWith("sendora_secret_"))throw new f("Secret keys must NEVER be used in browser SDK. Use a publishable key (pk_...).");if(!Ve.test(s))throw new f("Invalid publishable key format. Expected pk_... (17+ chars).")}function Ae(s){let e=s.replace(/\/$/,"");if(!(e.startsWith("http://localhost")||e.startsWith("http://127.0.0.1"))&&!e.startsWith("https://"))throw new f("apiUrl must be https:// (only http://localhost is allowed for dev).")}var Ie={name:"@sendoracloud/sdk-web",version:"3.14.0",description:"Sendora Cloud Web SDK \u2014 analytics, feature flags, surveys, in-app messaging, chatbot, auth, deep links (Branch / Firebase parity). Zero dependencies, TypeScript, SSR-safe.",type:"module",main:"./dist/index.cjs",module:"./dist/index.js",types:"./dist/index.d.ts",exports:{".":{types:"./dist/index.d.ts",import:"./dist/index.js",require:"./dist/index.cjs"}},files:["dist","README.md","LICENSE"],keywords:["sendora","analytics","feature-flags","surveys","in-app-messaging","chatbot","tracking","customer-engagement"],homepage:"https://sendoracloud.com/sdks",repository:{type:"git",url:"git+https://github.com/sendoracloud/sdk-web.git"},bugs:{url:"https://github.com/sendoracloud/sdk-web/issues"},author:"Sendora Cloud <hello@sendoracloud.com>",license:"Apache-2.0",publishConfig:{access:"public"},sideEffects:!1,scripts:{build:"tsup src/index.ts --format cjs,esm --dts --clean --minify",typecheck:"tsc --noEmit",prepublishOnly:"npm run build"},devDependencies:{typescript:"^5.4.0",tsup:"^8.0.0"}};var E=Ie.version;var $e="https://api.sendoracloud.com",Be=1e4,qe="@sendoracloud/sdk-web",Y=class{constructor(e){let t=(e.apiUrl??$e).replace(/\/$/,"");Ae(t),this.apiKey=e.apiKey,this.baseUrl=t,this.debug=!!e.debug}get(e,t){return this.request("GET",e,void 0,t)}post(e,t,r){return this.request("POST",e,t,r)}patch(e,t,r){return this.request("PATCH",e,t,r)}delete(e,t){return this.request("DELETE",e,void 0,t)}async request(e,t,r,n){let i=`${this.baseUrl}/api/v1${t}`,o=typeof AbortController<"u"?new AbortController:null,a=o?setTimeout(()=>o.abort(),Be):null;try{let c=await fetch(i,{method:e,headers:{"Content-Type":"application/json","X-API-Key":this.apiKey,"X-Sendora-SDK-Name":qe,"X-Sendora-SDK-Version":E,...n??{}},body:r?JSON.stringify(r):void 0,signal:o?o.signal:void 0});a&&clearTimeout(a);let l;try{l=await c.json()}catch{l={success:!1,error:{code:"PARSE_ERROR",message:`HTTP ${c.status}`}}}if(this.debug&&console.debug(`[SendoraCloud] ${e} ${t} \u2192 ${c.status}`),!c.ok){if(t.startsWith("/auth-service/")&&!this.debug)console.warn(`[SendoraCloud] ${e} ${t} \u2192 HTTP ${c.status}`);else{let v=l?.error?.code??"HTTP_ERROR",w=l?.error?.message??`HTTP ${c.status}`,k=l?.error?.details?.fieldErrors,L=k?` (${Object.entries(k).map(([A,ne])=>`${A}: ${(ne??[]).join(", ")}`).join("; ")})`:"";console.warn(`[SendoraCloud] ${e} ${t} \u2192 HTTP ${c.status} \u2014 ${v}: ${w}${L}`)}l&&l.error&&(l.error={...l.error,status:c.status})}return l}catch(c){a&&clearTimeout(a);let l=c instanceof Error&&c.name==="AbortError"?"timeout":"error";return console.warn(`[SendoraCloud] ${e} ${t} network ${l}`),{success:!1,error:{code:"NETWORK_ERROR",message:l}}}}};var X="sendora_",Q=class{constructor(){this.memoryStore=new Map;this.useLocalStorage=this.checkLocalStorage()}get(e){let t=X+e;if(this.useLocalStorage)try{return localStorage.getItem(t)}catch{return this.memoryStore.get(t)??null}return this.memoryStore.get(t)??null}set(e,t){let r=X+e;if(this.useLocalStorage)try{localStorage.setItem(r,t);return}catch{}this.memoryStore.set(r,t)}remove(e){let t=X+e;if(this.useLocalStorage)try{localStorage.removeItem(t)}catch{}this.memoryStore.delete(t)}clearPrefix(e){let t=X+e;if(this.useLocalStorage)try{for(let r=localStorage.length-1;r>=0;r--){let n=localStorage.key(r);n&&n.startsWith(t)&&localStorage.removeItem(n)}}catch{}for(let r of Array.from(this.memoryStore.keys()))r.startsWith(t)&&this.memoryStore.delete(r)}checkLocalStorage(){try{if(typeof localStorage>"u")return!1;let e="__sendora_test__";return localStorage.setItem(e,"1"),localStorage.removeItem(e),!0}catch{return!1}}};function U(s=""){let e=new Uint8Array(16),t=typeof globalThis<"u"&&"crypto"in globalThis?globalThis.crypto:void 0;if(!t||typeof t.getRandomValues!="function")throw new Error("[SendoraCloud] crypto.getRandomValues unavailable \u2014 refusing to mint IDs without a CSPRNG");t.getRandomValues(e);let r="";for(let n of e)r+=n.toString(16).padStart(2,"0");return s+r}var Se=500,ue=10,le=6e4,N=class{constructor(e,t,r,n){this.queue=[];this.timer=null;this.userId=null;this.identityToken=null;this.consecutiveFailures=0;this.nextFlushAfter=0;this.breakerOpenAt=0;this.breakerWarned=!1;this.http=t,this.storage=r,this.consent=n,this.projectId=e.projectId,this.flushInterval=e.flushInterval??5e3,this.maxQueueSize=e.maxQueueSize??20,this.persistUserId=!!e.persistUserId,this.stripQueryParams=e.stripQueryParams!==!1,this.appVersion=e.appVersion,this.storage.get("schema_version")===null&&this.storage.set("schema_version","1");let i=this.storage.get("anonymous_id");if(i?this.anonymousId=i:(this.anonymousId=U("anon_"),this.storage.set("anonymous_id",this.anonymousId)),this.persistUserId){let o=this.storage.get("user_id");o&&(this.userId=o)}this.timer=setInterval(()=>this.flush(),this.flushInterval),typeof window<"u"&&window.addEventListener("beforeunload",()=>this.flush()),this.consent.subscribe(o=>{o&&this.flush()})}track(e,t){me(e),t&&ye(t),this.queue.length>=Se&&this.queue.shift(),this.queue.push({eventType:e,properties:t??{},userId:this.userId??void 0,anonymousId:this.anonymousId,timestamp:new Date().toISOString(),identityToken:this.identityToken??void 0}),this.queue.length>=this.maxQueueSize&&this.flush()}page(e){if(typeof window>"u")return;let t=this.stripQueryParams?window.location.origin+window.location.pathname:window.location.href;this.track("page.viewed",{url:t,path:window.location.pathname,title:document.title,referrer:document.referrer||void 0,...e})}identify(e,t,r){if(typeof e!="string"||e.length===0||e.length>256)throw new Error("[SendoraCloud] userId must be a 1-256 char string.");this.userId=e,this.identityToken=r?.identityToken??null,this.persistUserId&&this.storage.set("user_id",e),this.track("user.identified",{...t})}reset(){this.userId=null,this.identityToken=null,this.storage.remove("user_id"),this.anonymousId=U("anon_"),this.storage.set("anonymous_id",this.anonymousId),this.storage.clearPrefix("iam_shown_"),this.storage.clearPrefix("survey_shown_")}dropQueue(){this.queue=[]}async flush(){if(this.queue.length===0||!this.consent.isGranted()||Date.now()<this.nextFlushAfter)return;this.consecutiveFailures>ue&&Date.now()-this.breakerOpenAt>=le&&(this.consecutiveFailures=0,this.breakerOpenAt=0,this.breakerWarned=!1);let e=this.queue.splice(0,100),t=this.getSessionId(),r=e.map(a=>({projectId:this.projectId,module:"custom",eventType:a.eventType,userId:a.userId,anonymousId:a.anonymousId,identityToken:a.identityToken,timestamp:a.timestamp??new Date().toISOString(),properties:a.properties??{},context:this.getContext(),...t?{sessionId:t}:{},consent:["analytics"]}));if((await this.http.post("/events/batch",{events:r})).success){this.consecutiveFailures=0,this.breakerOpenAt=0,this.breakerWarned=!1;return}if(this.consecutiveFailures++,this.consecutiveFailures>ue){this.breakerWarned||(this.breakerOpenAt=Date.now(),this.breakerWarned=!0,console.warn(`[SendoraCloud] Analytics circuit breaker open after ${ue} consecutive failed flushes \u2014 events will be dropped until a flush succeeds or ${le}ms elapse.`));return}let i=Math.min(le,1e3*2**this.consecutiveFailures);this.nextFlushAfter=Date.now()+i;let o=Math.max(0,Se-this.queue.length);o>0&&this.queue.unshift(...e.slice(0,o))}destroy(){this.timer&&clearInterval(this.timer),this.timer=null,this.flush()}getSessionId(){try{return typeof sessionStorage>"u"?void 0:sessionStorage.getItem("sendora_session_id")||void 0}catch{return}}getContext(){return typeof window>"u"?{sdk:{name:"@sendoracloud/sdk-web",version:E}}:{page:{url:this.stripQueryParams?window.location.origin+window.location.pathname:window.location.href,path:window.location.pathname,title:document.title},referrer:document.referrer||void 0,locale:typeof navigator<"u"?navigator.language:void 0,device:this.buildDevice(),sdk:{name:"@sendoracloud/sdk-web",version:E}}}buildDevice(){if(typeof navigator>"u")return;let e=navigator.userAgent||"",r=navigator.userAgentData?.mobile??/Mobi|Android|iPhone|iPod/i.test(e);return{type:/iPad|Tablet|(?:Android(?!.*Mobile))/i.test(e)?"tablet":r?"mobile":"desktop",os:"Web",osVersion:"",model:"",appVersion:this.resolveAppVersion(),browser:je(e)}}resolveAppVersion(){if(this.appVersion)return this.appVersion;if(typeof document<"u"){let r=document.querySelector('meta[name="sendora:app-version"]')?.getAttribute("content");if(r)return r}let e=globalThis.__SENDORA_APP_VERSION__;return typeof e=="string"?e:""}};function je(s){let e=[["Edge",/Edg\/(\d+)/],["Opera",/OPR\/(\d+)/],["Firefox",/Firefox\/(\d+)/],["Samsung Internet",/SamsungBrowser\/(\d+)/],["Chrome",/Chrome\/(\d+)/],["Safari",/Version\/(\d+)[^ ]* Safari/]];for(let[t,r]of e){let n=r.exec(s);if(n)return`${t} ${n[1]}`}return"Unknown"}var x=class{constructor(e,t,r){this.cache=new Map;this.refreshInterval=null;this.http=t,this.storage=r;let n=this.storage.get("flags");if(n)try{let i=JSON.parse(n);if(Array.isArray(i))for(let o of i)de(o)&&this.cache.set(o.key,o)}catch{}}setContext(e){this.userId=e.userId,this.entityId=e.entityId,this.environment=e.environment}async getFlag(e){if(typeof e!="string"||e.length===0||e.length>128)return null;let t=this.cache.get(e);if(t)return t.value;let r=await this.http.post("/feature-flags/evaluate",{key:e,userId:this.userId,entityId:this.entityId,environment:this.environment});return r.success&&r.data&&de(r.data)?(this.cache.set(e,r.data),this.persistCache(),r.data.value):null}async isEnabled(e,t=!1){let r=await this.getFlag(e);return typeof r=="boolean"?r:t}async refreshAll(){let e=await this.http.post("/feature-flags/evaluate-all",{userId:this.userId,entityId:this.entityId,environment:this.environment});if(e.success&&Array.isArray(e.data)){this.cache.clear();for(let t of e.data)de(t)&&this.cache.set(t.key,t);this.persistCache()}}startAutoRefresh(e=6e4){this.stopAutoRefresh(),this.refreshAll(),this.refreshInterval=setInterval(()=>this.refreshAll(),e)}stopAutoRefresh(){this.refreshInterval&&clearInterval(this.refreshInterval),this.refreshInterval=null}getAllFlags(){let e={};for(let[t,r]of this.cache)e[t]=r.value;return e}reset(){this.cache.clear(),this.storage.remove("flags"),this.userId=void 0,this.entityId=void 0,this.environment=void 0}destroy(){this.stopAutoRefresh()}persistCache(){this.storage.set("flags",JSON.stringify(Array.from(this.cache.values())))}};function de(s){if(s==null||typeof s!="object")return!1;let e=s;return Object.prototype.hasOwnProperty.call(e,"__proto__")||Object.prototype.hasOwnProperty.call(e,"constructor")?!1:typeof e.key=="string"&&"value"in e}var ze=32*1024,O=class{constructor(e,t,r){this.activeSurvey=null;this.http=t,this.storage=r}onDisplay(e){this.onSurveyDisplay=e}async showSurvey(e){if(typeof e!="string"||!/^[A-Za-z0-9_-]{6,64}$/.test(e))return null;let t=`survey_shown_${e}`;if(this.storage.get(t))return null;let r=await this.http.get(`/surveys/${encodeURIComponent(e)}/config`);return!r.success||!r.data||!Ge(r.data)?null:(this.activeSurvey=r.data,this.storage.set(t,"1"),this.onSurveyDisplay&&this.onSurveyDisplay(r.data),r.data)}async submitResponse(e,t,r=!0){let n=0;try{n=JSON.stringify(t).length}catch{return!1}if(n>ze)return!1;let i=await this.http.post("/surveys/responses",{surveyId:e,answers:t,completed:r});return i.success&&(this.activeSurvey=null),i.success}dismiss(){this.activeSurvey=null}getActiveSurvey(){return this.activeSurvey}};function Ge(s){if(s==null||typeof s!="object")return!1;let e=s;return typeof e.id!="string"||typeof e.name!="string"||!Array.isArray(e.questions)?!1:e.questions.every(Ye)}function Ye(s){if(s==null||typeof s!="object")return!1;let e=s;return typeof e.id=="string"&&typeof e.text=="string"}function S(s){if(!(!s||typeof s!="string"))try{let e=typeof window<"u"?window.location.href:"https://local/",t=new URL(s,e);return t.protocol==="http:"||t.protocol==="https:"?t.href:void 0}catch{return}}var Xe=new Set(["b","i","u","em","strong","a","p","br","span","ul","ol","li","h1","h2","h3","h4"]),Qe={a:new Set(["href","target","rel"])};function J(s){if(typeof DOMParser>"u")return"";let t=new DOMParser().parseFromString(`<div>${s}</div>`,"text/html").body.firstElementChild;return t?(be(t),t.innerHTML):""}function be(s){let e=Array.from(s.children);for(let t of e){let r=t.tagName.toLowerCase();if(!Xe.has(r)){let i=document.createTextNode(t.textContent??"");t.replaceWith(i);continue}let n=Qe[r]??new Set;for(let i of Array.from(t.attributes)){if(!n.has(i.name.toLowerCase())){t.removeAttribute(i.name);continue}if(i.name.toLowerCase()==="href"){let o=S(i.value);o?t.setAttribute("href",o):t.removeAttribute(i.name)}}r==="a"&&(t.setAttribute("rel","noopener noreferrer"),t.setAttribute("target","_blank")),be(t)}}var M=class{constructor(e,t,r){this.messages=[];this.pollInterval=null;this.http=t,this.storage=r}onDisplay(e){this.onMessageDisplay=e}async checkMessages(){let e=await this.http.get("/in-app-messages/active");if(!e.success||!Array.isArray(e.data))return null;this.messages=e.data.map(Je);for(let t of this.messages){let r=`iam_shown_${t.id}`;if(!this.storage.get(r))return this.storage.set(r,"1"),this.onMessageDisplay&&this.onMessageDisplay(t),t}return null}async recordAction(e,t){await this.http.post("/in-app-messages/impressions",{messageId:e,action:t})}startPolling(e=3e5){this.stopPolling(),this.checkMessages(),this.pollInterval=setInterval(()=>this.checkMessages(),e)}stopPolling(){this.pollInterval&&clearInterval(this.pollInterval),this.pollInterval=null}getMessages(){return this.messages}destroy(){this.stopPolling()}};function Je(s){return{...s,content:{...s.content,ctaUrl:S(s.content.ctaUrl),imageUrl:S(s.content.imageUrl)}}}var Ze=4e3,H=class{constructor(e,t,r){this.chatbotId=null;this.messages=[];this.http=t,this.storage=r;let n=this.storage.get("chat_session_id");this.sessionId=n??U("chat_"),n||this.storage.set("chat_session_id",this.sessionId)}init(e){if(typeof e!="string"||!/^[A-Za-z0-9_-]{6,64}$/.test(e))throw new Error("[SendoraCloud] Invalid chatbotId.");this.chatbotId=e}onNewMessage(e){this.onMessage=e}async sendMessage(e,t){if(!this.chatbotId||typeof e!="string"||e.length===0||e.length>Ze)return null;let r={role:"user",content:e,timestamp:new Date().toISOString()};this.messages.push(r);let n=await this.http.post("/chatbot/message",{chatbotId:this.chatbotId,sessionId:this.sessionId,message:e,userId:t});if(n.success&&n.data&&typeof n.data.reply=="string"){let i={role:"assistant",content:n.data.reply,timestamp:new Date().toISOString()};return this.messages.push(i),this.onMessage&&this.onMessage(i),i}return null}getMessages(){return[...this.messages]}newSession(){this.messages=[],this.sessionId=U("chat_"),this.storage.set("chat_session_id",this.sessionId)}getSessionId(){return this.sessionId}};var F=class{constructor(e,t){this.listeners=new Set;this.granted=e,this.http=t}isGranted(){return this.granted}grant(){this.granted||(this.granted=!0,this.emit())}revoke(){this.granted&&(this.granted=!1,this.emit())}subscribe(e){return this.listeners.add(e),()=>this.listeners.delete(e)}async record(e){if(!this.http)throw new Error("consent.record requires SDK init (HttpClient unavailable)");let t=await this.http.post("/consent/record",e);if(!t.success||!t.data)throw new Error(t.error?.message??"consent.record failed");return t.data}async requestDeletion(e){if(!this.http)throw new Error("consent.requestDeletion requires SDK init (HttpClient unavailable)");let t=await this.http.post("/consent/deletion-request",e);if(!t.success||!t.data)throw new Error(t.error?.message??"consent.requestDeletion failed");return t.data}emit(){for(let e of this.listeners)try{e(this.granted)}catch{}}};var T="auth_access_token",R="auth_access_expires",m="auth_refresh_token",_="auth_user",et=3e4,tt=/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;function y(s){if(!s||typeof s!="object")return!1;let e=s,t=e.user,r=e.tokens;return!(!t||typeof t.id!="string"||t.id.length===0||!r||typeof r.accessToken!="string"||r.accessToken.length===0||typeof r.refreshToken!="string"||r.refreshToken.length===0||typeof r.expiresIn!="number"||r.expiresIn<=0||t.isAnonymous!==void 0&&typeof t.isAnonymous!="boolean"||t.email!==void 0&&t.email!==null&&typeof t.email!="string"||t.emailVerified!==void 0&&typeof t.emailVerified!="boolean"||t.name!==void 0&&t.name!==null&&typeof t.name!="string"||t.signupMethod!==void 0&&t.signupMethod!==null&&typeof t.signupMethod!="string"||t.lastLoginMethod!==void 0&&t.lastLoginMethod!==null&&typeof t.lastLoginMethod!="string"||e.retiredAnonUserId!==void 0&&typeof e.retiredAnonUserId!="string")}var C=class extends Error{constructor(e="An account with this email already exists"){super(e),this.name="EmailAlreadyTakenError"}},K=class extends Error{constructor(e="Already signed in with an identity; use link*() to add a credential, or sign out first"){super(e),this.name="AlreadyIdentifiedError"}},P=class extends Error{constructor(t="This credential is already linked to a different account",r){super(t);this.code="CREDENTIAL_IN_USE";this.kind="credential_in_use";this.retryable=!1;this.name="CredentialInUseError",this.provider=r?.provider,this.collision=r?.collision,this.status=r?.status}};function Ee(s){let e=s?.error?.details;if(!e||typeof e!="object")return{};let t=e;return{provider:typeof t.provider=="string"?t.provider:void 0,collision:t.collision==="identity"||t.collision==="email"?t.collision:void 0}}var rt=new Set(["network","server","rate_limited","account_locked"]);function st(s,e){switch(s){case"NETWORK_ERROR":case"NETWORK_TIMEOUT":return"network";case"PARSE_ERROR":return"server";case"RATE_LIMITED":case"RATE_LIMIT":case"RATE_LIMIT_EXCEEDED":case"QUOTA_EXCEEDED":return"rate_limited";case"ACCOUNT_LOCKED":return"account_locked";case"CREDENTIAL_IN_USE":return"credential_in_use";case"NOT_ANONYMOUS":case"FORBIDDEN_NON_ANONYMOUS":return"already_identified";case"PASSKEY_USER_CANCELLED":case"GAME_CENTER_CANCELLED":case"PLAY_GAMES_CANCELLED":case"SSO_CANCELLED":return"cancelled";case"GAME_CENTER_UNAVAILABLE":case"PLAY_GAMES_UNAVAILABLE":case"ENTITLEMENT_ERROR":case"tier_required":return"config";case"UNAUTHORIZED":case"INVALID_CREDENTIALS":case"INVALID_REFRESH_TOKEN":case"EMAIL_OTP_INVALID":case"MAGIC_LINK_INVALID":case"PASSKEY_RP_MISMATCH":case"NOT_SIGNED_IN":return"invalid_credential";default:break}if(typeof e=="number"){if(e===429)return"rate_limited";if(e>=500)return"server";if(e>=400)return"invalid_credential"}return"unknown"}var u=class extends Error{constructor(e,t,r){super(t),this.name="AuthError",this.code=e,this.kind=r?.kind??st(e,r?.status),this.retryable=rt.has(this.kind),this.retryAfterSeconds=r?.retryAfterSeconds,this.status=r?.status}};function nt(s){if(s instanceof u||s instanceof C||s instanceof K||s instanceof P)return s;let e=s?.name;return e==="AbortError"||e==="TimeoutError"||e==="NetworkTimeoutError"?new u("NETWORK_TIMEOUT",s instanceof Error?s.message:"timeout",{kind:"network"}):new u("UNKNOWN_ERROR",s instanceof Error?s.message:String(s),{kind:"unknown"})}function p(s){let e=s.error?.status,t=s.error?.details?.retryAfterSeconds;return{status:typeof e=="number"?e:void 0,retryAfterSeconds:typeof t=="number"&&Number.isFinite(t)&&t>0?t:void 0}}var V=class{constructor(e,t,r,n,i,o){this.user=null;this.accessToken=null;this.accessExpiresAt=0;this.inflight=Promise.resolve();this.refreshInflight=null;this.refreshFailureCount=0;this.refreshCooldownUntil=0;this.visibilityResetUnsub=null;this.proactiveRefreshTimer=null;this.proactiveRefreshCleanup=null;this.takeoverListeners=new Set;this.lastTakeover=null;this.deletionCancelledListeners=new Set;this.lastDeletionCancelled=null;this.authStateListeners=new Set;this.pendingAnonTakeover=null;this.hydrated=!1;this.http=t,this.storage=r,this.analytics=n,this.flags=i,this.chatbot=o;let a=this.storage.get(_),c=this.storage.get(T),l=this.storage.get(m),g=this.storage.get(R);if(a&&(c||l))try{let v=JSON.parse(a);if(typeof v.id!="string"||v.id.length===0)throw new Error("invalid cache");this.user=v,c?(this.accessToken=c,this.accessExpiresAt=g?Number(g):0):(this.accessToken=null,this.accessExpiresAt=0),this.analytics.identify(this.user.id)}catch{this.storage.remove(_),this.storage.remove(T),this.storage.remove(R)}this.hydrated=!0}getCurrentUser(){return this.user}onDeviceTakeover(e){return this.takeoverListeners.add(e),()=>{this.takeoverListeners.delete(e)}}getLastDeviceTakeover(){return this.lastTakeover}onAuthStateChanged(e){if(this.authStateListeners.add(e),this.hydrated&&this.user){let t=this.user;try{e({event:"signed_in",user:t})}catch{}}return()=>{this.authStateListeners.delete(e)}}emitAuthState(e){for(let t of this.authStateListeners)try{t(e)}catch{}}fireDeviceTakeover(e,t){if(!e||!tt.test(e))return;let r={retiredAnonUserId:e,identifiedUserId:t,at:new Date};this.lastTakeover=r;for(let n of this.takeoverListeners)try{n(r)}catch{}this.emitAuthState({event:"device_takeover",user:this.user,retiredAnonUserId:e})}onDeletionCancelled(e){return this.deletionCancelledListeners.add(e),()=>{this.deletionCancelledListeners.delete(e)}}getLastDeletionCancelled(){return this.lastDeletionCancelled}fireDeletionCancelled(e){let t={userId:e,at:new Date};this.lastDeletionCancelled=t;for(let r of this.deletionCancelledListeners)try{r(t)}catch{}this.emitAuthState({event:"deletion_cancelled",user:this.user})}takeoverHint(){if(this.user?.isAnonymous)return this.storage.get(m)??void 0}async getAccessToken(){return this.accessToken&&this.accessExpiresAt&&Date.now()<this.accessExpiresAt-et?this.accessToken:this.storage.get(m)?this.refreshInflight?this.refreshInflight:Date.now()<this.refreshCooldownUntil?null:this.refreshAccessToken():null}getAccessTokenSync(){return this.accessToken}signInAnonymously(e){return this.serialize(async()=>{let t=this.user;if(!e?.forceNew&&t?.isAnonymous&&this.storage.get(m))return t;let r=await this.http.post("/auth-service/anonymous",{name:e?.name,metadata:e?.metadata});if(!r.success||!y(r.data))throw new u(r.error?.code??"AUTH_ERROR",r.error?.message??"Anonymous signup failed",p(r));return this.persist(r.data),r.data.user})}signUp(e,t,r){return this.serialize(async()=>{let n=this.storage.get(m);if(n){let o=await this.http.post("/auth-service/upgrade",{refreshToken:n,email:e,password:t,name:r?.name});if(o.success&&y(o.data))return this.persist(o.data),o.data.user;let a=o.error?.code??"",c=p(o),l=c.status;if(a==="NOT_ANONYMOUS"||a==="FORBIDDEN_NON_ANONYMOUS")throw new K(o.error?.message);if(a==="CONFLICT"||a==="EMAIL_ALREADY_TAKEN"||l===409)throw new C(o.error?.message);if(a==="INVALID_REFRESH"||a==="UNAUTHORIZED"||a==="REFRESH_EXPIRED")await this.analytics.flush(),this.wipeLocalIdentity("session_expired");else throw new u(a||"AUTH_ERROR",o.error?.message??"Upgrade failed",c)}let i=await this.http.post("/auth-service/signup",{email:e,password:t,name:r?.name,metadata:r?.metadata});if(!i.success||!y(i.data)){let o=i.error?.code??"";throw o==="CONFLICT"?new C(i.error?.message):new u(o||"AUTH_ERROR",i.error?.message??"Signup failed",p(i))}return this.persist(i.data),i.data.user})}signIn(e,t){return this.serialize(async()=>{let r=this.takeoverHint(),n=await this.http.post("/auth-service/login",{email:e,password:t,...r?{prevAnonRefreshToken:r}:{}});if(!n.success||!n.data)throw new u(n.error?.code??"AUTH_ERROR",n.error?.message??"Login failed",p(n));let i=n.data;if(i.mfaRequired===!0&&typeof i.mfaChallengeToken=="string"){r&&(this.pendingAnonTakeover={challengeToken:i.mfaChallengeToken,prevAnonRefreshToken:r});let o=i.user;return{mfaRequired:!0,mfaChallengeToken:i.mfaChallengeToken,userId:o?.id??""}}if(!y(n.data))throw new u(n.error?.code??"AUTH_ERROR",n.error?.message??"Login failed",p(n));return await this.replaceLocalIdentity(),this.persist(n.data),n.data.user})}loginSocial(e){return this.serialize(async()=>{let t=this.takeoverHint(),{link:r,onCredentialInUse:n,...i}=e,o=await this.http.post("/auth-service/login/social",{...i,...t?{prevAnonRefreshToken:t}:{},...r?{linkAnonymous:!0}:{},...n?{onCredentialInUse:n}:{}});if(!o.success||!y(o.data))throw o.error?.code==="CREDENTIAL_IN_USE"?new P(o.error.message,{...Ee(o),status:p(o).status}):new u(o.error?.code??"SOCIAL_LOGIN_FAILED",o.error?.message??"Social login failed",p(o));return await this.replaceLocalIdentity(),this.persist(o.data),o.data.user})}signInWithGoogle(e,t,r){return this.loginSocial({provider:"google",code:e,redirectUri:t,link:r})}signInWithGitHub(e,t){return this.loginSocial({provider:"github",code:e,redirectUri:t})}signInWithApple(e){return this.loginSocial({provider:"apple",...e})}signInWithMicrosoft(e,t){return this.loginSocial({provider:"microsoft",code:e,redirectUri:t})}signInWithLinkedIn(e,t){return this.loginSocial({provider:"linkedin",code:e,redirectUri:t})}signInWithFacebook(e,t){return this.loginSocial({provider:"facebook",code:e,redirectUri:t})}linkEmailPassword(e,t){return this.linkCredential("/auth-service/me/link/email",{email:e,password:t})}linkSocial(e){return this.linkCredential("/auth-service/me/link/social",e)}linkGoogle(e){return this.linkSocial({provider:"google",...e})}linkApple(e){return this.linkSocial({provider:"apple",...e})}async linkCredential(e,t){let r=await this.getAccessToken();if(!r)throw new u("NOT_SIGNED_IN","Sign in before linking a credential");let n=await this.http.post(e,t,{Authorization:`Bearer ${r}`});if(!n.success||!n.data?.user){let i=n.error?.code??"LINK_FAILED";throw i==="CREDENTIAL_IN_USE"?new P(n.error?.message,{...Ee(n),status:p(n).status}):new u(i,n.error?.message??"Failed to link credential",p(n))}return n.data.upgraded&&y(n.data)?(this.persist(n.data),n.data.user):(this.updateLocalUser(n.data.user),n.data.user)}updateLocalUser(e){this.user=e,this.storage.set(_,JSON.stringify(e))}async listLinkedIdentities(){let e=await this.getAccessToken();if(!e)throw new u("NOT_SIGNED_IN","Sign in before listing linked identities");let t=await this.http.get("/auth-service/me/identities",{Authorization:`Bearer ${e}`});if(!t.success||!t.data)throw new u(t.error?.code??"IDENTITIES_FAILED",t.error?.message??"Failed to list linked identities",p(t));return{identities:t.data.identities??[],hasPassword:!!t.data.hasPassword}}signInWithDiscord(e,t){return this.loginSocial({provider:"discord",code:e,redirectUri:t})}challengeMfa(e,t){return this.serialize(async()=>{let n=(this.pendingAnonTakeover?.challengeToken===e?this.pendingAnonTakeover.prevAnonRefreshToken:void 0)??this.takeoverHint(),i=await this.http.post("/auth-service/mfa/challenge",{challengeToken:e,code:t,...n?{prevAnonRefreshToken:n}:{}});if(!i.success||!y(i.data))throw new u(i.error?.code??"MFA_CHALLENGE_FAILED",i.error?.message??"Invalid MFA code",p(i));return await this.replaceLocalIdentity(),this.persist(i.data),i.data.user})}async sendMagicLink(e){let t=await this.http.post("/auth-service/magic-link/request",{email:e});if(!t.success)throw new u(t.error?.code??"MAGIC_LINK_REQUEST_FAILED",t.error?.message??"Failed to send link")}async sendEmailOtp(e){let t=await this.http.post("/auth-service/email-otp/request",{email:e});if(!t.success)throw new u(t.error?.code??"EMAIL_OTP_REQUEST_FAILED",t.error?.message??"Failed to send code")}verifyEmailOtp(e,t){return this.serialize(async()=>{let r=this.takeoverHint(),n=await this.http.post("/auth-service/email-otp/verify",{email:e,code:t,...r?{prevAnonRefreshToken:r}:{}});if(!n.success||!y(n.data))throw new u(n.error?.code??"EMAIL_OTP_INVALID",n.error?.message??"Wrong or expired code",p(n));return await this.replaceLocalIdentity(),this.persist(n.data),n.data.user})}async requestPasswordReset(e){let t=await this.http.post("/auth-service/password/forgot",{email:e});if(!t.success)throw new u(t.error?.code??"PASSWORD_RESET_REQUEST_FAILED",t.error?.message??"Failed to send reset email")}async resetPassword(e,t){let r=await this.http.post("/auth-service/password/reset",{token:e,newPassword:t});if(!r.success)throw new u(r.error?.code??"PASSWORD_RESET_FAILED",r.error?.message??"Reset failed")}async verifyEmail(e){let t=await this.http.post("/auth-service/email/verify",{token:e});if(!t.success)throw new u(t.error?.code??"EMAIL_VERIFICATION_FAILED",t.error?.message??"Verification failed")}async sendVerificationEmail(){let e=await this.getAccessToken();if(!e)throw new u("UNAUTHORIZED","Sign in before requesting a verification email");let t=await this.http.post("/auth-service/email/verify/resend",{},{Authorization:`Bearer ${e}`});if(!t.success)throw new u(t.error?.code??"VERIFICATION_RESEND_FAILED",t.error?.message??"Failed to send verification email")}verifyMagicLink(e){return this.serialize(async()=>{let t=this.takeoverHint(),r=await this.http.post("/auth-service/magic-link/verify",{token:e,...t?{prevAnonRefreshToken:t}:{}});if(!r.success||!y(r.data))throw new u(r.error?.code??"MAGIC_LINK_INVALID",r.error?.message??"Invalid or expired magic link",p(r));return await this.replaceLocalIdentity(),this.persist(r.data),r.data.user})}async enrollMfa(){let e=await this.http.post("/auth-service/mfa/enroll/start",{},await this.bearerHeaders());if(!e.success||!e.data)throw new u(e.error?.code??"MFA_ENROLL_FAILED",e.error?.message??"MFA enrollment failed");return e.data}async confirmMfa(e){let t=await this.http.post("/auth-service/mfa/enroll/confirm",{code:e},await this.bearerHeaders());return!!(t.success&&t.data?.confirmed)}async disableMfa(){await this.http.post("/auth-service/mfa/disable",{},await this.bearerHeaders())}async registerPasskey(e){if(typeof navigator>"u"||!navigator.credentials)throw new u("PASSKEY_UNSUPPORTED","WebAuthn not supported in this environment");let t=await this.http.post("/auth-service/passkeys/register/start",{},await this.bearerHeaders());if(!t.success||!t.data)throw new u(t.error?.code??"PASSKEY_START_FAILED",t.error?.message??"Passkey start failed");let r=ot(t.data),n;try{let a=await navigator.credentials.create({publicKey:r});if(!a)throw new Error("create returned null");n=a}catch(a){throw new u("PASSKEY_USER_CANCELLED",a instanceof Error?a.message:"User cancelled passkey registration")}let i=ct(n),o=await this.http.post("/auth-service/passkeys/register/finish",{response:i,name:e},await this.bearerHeaders());if(!o.success||!o.data)throw new u(o.error?.code??"PASSKEY_FINISH_FAILED",o.error?.message??"Passkey verification failed");return o.data}signInWithPasskey(e){return this.serialize(async()=>{if(typeof navigator>"u"||!navigator.credentials)throw new u("PASSKEY_UNSUPPORTED","WebAuthn not supported in this environment");let t=this.takeoverHint(),r=await this.http.post("/auth-service/passkeys/authenticate/start",{userId:e??null});if(!r.success||!r.data)throw new u(r.error?.code??"PASSKEY_START_FAILED",r.error?.message??"Passkey start failed",p(r));let n=at(r.data),i;try{let c=await navigator.credentials.get({publicKey:n});if(!c)throw new Error("get returned null");i=c}catch(c){throw new u("PASSKEY_USER_CANCELLED",c instanceof Error?c.message:"User cancelled passkey signin")}let o=dt(i),a=await this.http.post("/auth-service/passkeys/authenticate/finish",{response:o,userId:e??null,...t?{prevAnonRefreshToken:t}:{}});if(!a.success||!y(a.data))throw new u(a.error?.code??"PASSKEY_AUTH_FAILED",a.error?.message??"Passkey signin failed",p(a));return await this.replaceLocalIdentity(),this.persist(a.data),a.data.user})}async listPasskeys(){let e=await this.http.get("/auth-service/passkeys",await this.bearerHeaders());return!e.success||!Array.isArray(e.data)?[]:e.data}async deletePasskey(e){await this.http.delete(`/auth-service/passkeys/${e}`,await this.bearerHeaders())}async listMySessions(){let e=await this.http.get("/auth-service/sessions/me",await this.bearerHeaders());return!e.success||!Array.isArray(e.data)?[]:e.data}async revokeSession(e){await this.http.delete(`/auth-service/sessions/me/${e}`,await this.bearerHeaders())}async revokeAllSessions(){await this.http.delete("/auth-service/sessions/me",await this.bearerHeaders())}async startSso(e){let t=this.takeoverHint(),r=await this.http.post("/auth-service/sso/oidc/start",{returnTo:e,...t?{prevAnonRefreshToken:t}:{}});if(!r.success||!r.data)throw new u(r.error?.code??"SSO_START_FAILED",r.error?.message??"SSO start failed");return r.data}async signInWithSso(e){let t=e??(typeof window<"u"?window.location.origin:"");if(!t)throw new u("SSO_NO_RETURN_TO","returnTo required outside browser context");let r=await this.startSso(t);typeof window<"u"&&(window.location.href=r.authorizationUrl)}async startSaml(e){let t=this.takeoverHint(),r=await this.http.post("/auth-service/sso/saml/start",{returnTo:e,...t?{prevAnonRefreshToken:t}:{}});if(!r.success||!r.data)throw new u(r.error?.code??"SAML_START_FAILED",r.error?.message??"SAML start failed");return r.data}async signInWithSaml(e){let t=e??(typeof window<"u"?window.location.origin:"");if(!t)throw new u("SAML_NO_RETURN_TO","returnTo required outside browser context");let r=await this.startSaml(t);typeof window<"u"&&(window.location.href=r.authorizationUrl)}consumeSsoTokenFromUrl(){return this.serialize(async()=>{if(typeof window>"u")return null;let e=window.location.hash.replace(/^#/,"");if(!e)return null;let t=new URLSearchParams(e),r=t.get("sendora_oidc_error")??t.get("sendora_saml_error");if(r)throw window.history.replaceState(null,"",window.location.pathname+window.location.search),new u("SSO_FAILED",decodeURIComponent(r));let n=t.get("sendora_oidc_token")??t.get("sendora_saml_token");if(!n)return null;let i=t.get("sendora_retired_anon");window.history.replaceState(null,"",window.location.pathname+window.location.search);let o=await this.http.post("/auth-service/token/refresh",{refreshToken:n}),a=o.success?Te(o.data):null;if(!a)throw new u(o.error?.code??"SSO_EXCHANGE_FAILED",o.error?.message??"Could not exchange SSO token",p(o));let c={...a,tokenType:"Bearer"};await this.replaceLocalIdentity(),this.accessToken=c.accessToken,this.accessExpiresAt=Date.now()+c.expiresIn*1e3,this.storage.set(T,c.accessToken),this.storage.set(R,String(this.accessExpiresAt)),this.storage.set(m,c.refreshToken);let l=lt(c.accessToken),g={id:l?.sub??"",email:l?.email??null,emailVerified:l?.email_verified??!0,name:l?.name??null,isAnonymous:!1};return this.user=g,this.storage.set(_,JSON.stringify(g)),this.analytics.identify(g.id),this.flags.setContext({userId:g.id}),this.emitAuthState({event:"signed_in",user:g}),i&&this.fireDeviceTakeover(i,g.id),g})}async bearerHeaders(){let e=await this.getAccessToken();if(!e)throw new u("NOT_SIGNED_IN","Not signed in");return{Authorization:`Bearer ${e}`}}signOut(){return this.serialize(async()=>{let e=this.storage.get(m);this.wipeLocalIdentity("user"),e&&this.http.post("/auth-service/token/revoke",{refreshToken:e}).catch(()=>{})})}serialize(e){let t=async()=>{try{return await e()}catch(n){throw nt(n)}},r=this.inflight.then(t,t);return this.inflight=r.catch(()=>{}),r}async refreshAccessToken(){if(this.refreshInflight)return this.refreshInflight;let e=this.storage.get(m);if(!e)return null;let t=typeof navigator<"u"&&"locks"in navigator,r=async()=>{let n=this.storage.get(m);if(n&&n!==e){let i=this.storage.get(T),o=this.storage.get(R);if(i&&o){let a=Number(o);if(Number.isFinite(a)&&a>Date.now())return this.accessToken=i,this.accessExpiresAt=a,i}}try{let i=n??e,o=await this.http.post("/auth-service/token/refresh",{refreshToken:i});if(!o.success)return it(o.error?.code)&&this.tokenStillCurrent(i)?(this.wipeLocalIdentity("session_expired"),this.refreshFailureCount=0,this.refreshCooldownUntil=0,null):(this.armBackoff(),null);let a=Te(o.data);if(!a)return this.armBackoff(),null;if(!this.tokenStillCurrent(i))return null;if(this.accessToken=a.accessToken,this.accessExpiresAt=Date.now()+a.expiresIn*1e3,this.storage.set(T,a.accessToken),this.storage.set(R,String(this.accessExpiresAt)),this.storage.set(m,a.refreshToken),this.user===null){let c=ut(o.data);c&&(this.user=c,this.storage.set(_,JSON.stringify(c)),this.analytics.identify(c.id),this.emitAuthState({event:"signed_in",user:c}))}return this.refreshFailureCount=0,this.refreshCooldownUntil=0,a.accessToken}catch{return this.armBackoff(),null}};return this.refreshInflight=(async()=>{try{return t?await navigator.locks.request("sendora-authservice-refresh",{mode:"exclusive"},r):await r()}finally{this.refreshInflight=null}})(),this.refreshInflight}armBackoff(){this.refreshFailureCount+=1;let e=Math.min(3e4,1e3*Math.pow(2,this.refreshFailureCount-1));this.refreshCooldownUntil=Date.now()+e}resetRefreshState(){this.refreshFailureCount=0,this.refreshCooldownUntil=0}startVisibilityRefreshReset(){if(this.visibilityResetUnsub||typeof document>"u")return;let e=()=>{document.visibilityState==="visible"&&this.resetRefreshState()};document.addEventListener("visibilitychange",e),this.visibilityResetUnsub=()=>document.removeEventListener("visibilitychange",e)}stopVisibilityRefreshReset(){this.visibilityResetUnsub&&(this.visibilityResetUnsub(),this.visibilityResetUnsub=null)}startProactiveRefreshCron(){if(typeof window>"u"||this.proactiveRefreshTimer)return;let e=.8,t=6e4,r=3e4,n=async()=>{if(!this.accessToken||!this.accessExpiresAt)return;let o=Date.now(),a=this.accessExpiresAt-o;if(a<=0)return;let l=Math.max(a,5*6e4)*(1-e)+(Math.random()*r*2-r);a<=l&&await this.refreshAccessToken()};this.proactiveRefreshTimer=window.setInterval(()=>{n()},t);let i=()=>{document.visibilityState==="visible"&&n()};document.addEventListener("visibilitychange",i),this.proactiveRefreshCleanup=()=>{this.proactiveRefreshTimer&&(window.clearInterval(this.proactiveRefreshTimer),this.proactiveRefreshTimer=null),document.removeEventListener("visibilitychange",i)}}stopProactiveRefreshCron(){this.proactiveRefreshCleanup&&(this.proactiveRefreshCleanup(),this.proactiveRefreshCleanup=null)}persist(e){this.user=e.user,this.accessToken=e.tokens.accessToken,this.accessExpiresAt=Date.now()+e.tokens.expiresIn*1e3,this.storage.set(_,JSON.stringify(e.user)),this.storage.set(T,e.tokens.accessToken),this.storage.set(R,String(this.accessExpiresAt)),this.storage.set(m,e.tokens.refreshToken),this.analytics.identify(e.user.id),this.flags.setContext({userId:e.user.id}),this.startProactiveRefreshCron(),this.startVisibilityRefreshReset(),this.emitAuthState({event:"signed_in",user:e.user}),e.retiredAnonUserId&&this.fireDeviceTakeover(e.retiredAnonUserId,e.user.id),e.reactivatedFromDeletion&&this.fireDeletionCancelled(e.user.id)}async replaceLocalIdentity(){this.user!==null&&(await this.analytics.flush(),this.wipeLocalIdentity("replaced"))}tokenStillCurrent(e){return this.storage.get(m)===e}wipeLocalIdentity(e="replaced"){this.user=null,this.accessToken=null,this.accessExpiresAt=0,this.lastTakeover=null,this.lastDeletionCancelled=null,this.pendingAnonTakeover=null,this.storage.remove(_),this.storage.remove(T),this.storage.remove(R),this.storage.remove(m),this.analytics.reset(),this.analytics.dropQueue(),this.flags.reset(),this.chatbot.newSession(),this.stopProactiveRefreshCron(),this.stopVisibilityRefreshReset(),this.refreshFailureCount=0,this.refreshCooldownUntil=0,e!=="replaced"&&this.emitAuthState({event:"signed_out",user:null,reason:e})}};function it(s){return s?s==="INVALID_REFRESH_TOKEN":!1}function j(s){let e=(4-s.length%4)%4,t=(s+"===".slice(0,e)).replace(/-/g,"+").replace(/_/g,"/"),r=atob(t),n=new ArrayBuffer(r.length),i=new Uint8Array(n);for(let o=0;o<r.length;o++)i[o]=r.charCodeAt(o);return n}function b(s){let e=s instanceof Uint8Array?s:new Uint8Array(s),t="";for(let r=0;r<e.length;r++)t+=String.fromCharCode(e[r]);return btoa(t).replace(/\+/g,"-").replace(/\//g,"_").replace(/=+$/,"")}function ot(s){return{challenge:j(s.challenge),rp:s.rp,user:{id:j(s.user.id),name:s.user.name,displayName:s.user.displayName},pubKeyCredParams:s.pubKeyCredParams,timeout:s.timeout,attestation:s.attestation,excludeCredentials:s.excludeCredentials?.map(e=>({id:j(e.id),type:"public-key",transports:e.transports})),authenticatorSelection:s.authenticatorSelection}}function at(s){return{challenge:j(s.challenge),timeout:s.timeout,rpId:s.rpId,allowCredentials:s.allowCredentials?.map(e=>({id:j(e.id),type:"public-key",transports:e.transports})),userVerification:s.userVerification}}function ct(s){let e=s.response;return{id:s.id,rawId:b(s.rawId),type:s.type,response:{clientDataJSON:b(e.clientDataJSON),attestationObject:b(e.attestationObject),transports:typeof e.getTransports=="function"?e.getTransports():void 0},clientExtensionResults:s.getClientExtensionResults()}}function Te(s){if(!s||typeof s!="object")return null;let e=s.tokens,t=e&&typeof e=="object"?e:s;return typeof t.accessToken!="string"||!t.accessToken||typeof t.refreshToken!="string"||!t.refreshToken||typeof t.expiresIn!="number"||t.expiresIn<=0?null:{accessToken:t.accessToken,refreshToken:t.refreshToken,expiresIn:t.expiresIn}}function ut(s){if(!s||typeof s!="object")return null;let e=s.user;if(!e||typeof e!="object")return null;let t=e.id;return typeof t!="string"||t.length===0?null:e}function lt(s){try{let e=s.split(".");if(e.length!==3)return null;let t=e[1].replace(/-/g,"+").replace(/_/g,"/"),r=atob(t+"===".slice(0,(4-t.length%4)%4));return JSON.parse(r)}catch{return null}}function dt(s){let e=s.response;return{id:s.id,rawId:b(s.rawId),type:s.type,response:{clientDataJSON:b(e.clientDataJSON),authenticatorData:b(e.authenticatorData),signature:b(e.signature),userHandle:e.userHandle?b(e.userHandle):null},clientExtensionResults:s.getClientExtensionResults()}}function ht(s){return s===!1?{pageViews:!1,sessionStart:!1,appOpen:!1,appBackground:!1,engagement:!1}:s===!0||s===void 0?{pageViews:!0,sessionStart:!0,appOpen:!0,appBackground:!0,engagement:!0}:{pageViews:s.pageViews??!0,sessionStart:s.sessionStart??!0,appOpen:s.appOpen??!0,appBackground:s.appBackground??!0,engagement:s.engagement??!0}}var pt=250,Re=360*60*1e3;function Pe(s,e){if(typeof window>"u")return()=>{};let t=ht(e.autoTrack),r=e.sessionIdleMs??18e5,n=[],i="sendora_session_id",o="sendora_session_last_activity";function a(){return _e(i)??""}function c(d){Ce(i,d)}function l(){let d=_e(o);return d&&Number(d)||0}function g(d){Ce(o,String(d))}function v(){return`sess_${Date.now().toString(36)}_${Math.random().toString(36).slice(2,10)}`}function w(d){let I=Date.now(),q=l();if(!a()||q&&I-q>r){let G=v();c(G),d&&t.sessionStart&&s.track("session.start",{sessionId:G})}g(I)}w(t.sessionStart),t.appOpen&&s.track("app.opened",{sessionId:a()||void 0,url:window.location.origin+window.location.pathname,referrer:document.referrer||void 0});let k=window.location.pathname,L=0,A=document.visibilityState==="visible"?Date.now():0;function ne(){A>0&&(L+=Date.now()-A,A=0)}function z(){if(!t.engagement)return;ne();let d=L;if(L=0,!(d<pt)){d>Re&&(d=Re);try{s.track("app.engagement",{durationMs:Math.round(d),screen:k,sessionId:a()||void 0})}catch{}}}function De(d){k=d,L=0,A=document.visibilityState==="visible"?Date.now():0}if(t.pageViews||t.engagement){t.pageViews&&s.page();let d=()=>{z(),w(!0),t.pageViews&&s.page(),De(window.location.pathname)},I=()=>d();window.addEventListener("popstate",I),n.push(()=>window.removeEventListener("popstate",I));let q=window;if(!q.__sendoraHistoryPatched){q.__sendoraHistoryPatched=!0;let G=history.pushState,Ue=history.replaceState;history.pushState=function(...oe){let ae=G.apply(this,oe);return window.dispatchEvent(new Event("sendora:locationchange")),ae},history.replaceState=function(...oe){let ae=Ue.apply(this,oe);return window.dispatchEvent(new Event("sendora:locationchange")),ae}}let ie=()=>d();window.addEventListener("sendora:locationchange",ie),n.push(()=>window.removeEventListener("sendora:locationchange",ie))}if(t.appBackground||t.engagement){let d=()=>{if(document.visibilityState==="hidden"){z(),t.appBackground&&(w(!0),s.track("app.backgrounded",{sessionId:a()||void 0}));try{s.flush()}catch{}}else document.visibilityState==="visible"&&(t.appBackground&&(w(!0),s.track("app.foregrounded",{sessionId:a()||void 0})),t.engagement&&A===0&&(A=Date.now()))};if(document.addEventListener("visibilitychange",d),n.push(()=>document.removeEventListener("visibilitychange",d)),t.engagement){let I=()=>{z();try{s.flush()}catch{}};window.addEventListener("pagehide",I),n.push(()=>window.removeEventListener("pagehide",I))}}let ge=0,D=()=>{let d=Date.now();d-ge<5e3||(ge=d,w(!0))};return window.addEventListener("click",D,{passive:!0}),window.addEventListener("keydown",D,{passive:!0}),window.addEventListener("scroll",D,{passive:!0}),n.push(()=>{window.removeEventListener("click",D),window.removeEventListener("keydown",D),window.removeEventListener("scroll",D)}),()=>{z();for(let d of n)try{d()}catch{}}}function _e(s){try{return window.sessionStorage?.getItem(s)??null}catch{return null}}function Ce(s,e){try{window.sessionStorage?.setItem(s,e)}catch{}}var W=class s{constructor(e){this.http=e}static isSupported(){return typeof window>"u"?!1:"serviceWorker"in navigator&&"PushManager"in window&&"Notification"in window}static permission(){return typeof Notification>"u"?"denied":Notification.permission}async subscribe(e){if(!s.isSupported())throw new Error("[sendoracloud] Web Push not supported in this browser.");let t=Notification.permission;if(t==="default"&&(t=await Notification.requestPermission()),t!=="granted")throw new Error(`[sendoracloud] Notification permission ${t}.`);let r=await navigator.serviceWorker.getRegistration(e.scope??"/");r||(r=await navigator.serviceWorker.register(e.serviceWorkerPath,{scope:e.scope??"/"})),await navigator.serviceWorker.ready;let i=(await this.http.get("/push/vapid"))?.data?.publicKey;if(!i)throw new Error("[sendoracloud] backend returned no VAPID public key.");let a=await r.pushManager.getSubscription()??await r.pushManager.subscribe({userVisibleOnly:!0,applicationServerKey:gt(i)}),c=a.toJSON(),l=c.endpoint,g=c.keys?.p256dh??"",v=c.keys?.auth??"",w=await ft(l),k=await this.http.post("/push/tokens",{platform:"web",token:w,webEndpoint:l,webP256dh:g,webAuth:v,userAgent:typeof navigator<"u"?navigator.userAgent:void 0,locale:typeof navigator<"u"?navigator.language:void 0,timezone:Intl?.DateTimeFormat?.().resolvedOptions?.().timeZone,...e.userId?{userId:e.userId}:{}});if(!k?.data?.tokenId)throw new Error("[sendoracloud] backend returned no tokenId on subscribe.");return{tokenId:k.data.tokenId,subscription:a}}async unsubscribe(e="/"){if(!s.isSupported())return!1;let t=await navigator.serviceWorker.getRegistration(e);if(!t)return!1;let r=await t.pushManager.getSubscription();return r?await r.unsubscribe():!1}async trackOpen(e,t){let r={sendId:e};t!==void 0&&(r.clickAction=t);let n=await this.http.post("/push/track-open",r);if(!n.success)throw new Error(n.error?.message??"push.trackOpen failed")}};function gt(s){let e="=".repeat((4-s.length%4)%4),t=(s+e).replace(/-/g,"+").replace(/_/g,"/"),r=atob(t),n=new Uint8Array(r.length);for(let i=0;i<r.length;i++)n[i]=r.charCodeAt(i);return n}async function ft(s){let e=new TextEncoder().encode(s),t=await crypto.subtle.digest("SHA-256",e);return Array.from(new Uint8Array(t)).map(n=>n.toString(16).padStart(2,"0")).join("")}var $=class{constructor(e){}unsupported(){throw new Error("[SendoraCloud] sendora.audiences is not available in the browser SDK. Audiences require a secret (sk_*) key with the audiences:read/write scopes and must be managed from a trusted server, not the browser.")}list(){return this.unsupported()}get(e){return this.unsupported()}create(e){return this.unsupported()}update(e,t){return this.unsupported()}delete(e){return this.unsupported()}};var h=class extends Error{constructor(e,t,r){super(t),this.name="LinkError",this.code=e,this.statusCode=r}};function he(s,e){let t=(s??"").toUpperCase();return t==="NOT_FOUND"?new h("NOT_FOUND",e):t==="FORBIDDEN"||t==="UNAUTHORIZED"?new h("UNAUTHORIZED",e):t==="RATE_LIMITED"?new h("RATE_LIMITED",e):t==="ENTITLEMENT_ERROR"||/plan limit/i.test(e)?new h("PLAN_LIMIT",e):t==="VALIDATION_ERROR"?/iOS bundle|Android package/i.test(e)?new h("BUNDLE_MISMATCH",e):/2KB|10KB|linkData/i.test(e)?new h("DATA_TOO_LARGE",e):/fallbackUrl/i.test(e)&&/apps/i.test(e)?new h("FALLBACK_REQUIRED",e):new h("INVALID_INPUT",e):t==="PARSE_ERROR"||t==="HTTP_ERROR"||/HTTP \d{3}/.test(e)?new h("NETWORK",e):new h("UNKNOWN",e||`Unknown LinkError (code=${s??"none"})`)}var pe=/^[a-z0-9-]{3,20}$/;function Le(s){try{let t=new URL(s).pathname.split("/").filter(Boolean);if(t.length===0)return null;let r=t[t.length-1];return pe.test(r)?r:null}catch{return null}}var mt=5*6e4,yt=50,vt=5,B=class{constructor(e){this.http=e;this.prewarmCache=new Map;this.prewarmInflight=0}cacheKey(e,t){if(t)return`k:${t}`;let r={},n=e;for(let i of Object.keys(n).sort())r[i]=n[i];return`s:${JSON.stringify(r)}`}evictExpired(){let e=Date.now();for(let[t,r]of this.prewarmCache)e-r.ts>mt&&this.prewarmCache.delete(t);for(;this.prewarmCache.size>yt;){let t=this.prewarmCache.keys().next().value;if(t===void 0)break;this.prewarmCache.delete(t)}}prewarm(e,t){this.evictExpired();let r=this.cacheKey(e,t?.key);if(this.prewarmCache.has(r)||this.prewarmInflight>=vt)return;this.prewarmInflight++;let n=this.doCreate(e).catch(i=>{throw this.prewarmCache.delete(r),i}).finally(()=>{this.prewarmInflight--});this.prewarmCache.set(r,{promise:n,ts:Date.now()})}async create(e,t){if(!e.title)throw new h("INVALID_INPUT","links.create: title is required");this.evictExpired();let r=this.cacheKey(e,t?.key),n=this.prewarmCache.get(r);return n?(this.prewarmCache.delete(r),n.promise):this.doCreate(e)}async doCreate(e){let t={title:e.title};e.fallbackUrl&&(t.fallbackUrl=e.fallbackUrl),e.iosDeepLinkPath&&(t.iosDeepLinkPath=e.iosDeepLinkPath),e.androidDeepLinkPath&&(t.androidDeepLinkPath=e.androidDeepLinkPath),e.linkData&&(t.linkData=e.linkData),e.ogTitle&&(t.ogTitle=e.ogTitle),e.ogDescription&&(t.ogDescription=e.ogDescription),e.ogImageUrl&&(t.ogImageUrl=e.ogImageUrl),e.campaign&&(t.campaign=e.campaign),e.source&&(t.source=e.source),e.medium&&(t.medium=e.medium),e.channel&&(t.channel=e.channel),e.tags&&(t.tags=e.tags),e.expiresAt&&(t.expiresAt=e.expiresAt),e.iosBundleId&&(t.iosBundleId=e.iosBundleId),e.androidPackageName&&(t.androidPackageName=e.androidPackageName);let r=await this.http.post("/sdk/links",t);if(!r.success||!r.data)throw he(r.error?.code,r.error?.message??"links.create failed");return r.data}async revoke(e){if(!pe.test(e))throw new h("INVALID_INPUT",`links.revoke: '${e}' is not a valid shortcode`);let t=await this.http.post(`/sdk/links/${encodeURIComponent(e)}/revoke`,{});if(!t.success||!t.data)throw he(t.error?.code,t.error?.message??"links.revoke failed");return{shortcode:t.data.shortcode,revoked:!0}}async getStats(e){if(!pe.test(e))throw new h("INVALID_INPUT",`links.getStats: '${e}' is not a valid shortcode`);let t=await this.http.get(`/sdk/links/${encodeURIComponent(e)}/stats`);if(!t.success||!t.data)throw he(t.error?.code,t.error?.message??"links.getStats failed");return t.data}};var Z=class{constructor(e){this.http=e}async reportInstall(e={}){let t=await this.http.post("/attribution/install",e);if(!t.success||!t.data)throw new Error(t.error?.message??"attribution.reportInstall failed");return t.data}async checkDeferred(e={}){let t=await this.http.post("/attribution/deferred",e);if(!t.success)throw new Error(t.error?.message??"attribution.checkDeferred failed");return t.data??null}};var ee=class{constructor(e){this.http=e}async createTicket(e){let t=await this.http.post("/support/tickets",e);if(!t.success||!t.data)throw new Error(t.error?.message??"support.createTicket failed");return t.data}async submitCsat(e){let t=await this.http.post("/support/csat",e);if(!t.success||!t.data)throw new Error(t.error?.message??"support.submitCsat failed");return t.data}};var te=class{constructor(e){this.http=e}async search(e){if(!e.query||e.query.trim().length===0)return[];let t=new URLSearchParams({query:e.query});e.limit&&t.set("limit",String(e.limit));let r=await this.http.get(`/kb/search?${t.toString()}`);if(!r.success||!Array.isArray(r.data))throw new Error(r.error?.message??"kb.search failed");return r.data}};var se=class se{constructor(e){this.autoTrackCleanup=null;this.http=new Y(e),this.storage=new Q,this.consent=new F(e.defaultConsent===!0,this.http),this.analytics=new N(e,this.http,this.storage,this.consent),this.flags=new x(e,this.http,this.storage),this.surveys=new O(e,this.http,this.storage),this.messages=new M(e,this.http,this.storage),this.chatbot=new H(e,this.http,this.storage),this.auth=new V(e,this.http,this.storage,this.analytics,this.flags,this.chatbot),this.webPush=new W(this.http),this.audiences=new $(this.http),this.links=new B(this.http),this.attribution=new Z(this.http),this.support=new ee(this.http),this.kb=new te(this.http),e.autoTrack!==!1&&(this.autoTrackCleanup=Pe(this.analytics,e))}static init(e){return ke(e.apiKey),new se(e)}static sanitizeHtml(e){return J(e)}static safeUrl(e){return S(e)}track(e,t){this.analytics.track(e,t)}page(e){this.analytics.page(e)}identify(e,t,r){this.analytics.identify(e,t,r),this.flags.setContext({userId:e})}reset(){this.analytics.reset(),this.flags.reset(),this.chatbot.newSession()}async flush(){await this.analytics.flush()}destroy(){this.autoTrackCleanup&&(this.autoTrackCleanup(),this.autoTrackCleanup=null),this.analytics.destroy(),this.flags.destroy(),this.messages.destroy()}};se.version=E;var re=se,wt=re;0&&(module.exports={AlreadyIdentifiedError,Analytics,Audiences,Auth,AuthError,Chatbot,Consent,CredentialInUseError,EmailAlreadyTakenError,FeatureFlags,InAppMessaging,LinkError,Links,SendoraCloud,Surveys,ValidationError,WebPush,extractShortcodeFromUrl,safeUrl,sanitizeHtml});
|
package/dist/index.d.cts
CHANGED
|
@@ -127,6 +127,16 @@ interface ApiResponse<T> {
|
|
|
127
127
|
error?: {
|
|
128
128
|
code: string;
|
|
129
129
|
message: string;
|
|
130
|
+
/**
|
|
131
|
+
* HTTP status of the failing response, attached by the client. Absent
|
|
132
|
+
* when there was no response at all (offline / DNS / CORS / timeout).
|
|
133
|
+
*/
|
|
134
|
+
status?: number;
|
|
135
|
+
/**
|
|
136
|
+
* Server-supplied hints — `retryAfterSeconds` on a 429 or an account
|
|
137
|
+
* lockout, `fieldErrors` on a validation failure.
|
|
138
|
+
*/
|
|
139
|
+
details?: Record<string, unknown>;
|
|
130
140
|
};
|
|
131
141
|
}
|
|
132
142
|
|
|
@@ -381,9 +391,16 @@ declare class Chatbot {
|
|
|
381
391
|
* otherwise creates a fresh account.
|
|
382
392
|
* signIn(email, password) — log in to an existing account.
|
|
383
393
|
*
|
|
384
|
-
* Identity ordering —
|
|
385
|
-
*
|
|
386
|
-
*
|
|
394
|
+
* Identity ordering — a failed auth attempt leaves the caller exactly
|
|
395
|
+
* as it found them. Every credentialed path captures the anonymous
|
|
396
|
+
* refresh token into a local, makes the call, validates the response,
|
|
397
|
+
* and only THEN clears the outgoing identity and persists the new one.
|
|
398
|
+
* The stored refresh token is the only durable handle on an anonymous
|
|
399
|
+
* account, so a clear that isn't followed by a successful persist
|
|
400
|
+
* strands it permanently. (Cost of the ordering: a track() fired during
|
|
401
|
+
* the round-trip attaches to the prior identity — correct, the user
|
|
402
|
+
* isn't identified until the response says so, and the backend
|
|
403
|
+
* reattributes those events via the device takeover.) All public auth
|
|
387
404
|
* operations are serialized through a single-flight chain so a
|
|
388
405
|
* double-submit can't mint two anonymous users or interleave a
|
|
389
406
|
* signIn + signOut.
|
|
@@ -445,6 +462,41 @@ interface DeletionCancelledEvent {
|
|
|
445
462
|
at: Date;
|
|
446
463
|
}
|
|
447
464
|
type DeletionCancelledListener = (evt: DeletionCancelledEvent) => void;
|
|
465
|
+
/**
|
|
466
|
+
* Why the local session ended (3.12.0). Before this, a session that died in
|
|
467
|
+
* the background emitted NOTHING — an app could not tell a deliberate sign-out
|
|
468
|
+
* apart from an expired refresh token, and only discovered the difference when
|
|
469
|
+
* `getAccessToken()` started returning null.
|
|
470
|
+
*
|
|
471
|
+
* - `user` — the app called `signOut()`.
|
|
472
|
+
* - `session_expired` — the server rejected the stored refresh token (revoked,
|
|
473
|
+
* expired, rotated away). Transient network failures and
|
|
474
|
+
* rate limits do NOT produce this.
|
|
475
|
+
* - `account_deleted` — a self-service account deletion succeeded (the native
|
|
476
|
+
* SDKs' `deleteAccount()`; reserved here for parity).
|
|
477
|
+
*/
|
|
478
|
+
type AuthSignedOutReason = "user" | "session_expired" | "account_deleted";
|
|
479
|
+
/**
|
|
480
|
+
* A single auth-state transition (3.12.0). Subscribe with
|
|
481
|
+
* `onAuthStateChanged` — one stream covering everything that used to need
|
|
482
|
+
* separate listeners (or had none at all).
|
|
483
|
+
*/
|
|
484
|
+
type AuthStateChange = {
|
|
485
|
+
event: "signed_in";
|
|
486
|
+
user: AuthUser;
|
|
487
|
+
} | {
|
|
488
|
+
event: "signed_out";
|
|
489
|
+
user: null;
|
|
490
|
+
reason: AuthSignedOutReason;
|
|
491
|
+
} | {
|
|
492
|
+
event: "device_takeover";
|
|
493
|
+
user: AuthUser | null;
|
|
494
|
+
retiredAnonUserId: string;
|
|
495
|
+
} | {
|
|
496
|
+
event: "deletion_cancelled";
|
|
497
|
+
user: AuthUser | null;
|
|
498
|
+
};
|
|
499
|
+
type AuthStateListener = (change: AuthStateChange) => void;
|
|
448
500
|
declare class EmailAlreadyTakenError extends Error {
|
|
449
501
|
constructor(message?: string);
|
|
450
502
|
}
|
|
@@ -462,11 +514,79 @@ declare class AlreadyIdentifiedError extends Error {
|
|
|
462
514
|
* account. Sendora never auto-merges two real accounts.
|
|
463
515
|
*/
|
|
464
516
|
declare class CredentialInUseError extends Error {
|
|
465
|
-
|
|
517
|
+
/** Always `"CREDENTIAL_IN_USE"`. */
|
|
518
|
+
readonly code = "CREDENTIAL_IN_USE";
|
|
519
|
+
/**
|
|
520
|
+
* Always `"credential_in_use"`, so this can be handled in the same
|
|
521
|
+
* `switch (err.kind)` as every other auth failure. Deliberately NOT an
|
|
522
|
+
* `AuthError` subclass — widening `instanceof AuthError` would re-route an
|
|
523
|
+
* upgrading app that checks the base class first.
|
|
524
|
+
*/
|
|
525
|
+
readonly kind: "credential_in_use";
|
|
526
|
+
/** Always false — a different account owns it; retrying changes nothing. */
|
|
527
|
+
readonly retryable = false;
|
|
528
|
+
/** The provider whose credential collided, when the server named it. */
|
|
529
|
+
readonly provider?: string;
|
|
530
|
+
/**
|
|
531
|
+
* `"identity"` = that provider identity is already linked to another
|
|
532
|
+
* account; `"email"` = the provider's verified email belongs to one.
|
|
533
|
+
*/
|
|
534
|
+
readonly collision?: "identity" | "email";
|
|
535
|
+
readonly status?: number;
|
|
536
|
+
constructor(message?: string, opts?: {
|
|
537
|
+
provider?: string;
|
|
538
|
+
collision?: "identity" | "email";
|
|
539
|
+
status?: number;
|
|
540
|
+
});
|
|
466
541
|
}
|
|
542
|
+
/**
|
|
543
|
+
* What a sign-in should do when the credential you present already belongs to
|
|
544
|
+
* an account (3.14.0).
|
|
545
|
+
*
|
|
546
|
+
* - `"adopt"` (default, and what every earlier release did unconditionally):
|
|
547
|
+
* sign in to that account, retiring this browser's anonymous account if it
|
|
548
|
+
* had one (its row is deleted server-side).
|
|
549
|
+
* - `"reject"`: throw `CredentialInUseError` and change nothing at all.
|
|
550
|
+
*
|
|
551
|
+
* Mirrors Firebase `linkWithCredential` (`auth/credential-already-in-use`) and
|
|
552
|
+
* Supabase `linkIdentity` (`identity_already_exists`), both of which fail
|
|
553
|
+
* rather than adopt. `"reject"` blocks the switch; it does not merge.
|
|
554
|
+
*/
|
|
555
|
+
type CredentialCollisionPolicy = "adopt" | "reject";
|
|
556
|
+
/**
|
|
557
|
+
* Closed set of auth failure categories (3.12.0). Branch on `kind` instead of
|
|
558
|
+
* string-matching the ~20 raw `code`s the backend and transport can produce.
|
|
559
|
+
*
|
|
560
|
+
* Pairs with the hard guarantee this release introduces: **a failed auth
|
|
561
|
+
* attempt never changes the local session.** An offline sign-in, a mistyped
|
|
562
|
+
* OTP, a dismissed passkey sheet and a 429 all leave `getCurrentUser()` and the
|
|
563
|
+
* stored refresh token exactly as they were.
|
|
564
|
+
*/
|
|
565
|
+
type AuthErrorKind = "network" | "server" | "rate_limited" | "invalid_credential" | "account_locked" | "credential_in_use" | "already_identified" | "cancelled" | "config" | "unknown";
|
|
467
566
|
declare class AuthError extends Error {
|
|
468
567
|
code: string;
|
|
469
|
-
|
|
568
|
+
/**
|
|
569
|
+
* Category of the failure — the field to branch on. See `AuthErrorKind`.
|
|
570
|
+
* Derived from `code` (+ HTTP status when known); never affects `code`,
|
|
571
|
+
* which stays exactly what shipped before 3.12.0.
|
|
572
|
+
*/
|
|
573
|
+
readonly kind: AuthErrorKind;
|
|
574
|
+
/** True when retrying the SAME input can plausibly succeed later. */
|
|
575
|
+
readonly retryable: boolean;
|
|
576
|
+
/**
|
|
577
|
+
* Seconds to wait before retrying, when the server told us (429 backoff, or
|
|
578
|
+
* an ACCOUNT_LOCKED cool-off). Absent means "no server-provided hint" — for
|
|
579
|
+
* `account_locked` specifically, absent means the lock does NOT expire on
|
|
580
|
+
* its own and needs support.
|
|
581
|
+
*/
|
|
582
|
+
readonly retryAfterSeconds?: number;
|
|
583
|
+
/** HTTP status when the failure came from a response (absent when offline). */
|
|
584
|
+
readonly status?: number;
|
|
585
|
+
constructor(code: string, message: string, opts?: {
|
|
586
|
+
status?: number;
|
|
587
|
+
retryAfterSeconds?: number;
|
|
588
|
+
kind?: AuthErrorKind;
|
|
589
|
+
});
|
|
470
590
|
}
|
|
471
591
|
/** One credential linked to an account (ADR-030 read side, s58.270). */
|
|
472
592
|
type LinkedIdentity = {
|
|
@@ -508,6 +628,23 @@ declare class Auth {
|
|
|
508
628
|
private lastTakeover;
|
|
509
629
|
private deletionCancelledListeners;
|
|
510
630
|
private lastDeletionCancelled;
|
|
631
|
+
private authStateListeners;
|
|
632
|
+
/**
|
|
633
|
+
* Anon refresh token captured at `signIn()` time, stashed keyed to the
|
|
634
|
+
* issued `mfaChallengeToken` so the later `challengeMfa()` can forward it
|
|
635
|
+
* for device-takeover (s58.111). The MFA branch returns without persisting,
|
|
636
|
+
* so the anon identity has to survive the whole challenge round-trip —
|
|
637
|
+
* clearing it at signIn() time both orphaned the anon row (two user_ids on
|
|
638
|
+
* one device, duplicate pushes) and, for an abandoned challenge, left the
|
|
639
|
+
* account with no local handle at all.
|
|
640
|
+
*/
|
|
641
|
+
private pendingAnonTakeover;
|
|
642
|
+
/**
|
|
643
|
+
* True once the constructor has finished restoring from storage.
|
|
644
|
+
* Distinguishes "no session restored YET" from "genuinely signed out" for
|
|
645
|
+
* `onAuthStateChanged`'s replay-on-subscribe.
|
|
646
|
+
*/
|
|
647
|
+
private hydrated;
|
|
511
648
|
constructor(_config: SendoraConfig, http: HttpClient, storage: Storage, analytics: Analytics, flags: FeatureFlags, chatbot: Chatbot);
|
|
512
649
|
/** Returns the cached current user or null if signed out. */
|
|
513
650
|
getCurrentUser(): AuthUser | null;
|
|
@@ -531,6 +668,30 @@ declare class Auth {
|
|
|
531
668
|
* their handler missed.
|
|
532
669
|
*/
|
|
533
670
|
getLastDeviceTakeover(): DeviceTakeoverEvent | null;
|
|
671
|
+
/**
|
|
672
|
+
* Subscribe to every auth-state transition (3.12.0) — the single stream that
|
|
673
|
+
* answers "what happened to my session?". Firebase `onAuthStateChanged` /
|
|
674
|
+
* Supabase `onAuthStateChange` parity.
|
|
675
|
+
*
|
|
676
|
+
* The reason it exists: a session that dies in the BACKGROUND (the server
|
|
677
|
+
* rejected the stored refresh token) used to emit no signal whatsoever, so
|
|
678
|
+
* an app could not distinguish it from a deliberate sign-out and only
|
|
679
|
+
* noticed when `getAccessToken()` began returning null. That transition is
|
|
680
|
+
* now `{ event: "signed_out", reason: "session_expired" }`.
|
|
681
|
+
*
|
|
682
|
+
* Fires the CURRENT state immediately on subscribe when a session was
|
|
683
|
+
* restored from storage (`signed_in`), so a subscriber never misses the
|
|
684
|
+
* state it started in. Nothing is emitted for a signed-out cold start.
|
|
685
|
+
*
|
|
686
|
+
* A failed sign-in emits NOTHING: the session is unchanged, so there is no
|
|
687
|
+
* transition to report — the rejected promise is the whole story.
|
|
688
|
+
*
|
|
689
|
+
* Listeners are best-effort; a throwing listener is swallowed and never
|
|
690
|
+
* affects auth state. Returns an unsubscribe function.
|
|
691
|
+
*/
|
|
692
|
+
onAuthStateChanged(listener: AuthStateListener): () => void;
|
|
693
|
+
/** Fan a state change out to subscribers. Never throws. */
|
|
694
|
+
private emitAuthState;
|
|
534
695
|
/**
|
|
535
696
|
* Internal — called from every identified-signin path with the
|
|
536
697
|
* `retiredAnonUserId` parsed off the backend response (or off
|
|
@@ -591,9 +752,32 @@ declare class Auth {
|
|
|
591
752
|
getAccessToken(): Promise<string | null>;
|
|
592
753
|
/** Sync read — returns whatever's cached even if expired. Prefer the async variant. */
|
|
593
754
|
getAccessTokenSync(): string | null;
|
|
755
|
+
/**
|
|
756
|
+
* Mint (or return) an anonymous session.
|
|
757
|
+
*
|
|
758
|
+
* Reuses the existing anonymous user when there already is one — the same
|
|
759
|
+
* short-circuit Firebase's `signInAnonymously` performs. Without it, an app
|
|
760
|
+
* that calls this defensively on every page load (the most natural thing to
|
|
761
|
+
* write) mints a fresh `user_id` each time, and `persist()` overwrites the
|
|
762
|
+
* stored refresh token — the previous anonymous account's only durable
|
|
763
|
+
* handle — with no takeover, no webhook and no state event. That is the same
|
|
764
|
+
* silent progress loss as a failed sign-in wiping the session, except it
|
|
765
|
+
* happens on a *healthy* network.
|
|
766
|
+
*
|
|
767
|
+
* The guard is `cached user` AND `refresh token in storage`, both read INSIDE
|
|
768
|
+
* the serialized op: the refresh token is what actually keeps the account
|
|
769
|
+
* reachable (a cached user without one must still mint), and reading after
|
|
770
|
+
* the chain has drained is what collapses concurrent calls onto a single
|
|
771
|
+
* mint — the second call runs only once the first has persisted, so it takes
|
|
772
|
+
* the reuse branch instead of minting a sibling.
|
|
773
|
+
*
|
|
774
|
+
* Pass `forceNew: true` to deliberately abandon the current anonymous
|
|
775
|
+
* session and mint a separate one.
|
|
776
|
+
*/
|
|
594
777
|
signInAnonymously(opts?: {
|
|
595
778
|
name?: string;
|
|
596
779
|
metadata?: Record<string, unknown>;
|
|
780
|
+
forceNew?: boolean;
|
|
597
781
|
}): Promise<AuthUser>;
|
|
598
782
|
/**
|
|
599
783
|
* Create an email/password account. Upgrade-first flow (matches
|
|
@@ -661,6 +845,13 @@ declare class Auth {
|
|
|
661
845
|
* owned → falls back to takeover/merge).
|
|
662
846
|
*/
|
|
663
847
|
link?: boolean;
|
|
848
|
+
/**
|
|
849
|
+
* What to do if this social identity (or its verified email) already
|
|
850
|
+
* belongs to an account. Default `"adopt"` = sign in to it, retiring this
|
|
851
|
+
* browser's anonymous account. `"reject"` throws `CredentialInUseError`
|
|
852
|
+
* and leaves everything untouched. See {@link CredentialCollisionPolicy}.
|
|
853
|
+
*/
|
|
854
|
+
onCredentialInUse?: CredentialCollisionPolicy;
|
|
664
855
|
}): Promise<AuthUser>;
|
|
665
856
|
/** Convenience: Google authorization-code login. `link` = ADR-025 keep-the-sub on an anon upgrade. */
|
|
666
857
|
signInWithGoogle(code: string, redirectUri: string, link?: boolean): Promise<AuthUser>;
|
|
@@ -910,12 +1101,13 @@ declare class Auth {
|
|
|
910
1101
|
* whether to surface re-auth or proceed anonymously.
|
|
911
1102
|
*
|
|
912
1103
|
* s58.46 — on a dead-token signal (INVALID_REFRESH_TOKEN /
|
|
913
|
-
* UNAUTHORIZED / HTTP_401
|
|
914
|
-
*
|
|
1104
|
+
* UNAUTHORIZED / HTTP_401) we WIPE the stored refresh token.
|
|
1105
|
+
* Pre-s58.46 we returned null but kept the dead token in
|
|
915
1106
|
* storage, so the next caller re-tried the same token forever
|
|
916
1107
|
* (Pulse News iOS hit /refresh ~1×/s for hours). The wipe forces
|
|
917
1108
|
* the next op to fall through to /anonymous or surface the signed-
|
|
918
|
-
* out state to the host app instead of looping.
|
|
1109
|
+
* out state to the host app instead of looping. A rate limit is NOT
|
|
1110
|
+
* such a signal — see `isDeadRefreshError`.
|
|
919
1111
|
*/
|
|
920
1112
|
private refreshAccessToken;
|
|
921
1113
|
/**
|
|
@@ -950,6 +1142,15 @@ declare class Auth {
|
|
|
950
1142
|
private startProactiveRefreshCron;
|
|
951
1143
|
private stopProactiveRefreshCron;
|
|
952
1144
|
private persist;
|
|
1145
|
+
/**
|
|
1146
|
+
* Clear the outgoing identity immediately before a CONFIRMED `persist()`.
|
|
1147
|
+
* Flushes first so the prior identity's queued events reach the backend
|
|
1148
|
+
* before `dropQueue()` discards them. No-op when already signed out.
|
|
1149
|
+
*
|
|
1150
|
+
* `"replaced"` suppresses the signed_out emission — subscribers see one
|
|
1151
|
+
* `signed_in`, not a spurious logout/login pair.
|
|
1152
|
+
*/
|
|
1153
|
+
private replaceLocalIdentity;
|
|
953
1154
|
/**
|
|
954
1155
|
* Wipe everything tied to the current user identity. Anonymous-id
|
|
955
1156
|
* is regenerated by analytics.reset() so the next page-view doesn't
|
|
@@ -957,7 +1158,26 @@ declare class Auth {
|
|
|
957
1158
|
* so cross-account chat history can't bleed on a shared device.
|
|
958
1159
|
* Pending analytics events are dropped — they're tied to the prior
|
|
959
1160
|
* identity and shouldn't surface under the next.
|
|
1161
|
+
*
|
|
1162
|
+
* `reason` drives the `onAuthStateChanged` emission: a real sign-out reason
|
|
1163
|
+
* emits `signed_out`, while `"replaced"` (the internal pre-`persist()` clear
|
|
1164
|
+
* on a SUCCESSFUL sign-in) emits nothing.
|
|
1165
|
+
*
|
|
1166
|
+
* ⚠ Never call this before a fallible network request. A failed sign-in must
|
|
1167
|
+
* leave the caller exactly as it found them; for an anonymous session the
|
|
1168
|
+
* refresh token dropped here is the ONLY durable handle on the account, so a
|
|
1169
|
+
* pre-call wipe that is not followed by a successful `persist()` orphans the
|
|
1170
|
+
* account permanently (offline: deterministically). Capture the anon refresh
|
|
1171
|
+
* into a local, make the call, and wipe only after the response validates.
|
|
1172
|
+
*/
|
|
1173
|
+
/**
|
|
1174
|
+
* True when `sent` is still the refresh token in storage — i.e. nothing
|
|
1175
|
+
* replaced or cleared the session while a request carrying it was in
|
|
1176
|
+
* flight. Guards both directions of the refresh race: installing a rotated
|
|
1177
|
+
* token over a session that has since been signed out or replaced, and
|
|
1178
|
+
* wiping a fresh session because an OLD token was rejected.
|
|
960
1179
|
*/
|
|
1180
|
+
private tokenStillCurrent;
|
|
961
1181
|
private wipeLocalIdentity;
|
|
962
1182
|
}
|
|
963
1183
|
|
|
@@ -1437,4 +1657,4 @@ declare class SendoraCloud {
|
|
|
1437
1657
|
destroy(): void;
|
|
1438
1658
|
}
|
|
1439
1659
|
|
|
1440
|
-
export { AlreadyIdentifiedError, Analytics, type Audience, type AudienceRule, type AudienceRuleGroup, Audiences, Auth, AuthError, type AuthTokens, type AuthUser, Chatbot, Consent, type CreateAudienceInput, CredentialInUseError, EmailAlreadyTakenError, FeatureFlags, type FlagValue, type IdentifyOptions, type InAppMessage, InAppMessaging, type LinkCreateInput, type LinkCreateResult, type LinkData, LinkError, type LinkErrorCode, type LinkStats, type LinkedIdentitiesResponse, type LinkedIdentity, Links, SendoraCloud, type SendoraConfig, type SurveyConfig, type SurveyQuestion, Surveys, type TrackEvent, type UpdateAudienceInput, type UserTraits, ValidationError, WebPush, SendoraCloud as default, extractShortcodeFromUrl, safeUrl, sanitizeHtml };
|
|
1660
|
+
export { AlreadyIdentifiedError, Analytics, type Audience, type AudienceRule, type AudienceRuleGroup, Audiences, Auth, AuthError, type AuthErrorKind, type AuthSignedOutReason, type AuthStateChange, type AuthTokens, type AuthUser, Chatbot, Consent, type CreateAudienceInput, type CredentialCollisionPolicy, CredentialInUseError, type DeletionCancelledEvent, type DeviceTakeoverEvent, EmailAlreadyTakenError, FeatureFlags, type FlagValue, type IdentifyOptions, type InAppMessage, InAppMessaging, type LinkCreateInput, type LinkCreateResult, type LinkData, LinkError, type LinkErrorCode, type LinkStats, type LinkedIdentitiesResponse, type LinkedIdentity, Links, SendoraCloud, type SendoraConfig, type SurveyConfig, type SurveyQuestion, Surveys, type TrackEvent, type UpdateAudienceInput, type UserTraits, ValidationError, WebPush, SendoraCloud as default, extractShortcodeFromUrl, safeUrl, sanitizeHtml };
|
package/dist/index.d.ts
CHANGED
|
@@ -127,6 +127,16 @@ interface ApiResponse<T> {
|
|
|
127
127
|
error?: {
|
|
128
128
|
code: string;
|
|
129
129
|
message: string;
|
|
130
|
+
/**
|
|
131
|
+
* HTTP status of the failing response, attached by the client. Absent
|
|
132
|
+
* when there was no response at all (offline / DNS / CORS / timeout).
|
|
133
|
+
*/
|
|
134
|
+
status?: number;
|
|
135
|
+
/**
|
|
136
|
+
* Server-supplied hints — `retryAfterSeconds` on a 429 or an account
|
|
137
|
+
* lockout, `fieldErrors` on a validation failure.
|
|
138
|
+
*/
|
|
139
|
+
details?: Record<string, unknown>;
|
|
130
140
|
};
|
|
131
141
|
}
|
|
132
142
|
|
|
@@ -381,9 +391,16 @@ declare class Chatbot {
|
|
|
381
391
|
* otherwise creates a fresh account.
|
|
382
392
|
* signIn(email, password) — log in to an existing account.
|
|
383
393
|
*
|
|
384
|
-
* Identity ordering —
|
|
385
|
-
*
|
|
386
|
-
*
|
|
394
|
+
* Identity ordering — a failed auth attempt leaves the caller exactly
|
|
395
|
+
* as it found them. Every credentialed path captures the anonymous
|
|
396
|
+
* refresh token into a local, makes the call, validates the response,
|
|
397
|
+
* and only THEN clears the outgoing identity and persists the new one.
|
|
398
|
+
* The stored refresh token is the only durable handle on an anonymous
|
|
399
|
+
* account, so a clear that isn't followed by a successful persist
|
|
400
|
+
* strands it permanently. (Cost of the ordering: a track() fired during
|
|
401
|
+
* the round-trip attaches to the prior identity — correct, the user
|
|
402
|
+
* isn't identified until the response says so, and the backend
|
|
403
|
+
* reattributes those events via the device takeover.) All public auth
|
|
387
404
|
* operations are serialized through a single-flight chain so a
|
|
388
405
|
* double-submit can't mint two anonymous users or interleave a
|
|
389
406
|
* signIn + signOut.
|
|
@@ -445,6 +462,41 @@ interface DeletionCancelledEvent {
|
|
|
445
462
|
at: Date;
|
|
446
463
|
}
|
|
447
464
|
type DeletionCancelledListener = (evt: DeletionCancelledEvent) => void;
|
|
465
|
+
/**
|
|
466
|
+
* Why the local session ended (3.12.0). Before this, a session that died in
|
|
467
|
+
* the background emitted NOTHING — an app could not tell a deliberate sign-out
|
|
468
|
+
* apart from an expired refresh token, and only discovered the difference when
|
|
469
|
+
* `getAccessToken()` started returning null.
|
|
470
|
+
*
|
|
471
|
+
* - `user` — the app called `signOut()`.
|
|
472
|
+
* - `session_expired` — the server rejected the stored refresh token (revoked,
|
|
473
|
+
* expired, rotated away). Transient network failures and
|
|
474
|
+
* rate limits do NOT produce this.
|
|
475
|
+
* - `account_deleted` — a self-service account deletion succeeded (the native
|
|
476
|
+
* SDKs' `deleteAccount()`; reserved here for parity).
|
|
477
|
+
*/
|
|
478
|
+
type AuthSignedOutReason = "user" | "session_expired" | "account_deleted";
|
|
479
|
+
/**
|
|
480
|
+
* A single auth-state transition (3.12.0). Subscribe with
|
|
481
|
+
* `onAuthStateChanged` — one stream covering everything that used to need
|
|
482
|
+
* separate listeners (or had none at all).
|
|
483
|
+
*/
|
|
484
|
+
type AuthStateChange = {
|
|
485
|
+
event: "signed_in";
|
|
486
|
+
user: AuthUser;
|
|
487
|
+
} | {
|
|
488
|
+
event: "signed_out";
|
|
489
|
+
user: null;
|
|
490
|
+
reason: AuthSignedOutReason;
|
|
491
|
+
} | {
|
|
492
|
+
event: "device_takeover";
|
|
493
|
+
user: AuthUser | null;
|
|
494
|
+
retiredAnonUserId: string;
|
|
495
|
+
} | {
|
|
496
|
+
event: "deletion_cancelled";
|
|
497
|
+
user: AuthUser | null;
|
|
498
|
+
};
|
|
499
|
+
type AuthStateListener = (change: AuthStateChange) => void;
|
|
448
500
|
declare class EmailAlreadyTakenError extends Error {
|
|
449
501
|
constructor(message?: string);
|
|
450
502
|
}
|
|
@@ -462,11 +514,79 @@ declare class AlreadyIdentifiedError extends Error {
|
|
|
462
514
|
* account. Sendora never auto-merges two real accounts.
|
|
463
515
|
*/
|
|
464
516
|
declare class CredentialInUseError extends Error {
|
|
465
|
-
|
|
517
|
+
/** Always `"CREDENTIAL_IN_USE"`. */
|
|
518
|
+
readonly code = "CREDENTIAL_IN_USE";
|
|
519
|
+
/**
|
|
520
|
+
* Always `"credential_in_use"`, so this can be handled in the same
|
|
521
|
+
* `switch (err.kind)` as every other auth failure. Deliberately NOT an
|
|
522
|
+
* `AuthError` subclass — widening `instanceof AuthError` would re-route an
|
|
523
|
+
* upgrading app that checks the base class first.
|
|
524
|
+
*/
|
|
525
|
+
readonly kind: "credential_in_use";
|
|
526
|
+
/** Always false — a different account owns it; retrying changes nothing. */
|
|
527
|
+
readonly retryable = false;
|
|
528
|
+
/** The provider whose credential collided, when the server named it. */
|
|
529
|
+
readonly provider?: string;
|
|
530
|
+
/**
|
|
531
|
+
* `"identity"` = that provider identity is already linked to another
|
|
532
|
+
* account; `"email"` = the provider's verified email belongs to one.
|
|
533
|
+
*/
|
|
534
|
+
readonly collision?: "identity" | "email";
|
|
535
|
+
readonly status?: number;
|
|
536
|
+
constructor(message?: string, opts?: {
|
|
537
|
+
provider?: string;
|
|
538
|
+
collision?: "identity" | "email";
|
|
539
|
+
status?: number;
|
|
540
|
+
});
|
|
466
541
|
}
|
|
542
|
+
/**
|
|
543
|
+
* What a sign-in should do when the credential you present already belongs to
|
|
544
|
+
* an account (3.14.0).
|
|
545
|
+
*
|
|
546
|
+
* - `"adopt"` (default, and what every earlier release did unconditionally):
|
|
547
|
+
* sign in to that account, retiring this browser's anonymous account if it
|
|
548
|
+
* had one (its row is deleted server-side).
|
|
549
|
+
* - `"reject"`: throw `CredentialInUseError` and change nothing at all.
|
|
550
|
+
*
|
|
551
|
+
* Mirrors Firebase `linkWithCredential` (`auth/credential-already-in-use`) and
|
|
552
|
+
* Supabase `linkIdentity` (`identity_already_exists`), both of which fail
|
|
553
|
+
* rather than adopt. `"reject"` blocks the switch; it does not merge.
|
|
554
|
+
*/
|
|
555
|
+
type CredentialCollisionPolicy = "adopt" | "reject";
|
|
556
|
+
/**
|
|
557
|
+
* Closed set of auth failure categories (3.12.0). Branch on `kind` instead of
|
|
558
|
+
* string-matching the ~20 raw `code`s the backend and transport can produce.
|
|
559
|
+
*
|
|
560
|
+
* Pairs with the hard guarantee this release introduces: **a failed auth
|
|
561
|
+
* attempt never changes the local session.** An offline sign-in, a mistyped
|
|
562
|
+
* OTP, a dismissed passkey sheet and a 429 all leave `getCurrentUser()` and the
|
|
563
|
+
* stored refresh token exactly as they were.
|
|
564
|
+
*/
|
|
565
|
+
type AuthErrorKind = "network" | "server" | "rate_limited" | "invalid_credential" | "account_locked" | "credential_in_use" | "already_identified" | "cancelled" | "config" | "unknown";
|
|
467
566
|
declare class AuthError extends Error {
|
|
468
567
|
code: string;
|
|
469
|
-
|
|
568
|
+
/**
|
|
569
|
+
* Category of the failure — the field to branch on. See `AuthErrorKind`.
|
|
570
|
+
* Derived from `code` (+ HTTP status when known); never affects `code`,
|
|
571
|
+
* which stays exactly what shipped before 3.12.0.
|
|
572
|
+
*/
|
|
573
|
+
readonly kind: AuthErrorKind;
|
|
574
|
+
/** True when retrying the SAME input can plausibly succeed later. */
|
|
575
|
+
readonly retryable: boolean;
|
|
576
|
+
/**
|
|
577
|
+
* Seconds to wait before retrying, when the server told us (429 backoff, or
|
|
578
|
+
* an ACCOUNT_LOCKED cool-off). Absent means "no server-provided hint" — for
|
|
579
|
+
* `account_locked` specifically, absent means the lock does NOT expire on
|
|
580
|
+
* its own and needs support.
|
|
581
|
+
*/
|
|
582
|
+
readonly retryAfterSeconds?: number;
|
|
583
|
+
/** HTTP status when the failure came from a response (absent when offline). */
|
|
584
|
+
readonly status?: number;
|
|
585
|
+
constructor(code: string, message: string, opts?: {
|
|
586
|
+
status?: number;
|
|
587
|
+
retryAfterSeconds?: number;
|
|
588
|
+
kind?: AuthErrorKind;
|
|
589
|
+
});
|
|
470
590
|
}
|
|
471
591
|
/** One credential linked to an account (ADR-030 read side, s58.270). */
|
|
472
592
|
type LinkedIdentity = {
|
|
@@ -508,6 +628,23 @@ declare class Auth {
|
|
|
508
628
|
private lastTakeover;
|
|
509
629
|
private deletionCancelledListeners;
|
|
510
630
|
private lastDeletionCancelled;
|
|
631
|
+
private authStateListeners;
|
|
632
|
+
/**
|
|
633
|
+
* Anon refresh token captured at `signIn()` time, stashed keyed to the
|
|
634
|
+
* issued `mfaChallengeToken` so the later `challengeMfa()` can forward it
|
|
635
|
+
* for device-takeover (s58.111). The MFA branch returns without persisting,
|
|
636
|
+
* so the anon identity has to survive the whole challenge round-trip —
|
|
637
|
+
* clearing it at signIn() time both orphaned the anon row (two user_ids on
|
|
638
|
+
* one device, duplicate pushes) and, for an abandoned challenge, left the
|
|
639
|
+
* account with no local handle at all.
|
|
640
|
+
*/
|
|
641
|
+
private pendingAnonTakeover;
|
|
642
|
+
/**
|
|
643
|
+
* True once the constructor has finished restoring from storage.
|
|
644
|
+
* Distinguishes "no session restored YET" from "genuinely signed out" for
|
|
645
|
+
* `onAuthStateChanged`'s replay-on-subscribe.
|
|
646
|
+
*/
|
|
647
|
+
private hydrated;
|
|
511
648
|
constructor(_config: SendoraConfig, http: HttpClient, storage: Storage, analytics: Analytics, flags: FeatureFlags, chatbot: Chatbot);
|
|
512
649
|
/** Returns the cached current user or null if signed out. */
|
|
513
650
|
getCurrentUser(): AuthUser | null;
|
|
@@ -531,6 +668,30 @@ declare class Auth {
|
|
|
531
668
|
* their handler missed.
|
|
532
669
|
*/
|
|
533
670
|
getLastDeviceTakeover(): DeviceTakeoverEvent | null;
|
|
671
|
+
/**
|
|
672
|
+
* Subscribe to every auth-state transition (3.12.0) — the single stream that
|
|
673
|
+
* answers "what happened to my session?". Firebase `onAuthStateChanged` /
|
|
674
|
+
* Supabase `onAuthStateChange` parity.
|
|
675
|
+
*
|
|
676
|
+
* The reason it exists: a session that dies in the BACKGROUND (the server
|
|
677
|
+
* rejected the stored refresh token) used to emit no signal whatsoever, so
|
|
678
|
+
* an app could not distinguish it from a deliberate sign-out and only
|
|
679
|
+
* noticed when `getAccessToken()` began returning null. That transition is
|
|
680
|
+
* now `{ event: "signed_out", reason: "session_expired" }`.
|
|
681
|
+
*
|
|
682
|
+
* Fires the CURRENT state immediately on subscribe when a session was
|
|
683
|
+
* restored from storage (`signed_in`), so a subscriber never misses the
|
|
684
|
+
* state it started in. Nothing is emitted for a signed-out cold start.
|
|
685
|
+
*
|
|
686
|
+
* A failed sign-in emits NOTHING: the session is unchanged, so there is no
|
|
687
|
+
* transition to report — the rejected promise is the whole story.
|
|
688
|
+
*
|
|
689
|
+
* Listeners are best-effort; a throwing listener is swallowed and never
|
|
690
|
+
* affects auth state. Returns an unsubscribe function.
|
|
691
|
+
*/
|
|
692
|
+
onAuthStateChanged(listener: AuthStateListener): () => void;
|
|
693
|
+
/** Fan a state change out to subscribers. Never throws. */
|
|
694
|
+
private emitAuthState;
|
|
534
695
|
/**
|
|
535
696
|
* Internal — called from every identified-signin path with the
|
|
536
697
|
* `retiredAnonUserId` parsed off the backend response (or off
|
|
@@ -591,9 +752,32 @@ declare class Auth {
|
|
|
591
752
|
getAccessToken(): Promise<string | null>;
|
|
592
753
|
/** Sync read — returns whatever's cached even if expired. Prefer the async variant. */
|
|
593
754
|
getAccessTokenSync(): string | null;
|
|
755
|
+
/**
|
|
756
|
+
* Mint (or return) an anonymous session.
|
|
757
|
+
*
|
|
758
|
+
* Reuses the existing anonymous user when there already is one — the same
|
|
759
|
+
* short-circuit Firebase's `signInAnonymously` performs. Without it, an app
|
|
760
|
+
* that calls this defensively on every page load (the most natural thing to
|
|
761
|
+
* write) mints a fresh `user_id` each time, and `persist()` overwrites the
|
|
762
|
+
* stored refresh token — the previous anonymous account's only durable
|
|
763
|
+
* handle — with no takeover, no webhook and no state event. That is the same
|
|
764
|
+
* silent progress loss as a failed sign-in wiping the session, except it
|
|
765
|
+
* happens on a *healthy* network.
|
|
766
|
+
*
|
|
767
|
+
* The guard is `cached user` AND `refresh token in storage`, both read INSIDE
|
|
768
|
+
* the serialized op: the refresh token is what actually keeps the account
|
|
769
|
+
* reachable (a cached user without one must still mint), and reading after
|
|
770
|
+
* the chain has drained is what collapses concurrent calls onto a single
|
|
771
|
+
* mint — the second call runs only once the first has persisted, so it takes
|
|
772
|
+
* the reuse branch instead of minting a sibling.
|
|
773
|
+
*
|
|
774
|
+
* Pass `forceNew: true` to deliberately abandon the current anonymous
|
|
775
|
+
* session and mint a separate one.
|
|
776
|
+
*/
|
|
594
777
|
signInAnonymously(opts?: {
|
|
595
778
|
name?: string;
|
|
596
779
|
metadata?: Record<string, unknown>;
|
|
780
|
+
forceNew?: boolean;
|
|
597
781
|
}): Promise<AuthUser>;
|
|
598
782
|
/**
|
|
599
783
|
* Create an email/password account. Upgrade-first flow (matches
|
|
@@ -661,6 +845,13 @@ declare class Auth {
|
|
|
661
845
|
* owned → falls back to takeover/merge).
|
|
662
846
|
*/
|
|
663
847
|
link?: boolean;
|
|
848
|
+
/**
|
|
849
|
+
* What to do if this social identity (or its verified email) already
|
|
850
|
+
* belongs to an account. Default `"adopt"` = sign in to it, retiring this
|
|
851
|
+
* browser's anonymous account. `"reject"` throws `CredentialInUseError`
|
|
852
|
+
* and leaves everything untouched. See {@link CredentialCollisionPolicy}.
|
|
853
|
+
*/
|
|
854
|
+
onCredentialInUse?: CredentialCollisionPolicy;
|
|
664
855
|
}): Promise<AuthUser>;
|
|
665
856
|
/** Convenience: Google authorization-code login. `link` = ADR-025 keep-the-sub on an anon upgrade. */
|
|
666
857
|
signInWithGoogle(code: string, redirectUri: string, link?: boolean): Promise<AuthUser>;
|
|
@@ -910,12 +1101,13 @@ declare class Auth {
|
|
|
910
1101
|
* whether to surface re-auth or proceed anonymously.
|
|
911
1102
|
*
|
|
912
1103
|
* s58.46 — on a dead-token signal (INVALID_REFRESH_TOKEN /
|
|
913
|
-
* UNAUTHORIZED / HTTP_401
|
|
914
|
-
*
|
|
1104
|
+
* UNAUTHORIZED / HTTP_401) we WIPE the stored refresh token.
|
|
1105
|
+
* Pre-s58.46 we returned null but kept the dead token in
|
|
915
1106
|
* storage, so the next caller re-tried the same token forever
|
|
916
1107
|
* (Pulse News iOS hit /refresh ~1×/s for hours). The wipe forces
|
|
917
1108
|
* the next op to fall through to /anonymous or surface the signed-
|
|
918
|
-
* out state to the host app instead of looping.
|
|
1109
|
+
* out state to the host app instead of looping. A rate limit is NOT
|
|
1110
|
+
* such a signal — see `isDeadRefreshError`.
|
|
919
1111
|
*/
|
|
920
1112
|
private refreshAccessToken;
|
|
921
1113
|
/**
|
|
@@ -950,6 +1142,15 @@ declare class Auth {
|
|
|
950
1142
|
private startProactiveRefreshCron;
|
|
951
1143
|
private stopProactiveRefreshCron;
|
|
952
1144
|
private persist;
|
|
1145
|
+
/**
|
|
1146
|
+
* Clear the outgoing identity immediately before a CONFIRMED `persist()`.
|
|
1147
|
+
* Flushes first so the prior identity's queued events reach the backend
|
|
1148
|
+
* before `dropQueue()` discards them. No-op when already signed out.
|
|
1149
|
+
*
|
|
1150
|
+
* `"replaced"` suppresses the signed_out emission — subscribers see one
|
|
1151
|
+
* `signed_in`, not a spurious logout/login pair.
|
|
1152
|
+
*/
|
|
1153
|
+
private replaceLocalIdentity;
|
|
953
1154
|
/**
|
|
954
1155
|
* Wipe everything tied to the current user identity. Anonymous-id
|
|
955
1156
|
* is regenerated by analytics.reset() so the next page-view doesn't
|
|
@@ -957,7 +1158,26 @@ declare class Auth {
|
|
|
957
1158
|
* so cross-account chat history can't bleed on a shared device.
|
|
958
1159
|
* Pending analytics events are dropped — they're tied to the prior
|
|
959
1160
|
* identity and shouldn't surface under the next.
|
|
1161
|
+
*
|
|
1162
|
+
* `reason` drives the `onAuthStateChanged` emission: a real sign-out reason
|
|
1163
|
+
* emits `signed_out`, while `"replaced"` (the internal pre-`persist()` clear
|
|
1164
|
+
* on a SUCCESSFUL sign-in) emits nothing.
|
|
1165
|
+
*
|
|
1166
|
+
* ⚠ Never call this before a fallible network request. A failed sign-in must
|
|
1167
|
+
* leave the caller exactly as it found them; for an anonymous session the
|
|
1168
|
+
* refresh token dropped here is the ONLY durable handle on the account, so a
|
|
1169
|
+
* pre-call wipe that is not followed by a successful `persist()` orphans the
|
|
1170
|
+
* account permanently (offline: deterministically). Capture the anon refresh
|
|
1171
|
+
* into a local, make the call, and wipe only after the response validates.
|
|
1172
|
+
*/
|
|
1173
|
+
/**
|
|
1174
|
+
* True when `sent` is still the refresh token in storage — i.e. nothing
|
|
1175
|
+
* replaced or cleared the session while a request carrying it was in
|
|
1176
|
+
* flight. Guards both directions of the refresh race: installing a rotated
|
|
1177
|
+
* token over a session that has since been signed out or replaced, and
|
|
1178
|
+
* wiping a fresh session because an OLD token was rejected.
|
|
960
1179
|
*/
|
|
1180
|
+
private tokenStillCurrent;
|
|
961
1181
|
private wipeLocalIdentity;
|
|
962
1182
|
}
|
|
963
1183
|
|
|
@@ -1437,4 +1657,4 @@ declare class SendoraCloud {
|
|
|
1437
1657
|
destroy(): void;
|
|
1438
1658
|
}
|
|
1439
1659
|
|
|
1440
|
-
export { AlreadyIdentifiedError, Analytics, type Audience, type AudienceRule, type AudienceRuleGroup, Audiences, Auth, AuthError, type AuthTokens, type AuthUser, Chatbot, Consent, type CreateAudienceInput, CredentialInUseError, EmailAlreadyTakenError, FeatureFlags, type FlagValue, type IdentifyOptions, type InAppMessage, InAppMessaging, type LinkCreateInput, type LinkCreateResult, type LinkData, LinkError, type LinkErrorCode, type LinkStats, type LinkedIdentitiesResponse, type LinkedIdentity, Links, SendoraCloud, type SendoraConfig, type SurveyConfig, type SurveyQuestion, Surveys, type TrackEvent, type UpdateAudienceInput, type UserTraits, ValidationError, WebPush, SendoraCloud as default, extractShortcodeFromUrl, safeUrl, sanitizeHtml };
|
|
1660
|
+
export { AlreadyIdentifiedError, Analytics, type Audience, type AudienceRule, type AudienceRuleGroup, Audiences, Auth, AuthError, type AuthErrorKind, type AuthSignedOutReason, type AuthStateChange, type AuthTokens, type AuthUser, Chatbot, Consent, type CreateAudienceInput, type CredentialCollisionPolicy, CredentialInUseError, type DeletionCancelledEvent, type DeviceTakeoverEvent, EmailAlreadyTakenError, FeatureFlags, type FlagValue, type IdentifyOptions, type InAppMessage, InAppMessaging, type LinkCreateInput, type LinkCreateResult, type LinkData, LinkError, type LinkErrorCode, type LinkStats, type LinkedIdentitiesResponse, type LinkedIdentity, Links, SendoraCloud, type SendoraConfig, type SurveyConfig, type SurveyQuestion, Surveys, type TrackEvent, type UpdateAudienceInput, type UserTraits, ValidationError, WebPush, SendoraCloud as default, extractShortcodeFromUrl, safeUrl, sanitizeHtml };
|
package/dist/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
var pe=/^[a-zA-Z][a-zA-Z0-9._:-]{0,127}$/;var _e=new Set(["__proto__","constructor","prototype"]),p=class extends Error{constructor(e){super(`[SendoraCloud] ${e}`),this.name="SendoraValidationError"}};function ge(s){if(typeof s!="string"||!pe.test(s))throw new p(`Invalid event name: must match ${pe}, got "${String(s)}".`)}function fe(s){if(s==null)return;if(typeof s!="object"||Array.isArray(s))throw new p("Properties must be a plain object, not an array or primitive.");let e=me(s);if(e>5)throw new p(`Properties nested too deeply (${e} levels, max 5).`);ye(s);let t;try{t=JSON.stringify(s).length}catch{throw new p("Properties contain a circular reference or unserializable value.")}if(t>32768)throw new p(`Properties too large (${t} bytes, max 32768).`)}function me(s,e=0){if(e>6||s==null||typeof s!="object")return e;let t=e;for(let r of Object.keys(s)){let n=me(s[r],e+1);n>t&&(t=n)}return t}function ye(s){if(!(s==null||typeof s!="object"))for(let e of Object.keys(s)){if(_e.has(e))throw new p(`Forbidden property key "${e}" (prototype pollution guard).`);ye(s[e])}}var Pe=/^pk_[A-Za-z0-9_-]{16,}$/;function we(s){if(!s||typeof s!="string")throw new p("apiKey is required.");if(s.startsWith("sk_")||s.startsWith("sendora_secret_"))throw new p("Secret keys must NEVER be used in browser SDK. Use a publishable key (pk_...).");if(!Pe.test(s))throw new p("Invalid publishable key format. Expected pk_... (17+ chars).")}function ve(s){let e=s.replace(/\/$/,"");if(!(e.startsWith("http://localhost")||e.startsWith("http://127.0.0.1"))&&!e.startsWith("https://"))throw new p("apiUrl must be https:// (only http://localhost is allowed for dev).")}var ke={name:"@sendoracloud/sdk-web",version:"3.11.0",description:"Sendora Cloud Web SDK \u2014 analytics, feature flags, surveys, in-app messaging, chatbot, auth, deep links (Branch / Firebase parity). Zero dependencies, TypeScript, SSR-safe.",type:"module",main:"./dist/index.cjs",module:"./dist/index.js",types:"./dist/index.d.ts",exports:{".":{types:"./dist/index.d.ts",import:"./dist/index.js",require:"./dist/index.cjs"}},files:["dist","README.md","LICENSE"],keywords:["sendora","analytics","feature-flags","surveys","in-app-messaging","chatbot","tracking","customer-engagement"],homepage:"https://sendoracloud.com/sdks",repository:{type:"git",url:"git+https://github.com/sendoracloud/sdk-web.git"},bugs:{url:"https://github.com/sendoracloud/sdk-web/issues"},author:"Sendora Cloud <hello@sendoracloud.com>",license:"Apache-2.0",publishConfig:{access:"public"},sideEffects:!1,scripts:{build:"tsup src/index.ts --format cjs,esm --dts --clean --minify",typecheck:"tsc --noEmit",prepublishOnly:"npm run build"},devDependencies:{typescript:"^5.4.0",tsup:"^8.0.0"}};var b=ke.version;var Le="https://api.sendoracloud.com",Ue=1e4,xe="@sendoracloud/sdk-web",z=class{constructor(e){let t=(e.apiUrl??Le).replace(/\/$/,"");ve(t),this.apiKey=e.apiKey,this.baseUrl=t,this.debug=!!e.debug}get(e,t){return this.request("GET",e,void 0,t)}post(e,t,r){return this.request("POST",e,t,r)}patch(e,t,r){return this.request("PATCH",e,t,r)}delete(e,t){return this.request("DELETE",e,void 0,t)}async request(e,t,r,n){let i=`${this.baseUrl}/api/v1${t}`,o=typeof AbortController<"u"?new AbortController:null,a=o?setTimeout(()=>o.abort(),Ue):null;try{let l=await fetch(i,{method:e,headers:{"Content-Type":"application/json","X-API-Key":this.apiKey,"X-Sendora-SDK-Name":xe,"X-Sendora-SDK-Version":b,...n??{}},body:r?JSON.stringify(r):void 0,signal:o?o.signal:void 0});a&&clearTimeout(a);let u;try{u=await l.json()}catch{u={success:!1,error:{code:"PARSE_ERROR",message:`HTTP ${l.status}`}}}if(this.debug&&console.debug(`[SendoraCloud] ${e} ${t} \u2192 ${l.status}`),!l.ok)if(t.startsWith("/auth-service/")&&!this.debug)console.warn(`[SendoraCloud] ${e} ${t} \u2192 HTTP ${l.status}`);else{let f=u?.error?.code??"HTTP_ERROR",m=u?.error?.message??`HTTP ${l.status}`,w=u?.error?.details?.fieldErrors,R=w?` (${Object.entries(w).map(([v,te])=>`${v}: ${(te??[]).join(", ")}`).join("; ")})`:"";console.warn(`[SendoraCloud] ${e} ${t} \u2192 HTTP ${l.status} \u2014 ${f}: ${m}${R}`)}return u}catch(l){a&&clearTimeout(a);let u=l instanceof Error&&l.name==="AbortError"?"timeout":"error";return console.warn(`[SendoraCloud] ${e} ${t} network ${u}`),{success:!1,error:{code:"NETWORK_ERROR",message:u}}}}};var j="sendora_",G=class{constructor(){this.memoryStore=new Map;this.useLocalStorage=this.checkLocalStorage()}get(e){let t=j+e;if(this.useLocalStorage)try{return localStorage.getItem(t)}catch{return this.memoryStore.get(t)??null}return this.memoryStore.get(t)??null}set(e,t){let r=j+e;if(this.useLocalStorage)try{localStorage.setItem(r,t);return}catch{}this.memoryStore.set(r,t)}remove(e){let t=j+e;if(this.useLocalStorage)try{localStorage.removeItem(t)}catch{}this.memoryStore.delete(t)}clearPrefix(e){let t=j+e;if(this.useLocalStorage)try{for(let r=localStorage.length-1;r>=0;r--){let n=localStorage.key(r);n&&n.startsWith(t)&&localStorage.removeItem(n)}}catch{}for(let r of Array.from(this.memoryStore.keys()))r.startsWith(t)&&this.memoryStore.delete(r)}checkLocalStorage(){try{if(typeof localStorage>"u")return!1;let e="__sendora_test__";return localStorage.setItem(e,"1"),localStorage.removeItem(e),!0}catch{return!1}}};function _(s=""){let e=new Uint8Array(16),t=typeof globalThis<"u"&&"crypto"in globalThis?globalThis.crypto:void 0;if(!t||typeof t.getRandomValues!="function")throw new Error("[SendoraCloud] crypto.getRandomValues unavailable \u2014 refusing to mint IDs without a CSPRNG");t.getRandomValues(e);let r="";for(let n of e)r+=n.toString(16).padStart(2,"0");return s+r}var Ae=500,ie=10,oe=6e4,L=class{constructor(e,t,r,n){this.queue=[];this.timer=null;this.userId=null;this.identityToken=null;this.consecutiveFailures=0;this.nextFlushAfter=0;this.breakerOpenAt=0;this.breakerWarned=!1;this.http=t,this.storage=r,this.consent=n,this.projectId=e.projectId,this.flushInterval=e.flushInterval??5e3,this.maxQueueSize=e.maxQueueSize??20,this.persistUserId=!!e.persistUserId,this.stripQueryParams=e.stripQueryParams!==!1,this.appVersion=e.appVersion,this.storage.get("schema_version")===null&&this.storage.set("schema_version","1");let i=this.storage.get("anonymous_id");if(i?this.anonymousId=i:(this.anonymousId=_("anon_"),this.storage.set("anonymous_id",this.anonymousId)),this.persistUserId){let o=this.storage.get("user_id");o&&(this.userId=o)}this.timer=setInterval(()=>this.flush(),this.flushInterval),typeof window<"u"&&window.addEventListener("beforeunload",()=>this.flush()),this.consent.subscribe(o=>{o&&this.flush()})}track(e,t){ge(e),t&&fe(t),this.queue.length>=Ae&&this.queue.shift(),this.queue.push({eventType:e,properties:t??{},userId:this.userId??void 0,anonymousId:this.anonymousId,timestamp:new Date().toISOString(),identityToken:this.identityToken??void 0}),this.queue.length>=this.maxQueueSize&&this.flush()}page(e){if(typeof window>"u")return;let t=this.stripQueryParams?window.location.origin+window.location.pathname:window.location.href;this.track("page.viewed",{url:t,path:window.location.pathname,title:document.title,referrer:document.referrer||void 0,...e})}identify(e,t,r){if(typeof e!="string"||e.length===0||e.length>256)throw new Error("[SendoraCloud] userId must be a 1-256 char string.");this.userId=e,this.identityToken=r?.identityToken??null,this.persistUserId&&this.storage.set("user_id",e),this.track("user.identified",{...t})}reset(){this.userId=null,this.identityToken=null,this.storage.remove("user_id"),this.anonymousId=_("anon_"),this.storage.set("anonymous_id",this.anonymousId),this.storage.clearPrefix("iam_shown_"),this.storage.clearPrefix("survey_shown_")}dropQueue(){this.queue=[]}async flush(){if(this.queue.length===0||!this.consent.isGranted()||Date.now()<this.nextFlushAfter)return;this.consecutiveFailures>ie&&Date.now()-this.breakerOpenAt>=oe&&(this.consecutiveFailures=0,this.breakerOpenAt=0,this.breakerWarned=!1);let e=this.queue.splice(0,100),t=this.getSessionId(),r=e.map(a=>({projectId:this.projectId,module:"custom",eventType:a.eventType,userId:a.userId,anonymousId:a.anonymousId,identityToken:a.identityToken,timestamp:a.timestamp??new Date().toISOString(),properties:a.properties??{},context:this.getContext(),...t?{sessionId:t}:{},consent:["analytics"]}));if((await this.http.post("/events/batch",{events:r})).success){this.consecutiveFailures=0,this.breakerOpenAt=0,this.breakerWarned=!1;return}if(this.consecutiveFailures++,this.consecutiveFailures>ie){this.breakerWarned||(this.breakerOpenAt=Date.now(),this.breakerWarned=!0,console.warn(`[SendoraCloud] Analytics circuit breaker open after ${ie} consecutive failed flushes \u2014 events will be dropped until a flush succeeds or ${oe}ms elapse.`));return}let i=Math.min(oe,1e3*2**this.consecutiveFailures);this.nextFlushAfter=Date.now()+i;let o=Math.max(0,Ae-this.queue.length);o>0&&this.queue.unshift(...e.slice(0,o))}destroy(){this.timer&&clearInterval(this.timer),this.timer=null,this.flush()}getSessionId(){try{return typeof sessionStorage>"u"?void 0:sessionStorage.getItem("sendora_session_id")||void 0}catch{return}}getContext(){return typeof window>"u"?{sdk:{name:"@sendoracloud/sdk-web",version:b}}:{page:{url:this.stripQueryParams?window.location.origin+window.location.pathname:window.location.href,path:window.location.pathname,title:document.title},referrer:document.referrer||void 0,locale:typeof navigator<"u"?navigator.language:void 0,device:this.buildDevice(),sdk:{name:"@sendoracloud/sdk-web",version:b}}}buildDevice(){if(typeof navigator>"u")return;let e=navigator.userAgent||"",r=navigator.userAgentData?.mobile??/Mobi|Android|iPhone|iPod/i.test(e);return{type:/iPad|Tablet|(?:Android(?!.*Mobile))/i.test(e)?"tablet":r?"mobile":"desktop",os:"Web",osVersion:"",model:"",appVersion:this.resolveAppVersion(),browser:Oe(e)}}resolveAppVersion(){if(this.appVersion)return this.appVersion;if(typeof document<"u"){let r=document.querySelector('meta[name="sendora:app-version"]')?.getAttribute("content");if(r)return r}let e=globalThis.__SENDORA_APP_VERSION__;return typeof e=="string"?e:""}};function Oe(s){let e=[["Edge",/Edg\/(\d+)/],["Opera",/OPR\/(\d+)/],["Firefox",/Firefox\/(\d+)/],["Samsung Internet",/SamsungBrowser\/(\d+)/],["Chrome",/Chrome\/(\d+)/],["Safari",/Version\/(\d+)[^ ]* Safari/]];for(let[t,r]of e){let n=r.exec(s);if(n)return`${t} ${n[1]}`}return"Unknown"}var U=class{constructor(e,t,r){this.cache=new Map;this.refreshInterval=null;this.http=t,this.storage=r;let n=this.storage.get("flags");if(n)try{let i=JSON.parse(n);if(Array.isArray(i))for(let o of i)ae(o)&&this.cache.set(o.key,o)}catch{}}setContext(e){this.userId=e.userId,this.entityId=e.entityId,this.environment=e.environment}async getFlag(e){if(typeof e!="string"||e.length===0||e.length>128)return null;let t=this.cache.get(e);if(t)return t.value;let r=await this.http.post("/feature-flags/evaluate",{key:e,userId:this.userId,entityId:this.entityId,environment:this.environment});return r.success&&r.data&&ae(r.data)?(this.cache.set(e,r.data),this.persistCache(),r.data.value):null}async isEnabled(e,t=!1){let r=await this.getFlag(e);return typeof r=="boolean"?r:t}async refreshAll(){let e=await this.http.post("/feature-flags/evaluate-all",{userId:this.userId,entityId:this.entityId,environment:this.environment});if(e.success&&Array.isArray(e.data)){this.cache.clear();for(let t of e.data)ae(t)&&this.cache.set(t.key,t);this.persistCache()}}startAutoRefresh(e=6e4){this.stopAutoRefresh(),this.refreshAll(),this.refreshInterval=setInterval(()=>this.refreshAll(),e)}stopAutoRefresh(){this.refreshInterval&&clearInterval(this.refreshInterval),this.refreshInterval=null}getAllFlags(){let e={};for(let[t,r]of this.cache)e[t]=r.value;return e}reset(){this.cache.clear(),this.storage.remove("flags"),this.userId=void 0,this.entityId=void 0,this.environment=void 0}destroy(){this.stopAutoRefresh()}persistCache(){this.storage.set("flags",JSON.stringify(Array.from(this.cache.values())))}};function ae(s){if(s==null||typeof s!="object")return!1;let e=s;return Object.prototype.hasOwnProperty.call(e,"__proto__")||Object.prototype.hasOwnProperty.call(e,"constructor")?!1:typeof e.key=="string"&&"value"in e}var Ne=32*1024,x=class{constructor(e,t,r){this.activeSurvey=null;this.http=t,this.storage=r}onDisplay(e){this.onSurveyDisplay=e}async showSurvey(e){if(typeof e!="string"||!/^[A-Za-z0-9_-]{6,64}$/.test(e))return null;let t=`survey_shown_${e}`;if(this.storage.get(t))return null;let r=await this.http.get(`/surveys/${encodeURIComponent(e)}/config`);return!r.success||!r.data||!Me(r.data)?null:(this.activeSurvey=r.data,this.storage.set(t,"1"),this.onSurveyDisplay&&this.onSurveyDisplay(r.data),r.data)}async submitResponse(e,t,r=!0){let n=0;try{n=JSON.stringify(t).length}catch{return!1}if(n>Ne)return!1;let i=await this.http.post("/surveys/responses",{surveyId:e,answers:t,completed:r});return i.success&&(this.activeSurvey=null),i.success}dismiss(){this.activeSurvey=null}getActiveSurvey(){return this.activeSurvey}};function Me(s){if(s==null||typeof s!="object")return!1;let e=s;return typeof e.id!="string"||typeof e.name!="string"||!Array.isArray(e.questions)?!1:e.questions.every(He)}function He(s){if(s==null||typeof s!="object")return!1;let e=s;return typeof e.id=="string"&&typeof e.text=="string"}function S(s){if(!(!s||typeof s!="string"))try{let e=typeof window<"u"?window.location.href:"https://local/",t=new URL(s,e);return t.protocol==="http:"||t.protocol==="https:"?t.href:void 0}catch{return}}var Fe=new Set(["b","i","u","em","strong","a","p","br","span","ul","ol","li","h1","h2","h3","h4"]),Ke={a:new Set(["href","target","rel"])};function ce(s){if(typeof DOMParser>"u")return"";let t=new DOMParser().parseFromString(`<div>${s}</div>`,"text/html").body.firstElementChild;return t?(Ie(t),t.innerHTML):""}function Ie(s){let e=Array.from(s.children);for(let t of e){let r=t.tagName.toLowerCase();if(!Fe.has(r)){let i=document.createTextNode(t.textContent??"");t.replaceWith(i);continue}let n=Ke[r]??new Set;for(let i of Array.from(t.attributes)){if(!n.has(i.name.toLowerCase())){t.removeAttribute(i.name);continue}if(i.name.toLowerCase()==="href"){let o=S(i.value);o?t.setAttribute("href",o):t.removeAttribute(i.name)}}r==="a"&&(t.setAttribute("rel","noopener noreferrer"),t.setAttribute("target","_blank")),Ie(t)}}var O=class{constructor(e,t,r){this.messages=[];this.pollInterval=null;this.http=t,this.storage=r}onDisplay(e){this.onMessageDisplay=e}async checkMessages(){let e=await this.http.get("/in-app-messages/active");if(!e.success||!Array.isArray(e.data))return null;this.messages=e.data.map(Ve);for(let t of this.messages){let r=`iam_shown_${t.id}`;if(!this.storage.get(r))return this.storage.set(r,"1"),this.onMessageDisplay&&this.onMessageDisplay(t),t}return null}async recordAction(e,t){await this.http.post("/in-app-messages/impressions",{messageId:e,action:t})}startPolling(e=3e5){this.stopPolling(),this.checkMessages(),this.pollInterval=setInterval(()=>this.checkMessages(),e)}stopPolling(){this.pollInterval&&clearInterval(this.pollInterval),this.pollInterval=null}getMessages(){return this.messages}destroy(){this.stopPolling()}};function Ve(s){return{...s,content:{...s.content,ctaUrl:S(s.content.ctaUrl),imageUrl:S(s.content.imageUrl)}}}var $e=4e3,N=class{constructor(e,t,r){this.chatbotId=null;this.messages=[];this.http=t,this.storage=r;let n=this.storage.get("chat_session_id");this.sessionId=n??_("chat_"),n||this.storage.set("chat_session_id",this.sessionId)}init(e){if(typeof e!="string"||!/^[A-Za-z0-9_-]{6,64}$/.test(e))throw new Error("[SendoraCloud] Invalid chatbotId.");this.chatbotId=e}onNewMessage(e){this.onMessage=e}async sendMessage(e,t){if(!this.chatbotId||typeof e!="string"||e.length===0||e.length>$e)return null;let r={role:"user",content:e,timestamp:new Date().toISOString()};this.messages.push(r);let n=await this.http.post("/chatbot/message",{chatbotId:this.chatbotId,sessionId:this.sessionId,message:e,userId:t});if(n.success&&n.data&&typeof n.data.reply=="string"){let i={role:"assistant",content:n.data.reply,timestamp:new Date().toISOString()};return this.messages.push(i),this.onMessage&&this.onMessage(i),i}return null}getMessages(){return[...this.messages]}newSession(){this.messages=[],this.sessionId=_("chat_"),this.storage.set("chat_session_id",this.sessionId)}getSessionId(){return this.sessionId}};var M=class{constructor(e,t){this.listeners=new Set;this.granted=e,this.http=t}isGranted(){return this.granted}grant(){this.granted||(this.granted=!0,this.emit())}revoke(){this.granted&&(this.granted=!1,this.emit())}subscribe(e){return this.listeners.add(e),()=>this.listeners.delete(e)}async record(e){if(!this.http)throw new Error("consent.record requires SDK init (HttpClient unavailable)");let t=await this.http.post("/consent/record",e);if(!t.success||!t.data)throw new Error(t.error?.message??"consent.record failed");return t.data}async requestDeletion(e){if(!this.http)throw new Error("consent.requestDeletion requires SDK init (HttpClient unavailable)");let t=await this.http.post("/consent/deletion-request",e);if(!t.success||!t.data)throw new Error(t.error?.message??"consent.requestDeletion failed");return t.data}emit(){for(let e of this.listeners)try{e(this.granted)}catch{}}};var T="auth_access_token",E="auth_access_expires",g="auth_refresh_token",P="auth_user",We=3e4,qe=/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;function y(s){if(!s||typeof s!="object")return!1;let e=s,t=e.user,r=e.tokens;return!(!t||typeof t.id!="string"||t.id.length===0||!r||typeof r.accessToken!="string"||r.accessToken.length===0||typeof r.refreshToken!="string"||r.refreshToken.length===0||typeof r.expiresIn!="number"||r.expiresIn<=0||t.isAnonymous!==void 0&&typeof t.isAnonymous!="boolean"||t.email!==void 0&&t.email!==null&&typeof t.email!="string"||t.emailVerified!==void 0&&typeof t.emailVerified!="boolean"||t.name!==void 0&&t.name!==null&&typeof t.name!="string"||t.signupMethod!==void 0&&t.signupMethod!==null&&typeof t.signupMethod!="string"||t.lastLoginMethod!==void 0&&t.lastLoginMethod!==null&&typeof t.lastLoginMethod!="string"||e.retiredAnonUserId!==void 0&&typeof e.retiredAnonUserId!="string")}var F=class extends Error{constructor(e="An account with this email already exists"){super(e),this.name="EmailAlreadyTakenError"}},Y=class extends Error{constructor(e="Already signed in with an identity; use link*() to add a credential, or sign out first"){super(e),this.name="AlreadyIdentifiedError"}},X=class extends Error{constructor(e="This credential is already linked to a different account"){super(e),this.name="CredentialInUseError"}},c=class extends Error{constructor(e,t){super(t),this.name="AuthError",this.code=e}},K=class{constructor(e,t,r,n,i,o){this.user=null;this.accessToken=null;this.accessExpiresAt=0;this.inflight=Promise.resolve();this.refreshInflight=null;this.refreshFailureCount=0;this.refreshCooldownUntil=0;this.visibilityResetUnsub=null;this.proactiveRefreshTimer=null;this.proactiveRefreshCleanup=null;this.takeoverListeners=new Set;this.lastTakeover=null;this.deletionCancelledListeners=new Set;this.lastDeletionCancelled=null;this.http=t,this.storage=r,this.analytics=n,this.flags=i,this.chatbot=o;let a=this.storage.get(P),l=this.storage.get(T),u=this.storage.get(g),I=this.storage.get(E);if(a&&(l||u))try{let f=JSON.parse(a);if(typeof f.id!="string"||f.id.length===0)throw new Error("invalid cache");this.user=f,l?(this.accessToken=l,this.accessExpiresAt=I?Number(I):0):(this.accessToken=null,this.accessExpiresAt=0),this.analytics.identify(this.user.id)}catch{this.storage.remove(P),this.storage.remove(T),this.storage.remove(E),this.storage.remove(g)}}getCurrentUser(){return this.user}onDeviceTakeover(e){return this.takeoverListeners.add(e),()=>{this.takeoverListeners.delete(e)}}getLastDeviceTakeover(){return this.lastTakeover}fireDeviceTakeover(e,t){if(!e||!qe.test(e))return;let r={retiredAnonUserId:e,identifiedUserId:t,at:new Date};this.lastTakeover=r;for(let n of this.takeoverListeners)try{n(r)}catch{}}onDeletionCancelled(e){return this.deletionCancelledListeners.add(e),()=>{this.deletionCancelledListeners.delete(e)}}getLastDeletionCancelled(){return this.lastDeletionCancelled}fireDeletionCancelled(e){let t={userId:e,at:new Date};this.lastDeletionCancelled=t;for(let r of this.deletionCancelledListeners)try{r(t)}catch{}}takeoverHint(){if(this.user?.isAnonymous)return this.storage.get(g)??void 0}async getAccessToken(){return this.accessToken&&this.accessExpiresAt&&Date.now()<this.accessExpiresAt-We?this.accessToken:this.storage.get(g)?this.refreshInflight?this.refreshInflight:Date.now()<this.refreshCooldownUntil?null:this.refreshAccessToken():null}getAccessTokenSync(){return this.accessToken}signInAnonymously(e){return this.serialize(async()=>{let t=await this.http.post("/auth-service/anonymous",{name:e?.name,metadata:e?.metadata});if(!t.success||!y(t.data))throw new c(t.error?.code??"AUTH_ERROR",t.error?.message??"Anonymous signup failed");return this.persist(t.data),t.data.user})}signUp(e,t,r){return this.serialize(async()=>{let n=this.storage.get(g);if(n){let o=await this.http.post("/auth-service/upgrade",{refreshToken:n,email:e,password:t,name:r?.name});if(o.success&&y(o.data))return this.persist(o.data),o.data.user;let a=o.error?.code??"",l=o.error?.status;if(a==="NOT_ANONYMOUS"||a==="FORBIDDEN_NON_ANONYMOUS")throw new Y(o.error?.message);if(a==="CONFLICT"||a==="EMAIL_ALREADY_TAKEN"||l===409)throw new F(o.error?.message);if(a==="INVALID_REFRESH"||a==="UNAUTHORIZED"||a==="REFRESH_EXPIRED")await this.analytics.flush(),this.wipeLocalIdentity();else throw new c(a||"AUTH_ERROR",o.error?.message??"Upgrade failed")}let i=await this.http.post("/auth-service/signup",{email:e,password:t,name:r?.name,metadata:r?.metadata});if(!i.success||!y(i.data)){let o=i.error?.code??"";throw o==="CONFLICT"?new F(i.error?.message):new c(o||"AUTH_ERROR",i.error?.message??"Signup failed")}return this.persist(i.data),i.data.user})}signIn(e,t){return this.serialize(async()=>{let r=this.user!==null,n=this.takeoverHint();r&&(await this.analytics.flush(),this.wipeLocalIdentity());let i=await this.http.post("/auth-service/login",{email:e,password:t,...n?{prevAnonRefreshToken:n}:{}});if(!i.success||!i.data)throw new c(i.error?.code??"AUTH_ERROR",i.error?.message??"Login failed");let o=i.data;if(o.mfaRequired===!0&&typeof o.mfaChallengeToken=="string"){let a=o.user;return{mfaRequired:!0,mfaChallengeToken:o.mfaChallengeToken,userId:a?.id??""}}if(!y(i.data))throw new c(i.error?.code??"AUTH_ERROR",i.error?.message??"Login failed");return this.persist(i.data),i.data.user})}loginSocial(e){return this.serialize(async()=>{let t=this.user!==null,r=this.takeoverHint();t&&(await this.analytics.flush(),this.wipeLocalIdentity());let{link:n,...i}=e,o=await this.http.post("/auth-service/login/social",{...i,...r?{prevAnonRefreshToken:r}:{},...n?{linkAnonymous:!0}:{}});if(!o.success||!y(o.data))throw new c(o.error?.code??"SOCIAL_LOGIN_FAILED",o.error?.message??"Social login failed");return this.persist(o.data),o.data.user})}signInWithGoogle(e,t,r){return this.loginSocial({provider:"google",code:e,redirectUri:t,link:r})}signInWithGitHub(e,t){return this.loginSocial({provider:"github",code:e,redirectUri:t})}signInWithApple(e){return this.loginSocial({provider:"apple",...e})}signInWithMicrosoft(e,t){return this.loginSocial({provider:"microsoft",code:e,redirectUri:t})}signInWithLinkedIn(e,t){return this.loginSocial({provider:"linkedin",code:e,redirectUri:t})}signInWithFacebook(e,t){return this.loginSocial({provider:"facebook",code:e,redirectUri:t})}linkEmailPassword(e,t){return this.linkCredential("/auth-service/me/link/email",{email:e,password:t})}linkSocial(e){return this.linkCredential("/auth-service/me/link/social",e)}linkGoogle(e){return this.linkSocial({provider:"google",...e})}linkApple(e){return this.linkSocial({provider:"apple",...e})}async linkCredential(e,t){let r=await this.getAccessToken();if(!r)throw new c("NOT_SIGNED_IN","Sign in before linking a credential");let n=await this.http.post(e,t,{Authorization:`Bearer ${r}`});if(!n.success||!n.data?.user){let i=n.error?.code??"LINK_FAILED";throw i==="CREDENTIAL_IN_USE"?new X(n.error?.message):new c(i,n.error?.message??"Failed to link credential")}return this.updateLocalUser(n.data.user),n.data.user}updateLocalUser(e){this.user=e,this.storage.set(P,JSON.stringify(e))}async listLinkedIdentities(){let e=await this.getAccessToken();if(!e)throw new c("NOT_SIGNED_IN","Sign in before listing linked identities");let t=await this.http.get("/auth-service/me/identities",{Authorization:`Bearer ${e}`});if(!t.success||!t.data)throw new c(t.error?.code??"IDENTITIES_FAILED",t.error?.message??"Failed to list linked identities");return{identities:t.data.identities??[],hasPassword:!!t.data.hasPassword}}signInWithDiscord(e,t){return this.loginSocial({provider:"discord",code:e,redirectUri:t})}challengeMfa(e,t){return this.serialize(async()=>{let r=this.takeoverHint(),n=await this.http.post("/auth-service/mfa/challenge",{challengeToken:e,code:t,...r?{prevAnonRefreshToken:r}:{}});if(!n.success||!y(n.data))throw new c(n.error?.code??"MFA_CHALLENGE_FAILED",n.error?.message??"Invalid MFA code");return this.persist(n.data),n.data.user})}async sendMagicLink(e){let t=await this.http.post("/auth-service/magic-link/request",{email:e});if(!t.success)throw new c(t.error?.code??"MAGIC_LINK_REQUEST_FAILED",t.error?.message??"Failed to send link")}async sendEmailOtp(e){let t=await this.http.post("/auth-service/email-otp/request",{email:e});if(!t.success)throw new c(t.error?.code??"EMAIL_OTP_REQUEST_FAILED",t.error?.message??"Failed to send code")}verifyEmailOtp(e,t){return this.serialize(async()=>{let r=this.user!==null,n=this.takeoverHint();r&&(await this.analytics.flush(),this.wipeLocalIdentity());let i=await this.http.post("/auth-service/email-otp/verify",{email:e,code:t,...n?{prevAnonRefreshToken:n}:{}});if(!i.success||!y(i.data))throw new c(i.error?.code??"EMAIL_OTP_INVALID",i.error?.message??"Wrong or expired code");return this.persist(i.data),i.data.user})}async requestPasswordReset(e){let t=await this.http.post("/auth-service/password/forgot",{email:e});if(!t.success)throw new c(t.error?.code??"PASSWORD_RESET_REQUEST_FAILED",t.error?.message??"Failed to send reset email")}async resetPassword(e,t){let r=await this.http.post("/auth-service/password/reset",{token:e,newPassword:t});if(!r.success)throw new c(r.error?.code??"PASSWORD_RESET_FAILED",r.error?.message??"Reset failed")}async verifyEmail(e){let t=await this.http.post("/auth-service/email/verify",{token:e});if(!t.success)throw new c(t.error?.code??"EMAIL_VERIFICATION_FAILED",t.error?.message??"Verification failed")}async sendVerificationEmail(){let e=await this.getAccessToken();if(!e)throw new c("UNAUTHORIZED","Sign in before requesting a verification email");let t=await this.http.post("/auth-service/email/verify/resend",{},{Authorization:`Bearer ${e}`});if(!t.success)throw new c(t.error?.code??"VERIFICATION_RESEND_FAILED",t.error?.message??"Failed to send verification email")}verifyMagicLink(e){return this.serialize(async()=>{let t=this.user!==null,r=this.takeoverHint();t&&(await this.analytics.flush(),this.wipeLocalIdentity());let n=await this.http.post("/auth-service/magic-link/verify",{token:e,...r?{prevAnonRefreshToken:r}:{}});if(!n.success||!y(n.data))throw new c(n.error?.code??"MAGIC_LINK_INVALID",n.error?.message??"Invalid or expired magic link");return this.persist(n.data),n.data.user})}async enrollMfa(){let e=await this.http.post("/auth-service/mfa/enroll/start",{},await this.bearerHeaders());if(!e.success||!e.data)throw new c(e.error?.code??"MFA_ENROLL_FAILED",e.error?.message??"MFA enrollment failed");return e.data}async confirmMfa(e){let t=await this.http.post("/auth-service/mfa/enroll/confirm",{code:e},await this.bearerHeaders());return!!(t.success&&t.data?.confirmed)}async disableMfa(){await this.http.post("/auth-service/mfa/disable",{},await this.bearerHeaders())}async registerPasskey(e){if(typeof navigator>"u"||!navigator.credentials)throw new c("PASSKEY_UNSUPPORTED","WebAuthn not supported in this environment");let t=await this.http.post("/auth-service/passkeys/register/start",{},await this.bearerHeaders());if(!t.success||!t.data)throw new c(t.error?.code??"PASSKEY_START_FAILED",t.error?.message??"Passkey start failed");let r=ze(t.data),n;try{let a=await navigator.credentials.create({publicKey:r});if(!a)throw new Error("create returned null");n=a}catch(a){throw new c("PASSKEY_USER_CANCELLED",a instanceof Error?a.message:"User cancelled passkey registration")}let i=Ge(n),o=await this.http.post("/auth-service/passkeys/register/finish",{response:i,name:e},await this.bearerHeaders());if(!o.success||!o.data)throw new c(o.error?.code??"PASSKEY_FINISH_FAILED",o.error?.message??"Passkey verification failed");return o.data}signInWithPasskey(e){return this.serialize(async()=>{if(typeof navigator>"u"||!navigator.credentials)throw new c("PASSKEY_UNSUPPORTED","WebAuthn not supported in this environment");let t=this.user!==null,r=this.takeoverHint();t&&(await this.analytics.flush(),this.wipeLocalIdentity());let n=await this.http.post("/auth-service/passkeys/authenticate/start",{userId:e??null});if(!n.success||!n.data)throw new c(n.error?.code??"PASSKEY_START_FAILED",n.error?.message??"Passkey start failed");let i=je(n.data),o;try{let u=await navigator.credentials.get({publicKey:i});if(!u)throw new Error("get returned null");o=u}catch(u){throw new c("PASSKEY_USER_CANCELLED",u instanceof Error?u.message:"User cancelled passkey signin")}let a=Xe(o),l=await this.http.post("/auth-service/passkeys/authenticate/finish",{response:a,userId:e??null,...r?{prevAnonRefreshToken:r}:{}});if(!l.success||!y(l.data))throw new c(l.error?.code??"PASSKEY_AUTH_FAILED",l.error?.message??"Passkey signin failed");return this.persist(l.data),l.data.user})}async listPasskeys(){let e=await this.http.get("/auth-service/passkeys",await this.bearerHeaders());return!e.success||!Array.isArray(e.data)?[]:e.data}async deletePasskey(e){await this.http.delete(`/auth-service/passkeys/${e}`,await this.bearerHeaders())}async listMySessions(){let e=await this.http.get("/auth-service/sessions/me",await this.bearerHeaders());return!e.success||!Array.isArray(e.data)?[]:e.data}async revokeSession(e){await this.http.delete(`/auth-service/sessions/me/${e}`,await this.bearerHeaders())}async revokeAllSessions(){await this.http.delete("/auth-service/sessions/me",await this.bearerHeaders())}async startSso(e){let t=this.takeoverHint(),r=await this.http.post("/auth-service/sso/oidc/start",{returnTo:e,...t?{prevAnonRefreshToken:t}:{}});if(!r.success||!r.data)throw new c(r.error?.code??"SSO_START_FAILED",r.error?.message??"SSO start failed");return r.data}async signInWithSso(e){let t=e??(typeof window<"u"?window.location.origin:"");if(!t)throw new c("SSO_NO_RETURN_TO","returnTo required outside browser context");let r=await this.startSso(t);typeof window<"u"&&(window.location.href=r.authorizationUrl)}async startSaml(e){let t=this.takeoverHint(),r=await this.http.post("/auth-service/sso/saml/start",{returnTo:e,...t?{prevAnonRefreshToken:t}:{}});if(!r.success||!r.data)throw new c(r.error?.code??"SAML_START_FAILED",r.error?.message??"SAML start failed");return r.data}async signInWithSaml(e){let t=e??(typeof window<"u"?window.location.origin:"");if(!t)throw new c("SAML_NO_RETURN_TO","returnTo required outside browser context");let r=await this.startSaml(t);typeof window<"u"&&(window.location.href=r.authorizationUrl)}consumeSsoTokenFromUrl(){return this.serialize(async()=>{if(typeof window>"u")return null;let e=window.location.hash.replace(/^#/,"");if(!e)return null;let t=new URLSearchParams(e),r=t.get("sendora_oidc_error")??t.get("sendora_saml_error");if(r)throw window.history.replaceState(null,"",window.location.pathname+window.location.search),new c("SSO_FAILED",decodeURIComponent(r));let n=t.get("sendora_oidc_token")??t.get("sendora_saml_token");if(!n)return null;let i=t.get("sendora_retired_anon");window.history.replaceState(null,"",window.location.pathname+window.location.search),this.user!==null&&(await this.analytics.flush(),this.wipeLocalIdentity());let o=await this.http.post("/auth-service/token/refresh",{refreshToken:n});if(!o.success||!o.data)throw new c(o.error?.code??"SSO_EXCHANGE_FAILED",o.error?.message??"Could not exchange SSO token");let a=o.data;this.accessToken=a.accessToken,this.accessExpiresAt=Date.now()+a.expiresIn*1e3,this.storage.set(T,a.accessToken),this.storage.set(E,String(this.accessExpiresAt)),this.storage.set(g,a.refreshToken);let l=Ye(a.accessToken),u={id:l?.sub??"",email:l?.email??null,emailVerified:l?.email_verified??!0,name:l?.name??null,isAnonymous:!1};return this.user=u,this.storage.set(P,JSON.stringify(u)),this.analytics.identify(u.id),this.flags.setContext({userId:u.id}),i&&this.fireDeviceTakeover(i,u.id),u})}async bearerHeaders(){let e=await this.getAccessToken();if(!e)throw new c("NOT_SIGNED_IN","Not signed in");return{Authorization:`Bearer ${e}`}}signOut(){return this.serialize(async()=>{let e=this.storage.get(g);this.wipeLocalIdentity(),e&&this.http.post("/auth-service/token/revoke",{refreshToken:e}).catch(()=>{})})}serialize(e){let t=this.inflight.then(e,e);return this.inflight=t.catch(()=>{}),t}async refreshAccessToken(){if(this.refreshInflight)return this.refreshInflight;let e=this.storage.get(g);if(!e)return null;let t=typeof navigator<"u"&&"locks"in navigator,r=async()=>{let n=this.storage.get(g);if(n&&n!==e){let i=this.storage.get(T),o=this.storage.get(E);if(i&&o){let a=Number(o);if(Number.isFinite(a)&&a>Date.now())return this.accessToken=i,this.accessExpiresAt=a,i}}try{let i=await this.http.post("/auth-service/token/refresh",{refreshToken:n??e});if(!i.success)return Be(i.error?.code)?(this.wipeLocalIdentity(),this.refreshFailureCount=0,this.refreshCooldownUntil=0,null):(this.armBackoff(),null);if(!i.data||typeof i.data!="object"||typeof i.data.accessToken!="string"||typeof i.data.refreshToken!="string"||typeof i.data.expiresIn!="number")return this.armBackoff(),null;let o=i.data;return this.accessToken=o.accessToken,this.accessExpiresAt=Date.now()+o.expiresIn*1e3,this.storage.set(T,o.accessToken),this.storage.set(E,String(this.accessExpiresAt)),this.storage.set(g,o.refreshToken),this.refreshFailureCount=0,this.refreshCooldownUntil=0,o.accessToken}catch{return this.armBackoff(),null}};return this.refreshInflight=(async()=>{try{return t?await navigator.locks.request("sendora-authservice-refresh",{mode:"exclusive"},r):await r()}finally{this.refreshInflight=null}})(),this.refreshInflight}armBackoff(){this.refreshFailureCount+=1;let e=Math.min(3e4,1e3*Math.pow(2,this.refreshFailureCount-1));this.refreshCooldownUntil=Date.now()+e}resetRefreshState(){this.refreshFailureCount=0,this.refreshCooldownUntil=0}startVisibilityRefreshReset(){if(this.visibilityResetUnsub||typeof document>"u")return;let e=()=>{document.visibilityState==="visible"&&this.resetRefreshState()};document.addEventListener("visibilitychange",e),this.visibilityResetUnsub=()=>document.removeEventListener("visibilitychange",e)}stopVisibilityRefreshReset(){this.visibilityResetUnsub&&(this.visibilityResetUnsub(),this.visibilityResetUnsub=null)}startProactiveRefreshCron(){if(typeof window>"u"||this.proactiveRefreshTimer)return;let e=.8,t=6e4,r=3e4,n=async()=>{if(!this.accessToken||!this.accessExpiresAt)return;let o=Date.now(),a=this.accessExpiresAt-o;if(a<=0)return;let u=Math.max(a,5*6e4)*(1-e)+(Math.random()*r*2-r);a<=u&&await this.refreshAccessToken()};this.proactiveRefreshTimer=window.setInterval(()=>{n()},t);let i=()=>{document.visibilityState==="visible"&&n()};document.addEventListener("visibilitychange",i),this.proactiveRefreshCleanup=()=>{this.proactiveRefreshTimer&&(window.clearInterval(this.proactiveRefreshTimer),this.proactiveRefreshTimer=null),document.removeEventListener("visibilitychange",i)}}stopProactiveRefreshCron(){this.proactiveRefreshCleanup&&(this.proactiveRefreshCleanup(),this.proactiveRefreshCleanup=null)}persist(e){this.user=e.user,this.accessToken=e.tokens.accessToken,this.accessExpiresAt=Date.now()+e.tokens.expiresIn*1e3,this.storage.set(P,JSON.stringify(e.user)),this.storage.set(T,e.tokens.accessToken),this.storage.set(E,String(this.accessExpiresAt)),this.storage.set(g,e.tokens.refreshToken),this.analytics.identify(e.user.id),this.flags.setContext({userId:e.user.id}),this.startProactiveRefreshCron(),this.startVisibilityRefreshReset(),e.retiredAnonUserId&&this.fireDeviceTakeover(e.retiredAnonUserId,e.user.id),e.reactivatedFromDeletion&&this.fireDeletionCancelled(e.user.id)}wipeLocalIdentity(){this.user=null,this.accessToken=null,this.accessExpiresAt=0,this.storage.remove(P),this.storage.remove(T),this.storage.remove(E),this.storage.remove(g),this.analytics.reset(),this.analytics.dropQueue(),this.flags.reset(),this.chatbot.newSession(),this.stopProactiveRefreshCron(),this.stopVisibilityRefreshReset(),this.refreshFailureCount=0,this.refreshCooldownUntil=0}};function Be(s){return s?s==="INVALID_REFRESH_TOKEN"||s==="UNAUTHORIZED"||s==="HTTP_401"||s==="RATE_LIMIT_EXCEEDED"||s==="RATE_LIMIT":!1}function H(s){let e=(4-s.length%4)%4,t=(s+"===".slice(0,e)).replace(/-/g,"+").replace(/_/g,"/"),r=atob(t),n=new ArrayBuffer(r.length),i=new Uint8Array(n);for(let o=0;o<r.length;o++)i[o]=r.charCodeAt(o);return n}function A(s){let e=s instanceof Uint8Array?s:new Uint8Array(s),t="";for(let r=0;r<e.length;r++)t+=String.fromCharCode(e[r]);return btoa(t).replace(/\+/g,"-").replace(/\//g,"_").replace(/=+$/,"")}function ze(s){return{challenge:H(s.challenge),rp:s.rp,user:{id:H(s.user.id),name:s.user.name,displayName:s.user.displayName},pubKeyCredParams:s.pubKeyCredParams,timeout:s.timeout,attestation:s.attestation,excludeCredentials:s.excludeCredentials?.map(e=>({id:H(e.id),type:"public-key",transports:e.transports})),authenticatorSelection:s.authenticatorSelection}}function je(s){return{challenge:H(s.challenge),timeout:s.timeout,rpId:s.rpId,allowCredentials:s.allowCredentials?.map(e=>({id:H(e.id),type:"public-key",transports:e.transports})),userVerification:s.userVerification}}function Ge(s){let e=s.response;return{id:s.id,rawId:A(s.rawId),type:s.type,response:{clientDataJSON:A(e.clientDataJSON),attestationObject:A(e.attestationObject),transports:typeof e.getTransports=="function"?e.getTransports():void 0},clientExtensionResults:s.getClientExtensionResults()}}function Ye(s){try{let e=s.split(".");if(e.length!==3)return null;let t=e[1].replace(/-/g,"+").replace(/_/g,"/"),r=atob(t+"===".slice(0,(4-t.length%4)%4));return JSON.parse(r)}catch{return null}}function Xe(s){let e=s.response;return{id:s.id,rawId:A(s.rawId),type:s.type,response:{clientDataJSON:A(e.clientDataJSON),authenticatorData:A(e.authenticatorData),signature:A(e.signature),userHandle:e.userHandle?A(e.userHandle):null},clientExtensionResults:s.getClientExtensionResults()}}function Qe(s){return s===!1?{pageViews:!1,sessionStart:!1,appOpen:!1,appBackground:!1,engagement:!1}:s===!0||s===void 0?{pageViews:!0,sessionStart:!0,appOpen:!0,appBackground:!0,engagement:!0}:{pageViews:s.pageViews??!0,sessionStart:s.sessionStart??!0,appOpen:s.appOpen??!0,appBackground:s.appBackground??!0,engagement:s.engagement??!0}}var Je=250,be=360*60*1e3;function Ee(s,e){if(typeof window>"u")return()=>{};let t=Qe(e.autoTrack),r=e.sessionIdleMs??18e5,n=[],i="sendora_session_id",o="sendora_session_last_activity";function a(){return Se(i)??""}function l(d){Te(i,d)}function u(){let d=Se(o);return d&&Number(d)||0}function I(d){Te(o,String(d))}function f(){return`sess_${Date.now().toString(36)}_${Math.random().toString(36).slice(2,10)}`}function m(d){let k=Date.now(),D=u();if(!a()||D&&k-D>r){let B=f();l(B),d&&t.sessionStart&&s.track("session.start",{sessionId:B})}I(k)}m(t.sessionStart),t.appOpen&&s.track("app.opened",{sessionId:a()||void 0,url:window.location.origin+window.location.pathname,referrer:document.referrer||void 0});let w=window.location.pathname,R=0,v=document.visibilityState==="visible"?Date.now():0;function te(){v>0&&(R+=Date.now()-v,v=0)}function q(){if(!t.engagement)return;te();let d=R;if(R=0,!(d<Je)){d>be&&(d=be);try{s.track("app.engagement",{durationMs:Math.round(d),screen:w,sessionId:a()||void 0})}catch{}}}function Re(d){w=d,R=0,v=document.visibilityState==="visible"?Date.now():0}if(t.pageViews||t.engagement){t.pageViews&&s.page();let d=()=>{q(),m(!0),t.pageViews&&s.page(),Re(window.location.pathname)},k=()=>d();window.addEventListener("popstate",k),n.push(()=>window.removeEventListener("popstate",k));let D=window;if(!D.__sendoraHistoryPatched){D.__sendoraHistoryPatched=!0;let B=history.pushState,Ce=history.replaceState;history.pushState=function(...se){let ne=B.apply(this,se);return window.dispatchEvent(new Event("sendora:locationchange")),ne},history.replaceState=function(...se){let ne=Ce.apply(this,se);return window.dispatchEvent(new Event("sendora:locationchange")),ne}}let re=()=>d();window.addEventListener("sendora:locationchange",re),n.push(()=>window.removeEventListener("sendora:locationchange",re))}if(t.appBackground||t.engagement){let d=()=>{if(document.visibilityState==="hidden"){q(),t.appBackground&&(m(!0),s.track("app.backgrounded",{sessionId:a()||void 0}));try{s.flush()}catch{}}else document.visibilityState==="visible"&&(t.appBackground&&(m(!0),s.track("app.foregrounded",{sessionId:a()||void 0})),t.engagement&&v===0&&(v=Date.now()))};if(document.addEventListener("visibilitychange",d),n.push(()=>document.removeEventListener("visibilitychange",d)),t.engagement){let k=()=>{q();try{s.flush()}catch{}};window.addEventListener("pagehide",k),n.push(()=>window.removeEventListener("pagehide",k))}}let he=0,C=()=>{let d=Date.now();d-he<5e3||(he=d,m(!0))};return window.addEventListener("click",C,{passive:!0}),window.addEventListener("keydown",C,{passive:!0}),window.addEventListener("scroll",C,{passive:!0}),n.push(()=>{window.removeEventListener("click",C),window.removeEventListener("keydown",C),window.removeEventListener("scroll",C)}),()=>{q();for(let d of n)try{d()}catch{}}}function Se(s){try{return window.sessionStorage?.getItem(s)??null}catch{return null}}function Te(s,e){try{window.sessionStorage?.setItem(s,e)}catch{}}var V=class s{constructor(e){this.http=e}static isSupported(){return typeof window>"u"?!1:"serviceWorker"in navigator&&"PushManager"in window&&"Notification"in window}static permission(){return typeof Notification>"u"?"denied":Notification.permission}async subscribe(e){if(!s.isSupported())throw new Error("[sendoracloud] Web Push not supported in this browser.");let t=Notification.permission;if(t==="default"&&(t=await Notification.requestPermission()),t!=="granted")throw new Error(`[sendoracloud] Notification permission ${t}.`);let r=await navigator.serviceWorker.getRegistration(e.scope??"/");r||(r=await navigator.serviceWorker.register(e.serviceWorkerPath,{scope:e.scope??"/"})),await navigator.serviceWorker.ready;let i=(await this.http.get("/push/vapid"))?.data?.publicKey;if(!i)throw new Error("[sendoracloud] backend returned no VAPID public key.");let a=await r.pushManager.getSubscription()??await r.pushManager.subscribe({userVisibleOnly:!0,applicationServerKey:Ze(i)}),l=a.toJSON(),u=l.endpoint,I=l.keys?.p256dh??"",f=l.keys?.auth??"",m=await et(u),w=await this.http.post("/push/tokens",{platform:"web",token:m,webEndpoint:u,webP256dh:I,webAuth:f,userAgent:typeof navigator<"u"?navigator.userAgent:void 0,locale:typeof navigator<"u"?navigator.language:void 0,timezone:Intl?.DateTimeFormat?.().resolvedOptions?.().timeZone,...e.userId?{userId:e.userId}:{}});if(!w?.data?.tokenId)throw new Error("[sendoracloud] backend returned no tokenId on subscribe.");return{tokenId:w.data.tokenId,subscription:a}}async unsubscribe(e="/"){if(!s.isSupported())return!1;let t=await navigator.serviceWorker.getRegistration(e);if(!t)return!1;let r=await t.pushManager.getSubscription();return r?await r.unsubscribe():!1}async trackOpen(e,t){let r={sendId:e};t!==void 0&&(r.clickAction=t);let n=await this.http.post("/push/track-open",r);if(!n.success)throw new Error(n.error?.message??"push.trackOpen failed")}};function Ze(s){let e="=".repeat((4-s.length%4)%4),t=(s+e).replace(/-/g,"+").replace(/_/g,"/"),r=atob(t),n=new Uint8Array(r.length);for(let i=0;i<r.length;i++)n[i]=r.charCodeAt(i);return n}async function et(s){let e=new TextEncoder().encode(s),t=await crypto.subtle.digest("SHA-256",e);return Array.from(new Uint8Array(t)).map(n=>n.toString(16).padStart(2,"0")).join("")}var $=class{constructor(e){}unsupported(){throw new Error("[SendoraCloud] sendora.audiences is not available in the browser SDK. Audiences require a secret (sk_*) key with the audiences:read/write scopes and must be managed from a trusted server, not the browser.")}list(){return this.unsupported()}get(e){return this.unsupported()}create(e){return this.unsupported()}update(e,t){return this.unsupported()}delete(e){return this.unsupported()}};var h=class extends Error{constructor(e,t,r){super(t),this.name="LinkError",this.code=e,this.statusCode=r}};function le(s,e){let t=(s??"").toUpperCase();return t==="NOT_FOUND"?new h("NOT_FOUND",e):t==="FORBIDDEN"||t==="UNAUTHORIZED"?new h("UNAUTHORIZED",e):t==="RATE_LIMITED"?new h("RATE_LIMITED",e):t==="ENTITLEMENT_ERROR"||/plan limit/i.test(e)?new h("PLAN_LIMIT",e):t==="VALIDATION_ERROR"?/iOS bundle|Android package/i.test(e)?new h("BUNDLE_MISMATCH",e):/2KB|10KB|linkData/i.test(e)?new h("DATA_TOO_LARGE",e):/fallbackUrl/i.test(e)&&/apps/i.test(e)?new h("FALLBACK_REQUIRED",e):new h("INVALID_INPUT",e):t==="PARSE_ERROR"||t==="HTTP_ERROR"||/HTTP \d{3}/.test(e)?new h("NETWORK",e):new h("UNKNOWN",e||`Unknown LinkError (code=${s??"none"})`)}var ue=/^[a-z0-9-]{3,20}$/;function tt(s){try{let t=new URL(s).pathname.split("/").filter(Boolean);if(t.length===0)return null;let r=t[t.length-1];return ue.test(r)?r:null}catch{return null}}var rt=5*6e4,st=50,nt=5,W=class{constructor(e){this.http=e;this.prewarmCache=new Map;this.prewarmInflight=0}cacheKey(e,t){if(t)return`k:${t}`;let r={},n=e;for(let i of Object.keys(n).sort())r[i]=n[i];return`s:${JSON.stringify(r)}`}evictExpired(){let e=Date.now();for(let[t,r]of this.prewarmCache)e-r.ts>rt&&this.prewarmCache.delete(t);for(;this.prewarmCache.size>st;){let t=this.prewarmCache.keys().next().value;if(t===void 0)break;this.prewarmCache.delete(t)}}prewarm(e,t){this.evictExpired();let r=this.cacheKey(e,t?.key);if(this.prewarmCache.has(r)||this.prewarmInflight>=nt)return;this.prewarmInflight++;let n=this.doCreate(e).catch(i=>{throw this.prewarmCache.delete(r),i}).finally(()=>{this.prewarmInflight--});this.prewarmCache.set(r,{promise:n,ts:Date.now()})}async create(e,t){if(!e.title)throw new h("INVALID_INPUT","links.create: title is required");this.evictExpired();let r=this.cacheKey(e,t?.key),n=this.prewarmCache.get(r);return n?(this.prewarmCache.delete(r),n.promise):this.doCreate(e)}async doCreate(e){let t={title:e.title};e.fallbackUrl&&(t.fallbackUrl=e.fallbackUrl),e.iosDeepLinkPath&&(t.iosDeepLinkPath=e.iosDeepLinkPath),e.androidDeepLinkPath&&(t.androidDeepLinkPath=e.androidDeepLinkPath),e.linkData&&(t.linkData=e.linkData),e.ogTitle&&(t.ogTitle=e.ogTitle),e.ogDescription&&(t.ogDescription=e.ogDescription),e.ogImageUrl&&(t.ogImageUrl=e.ogImageUrl),e.campaign&&(t.campaign=e.campaign),e.source&&(t.source=e.source),e.medium&&(t.medium=e.medium),e.channel&&(t.channel=e.channel),e.tags&&(t.tags=e.tags),e.expiresAt&&(t.expiresAt=e.expiresAt),e.iosBundleId&&(t.iosBundleId=e.iosBundleId),e.androidPackageName&&(t.androidPackageName=e.androidPackageName);let r=await this.http.post("/sdk/links",t);if(!r.success||!r.data)throw le(r.error?.code,r.error?.message??"links.create failed");return r.data}async revoke(e){if(!ue.test(e))throw new h("INVALID_INPUT",`links.revoke: '${e}' is not a valid shortcode`);let t=await this.http.post(`/sdk/links/${encodeURIComponent(e)}/revoke`,{});if(!t.success||!t.data)throw le(t.error?.code,t.error?.message??"links.revoke failed");return{shortcode:t.data.shortcode,revoked:!0}}async getStats(e){if(!ue.test(e))throw new h("INVALID_INPUT",`links.getStats: '${e}' is not a valid shortcode`);let t=await this.http.get(`/sdk/links/${encodeURIComponent(e)}/stats`);if(!t.success||!t.data)throw le(t.error?.code,t.error?.message??"links.getStats failed");return t.data}};var Q=class{constructor(e){this.http=e}async reportInstall(e={}){let t=await this.http.post("/attribution/install",e);if(!t.success||!t.data)throw new Error(t.error?.message??"attribution.reportInstall failed");return t.data}async checkDeferred(e={}){let t=await this.http.post("/attribution/deferred",e);if(!t.success)throw new Error(t.error?.message??"attribution.checkDeferred failed");return t.data??null}};var J=class{constructor(e){this.http=e}async createTicket(e){let t=await this.http.post("/support/tickets",e);if(!t.success||!t.data)throw new Error(t.error?.message??"support.createTicket failed");return t.data}async submitCsat(e){let t=await this.http.post("/support/csat",e);if(!t.success||!t.data)throw new Error(t.error?.message??"support.submitCsat failed");return t.data}};var Z=class{constructor(e){this.http=e}async search(e){if(!e.query||e.query.trim().length===0)return[];let t=new URLSearchParams({query:e.query});e.limit&&t.set("limit",String(e.limit));let r=await this.http.get(`/kb/search?${t.toString()}`);if(!r.success||!Array.isArray(r.data))throw new Error(r.error?.message??"kb.search failed");return r.data}};var ee=class ee{constructor(e){this.autoTrackCleanup=null;this.http=new z(e),this.storage=new G,this.consent=new M(e.defaultConsent===!0,this.http),this.analytics=new L(e,this.http,this.storage,this.consent),this.flags=new U(e,this.http,this.storage),this.surveys=new x(e,this.http,this.storage),this.messages=new O(e,this.http,this.storage),this.chatbot=new N(e,this.http,this.storage),this.auth=new K(e,this.http,this.storage,this.analytics,this.flags,this.chatbot),this.webPush=new V(this.http),this.audiences=new $(this.http),this.links=new W(this.http),this.attribution=new Q(this.http),this.support=new J(this.http),this.kb=new Z(this.http),e.autoTrack!==!1&&(this.autoTrackCleanup=Ee(this.analytics,e))}static init(e){return we(e.apiKey),new ee(e)}static sanitizeHtml(e){return ce(e)}static safeUrl(e){return S(e)}track(e,t){this.analytics.track(e,t)}page(e){this.analytics.page(e)}identify(e,t,r){this.analytics.identify(e,t,r),this.flags.setContext({userId:e})}reset(){this.analytics.reset(),this.flags.reset(),this.chatbot.newSession()}async flush(){await this.analytics.flush()}destroy(){this.autoTrackCleanup&&(this.autoTrackCleanup(),this.autoTrackCleanup=null),this.analytics.destroy(),this.flags.destroy(),this.messages.destroy()}};ee.version=b;var de=ee,Zt=de;export{Y as AlreadyIdentifiedError,L as Analytics,$ as Audiences,K as Auth,c as AuthError,N as Chatbot,M as Consent,X as CredentialInUseError,F as EmailAlreadyTakenError,U as FeatureFlags,O as InAppMessaging,h as LinkError,W as Links,de as SendoraCloud,x as Surveys,p as ValidationError,V as WebPush,Zt as default,tt as extractShortcodeFromUrl,S as safeUrl,ce as sanitizeHtml};
|
|
1
|
+
var ge=/^[a-zA-Z][a-zA-Z0-9._:-]{0,127}$/;var De=new Set(["__proto__","constructor","prototype"]),f=class extends Error{constructor(e){super(`[SendoraCloud] ${e}`),this.name="SendoraValidationError"}};function fe(s){if(typeof s!="string"||!ge.test(s))throw new f(`Invalid event name: must match ${ge}, got "${String(s)}".`)}function me(s){if(s==null)return;if(typeof s!="object"||Array.isArray(s))throw new f("Properties must be a plain object, not an array or primitive.");let e=ye(s);if(e>5)throw new f(`Properties nested too deeply (${e} levels, max 5).`);ve(s);let t;try{t=JSON.stringify(s).length}catch{throw new f("Properties contain a circular reference or unserializable value.")}if(t>32768)throw new f(`Properties too large (${t} bytes, max 32768).`)}function ye(s,e=0){if(e>6||s==null||typeof s!="object")return e;let t=e;for(let r of Object.keys(s)){let n=ye(s[r],e+1);n>t&&(t=n)}return t}function ve(s){if(!(s==null||typeof s!="object"))for(let e of Object.keys(s)){if(De.has(e))throw new f(`Forbidden property key "${e}" (prototype pollution guard).`);ve(s[e])}}var Ue=/^pk_[A-Za-z0-9_-]{16,}$/;function we(s){if(!s||typeof s!="string")throw new f("apiKey is required.");if(s.startsWith("sk_")||s.startsWith("sendora_secret_"))throw new f("Secret keys must NEVER be used in browser SDK. Use a publishable key (pk_...).");if(!Ue.test(s))throw new f("Invalid publishable key format. Expected pk_... (17+ chars).")}function ke(s){let e=s.replace(/\/$/,"");if(!(e.startsWith("http://localhost")||e.startsWith("http://127.0.0.1"))&&!e.startsWith("https://"))throw new f("apiUrl must be https:// (only http://localhost is allowed for dev).")}var Ae={name:"@sendoracloud/sdk-web",version:"3.14.0",description:"Sendora Cloud Web SDK \u2014 analytics, feature flags, surveys, in-app messaging, chatbot, auth, deep links (Branch / Firebase parity). Zero dependencies, TypeScript, SSR-safe.",type:"module",main:"./dist/index.cjs",module:"./dist/index.js",types:"./dist/index.d.ts",exports:{".":{types:"./dist/index.d.ts",import:"./dist/index.js",require:"./dist/index.cjs"}},files:["dist","README.md","LICENSE"],keywords:["sendora","analytics","feature-flags","surveys","in-app-messaging","chatbot","tracking","customer-engagement"],homepage:"https://sendoracloud.com/sdks",repository:{type:"git",url:"git+https://github.com/sendoracloud/sdk-web.git"},bugs:{url:"https://github.com/sendoracloud/sdk-web/issues"},author:"Sendora Cloud <hello@sendoracloud.com>",license:"Apache-2.0",publishConfig:{access:"public"},sideEffects:!1,scripts:{build:"tsup src/index.ts --format cjs,esm --dts --clean --minify",typecheck:"tsc --noEmit",prepublishOnly:"npm run build"},devDependencies:{typescript:"^5.4.0",tsup:"^8.0.0"}};var b=Ae.version;var xe="https://api.sendoracloud.com",Oe=1e4,Me="@sendoracloud/sdk-web",Y=class{constructor(e){let t=(e.apiUrl??xe).replace(/\/$/,"");ke(t),this.apiKey=e.apiKey,this.baseUrl=t,this.debug=!!e.debug}get(e,t){return this.request("GET",e,void 0,t)}post(e,t,r){return this.request("POST",e,t,r)}patch(e,t,r){return this.request("PATCH",e,t,r)}delete(e,t){return this.request("DELETE",e,void 0,t)}async request(e,t,r,n){let i=`${this.baseUrl}/api/v1${t}`,o=typeof AbortController<"u"?new AbortController:null,a=o?setTimeout(()=>o.abort(),Oe):null;try{let c=await fetch(i,{method:e,headers:{"Content-Type":"application/json","X-API-Key":this.apiKey,"X-Sendora-SDK-Name":Me,"X-Sendora-SDK-Version":b,...n??{}},body:r?JSON.stringify(r):void 0,signal:o?o.signal:void 0});a&&clearTimeout(a);let l;try{l=await c.json()}catch{l={success:!1,error:{code:"PARSE_ERROR",message:`HTTP ${c.status}`}}}if(this.debug&&console.debug(`[SendoraCloud] ${e} ${t} \u2192 ${c.status}`),!c.ok){if(t.startsWith("/auth-service/")&&!this.debug)console.warn(`[SendoraCloud] ${e} ${t} \u2192 HTTP ${c.status}`);else{let v=l?.error?.code??"HTTP_ERROR",w=l?.error?.message??`HTTP ${c.status}`,k=l?.error?.details?.fieldErrors,C=k?` (${Object.entries(k).map(([A,re])=>`${A}: ${(re??[]).join(", ")}`).join("; ")})`:"";console.warn(`[SendoraCloud] ${e} ${t} \u2192 HTTP ${c.status} \u2014 ${v}: ${w}${C}`)}l&&l.error&&(l.error={...l.error,status:c.status})}return l}catch(c){a&&clearTimeout(a);let l=c instanceof Error&&c.name==="AbortError"?"timeout":"error";return console.warn(`[SendoraCloud] ${e} ${t} network ${l}`),{success:!1,error:{code:"NETWORK_ERROR",message:l}}}}};var X="sendora_",Q=class{constructor(){this.memoryStore=new Map;this.useLocalStorage=this.checkLocalStorage()}get(e){let t=X+e;if(this.useLocalStorage)try{return localStorage.getItem(t)}catch{return this.memoryStore.get(t)??null}return this.memoryStore.get(t)??null}set(e,t){let r=X+e;if(this.useLocalStorage)try{localStorage.setItem(r,t);return}catch{}this.memoryStore.set(r,t)}remove(e){let t=X+e;if(this.useLocalStorage)try{localStorage.removeItem(t)}catch{}this.memoryStore.delete(t)}clearPrefix(e){let t=X+e;if(this.useLocalStorage)try{for(let r=localStorage.length-1;r>=0;r--){let n=localStorage.key(r);n&&n.startsWith(t)&&localStorage.removeItem(n)}}catch{}for(let r of Array.from(this.memoryStore.keys()))r.startsWith(t)&&this.memoryStore.delete(r)}checkLocalStorage(){try{if(typeof localStorage>"u")return!1;let e="__sendora_test__";return localStorage.setItem(e,"1"),localStorage.removeItem(e),!0}catch{return!1}}};function L(s=""){let e=new Uint8Array(16),t=typeof globalThis<"u"&&"crypto"in globalThis?globalThis.crypto:void 0;if(!t||typeof t.getRandomValues!="function")throw new Error("[SendoraCloud] crypto.getRandomValues unavailable \u2014 refusing to mint IDs without a CSPRNG");t.getRandomValues(e);let r="";for(let n of e)r+=n.toString(16).padStart(2,"0");return s+r}var Ie=500,oe=10,ae=6e4,x=class{constructor(e,t,r,n){this.queue=[];this.timer=null;this.userId=null;this.identityToken=null;this.consecutiveFailures=0;this.nextFlushAfter=0;this.breakerOpenAt=0;this.breakerWarned=!1;this.http=t,this.storage=r,this.consent=n,this.projectId=e.projectId,this.flushInterval=e.flushInterval??5e3,this.maxQueueSize=e.maxQueueSize??20,this.persistUserId=!!e.persistUserId,this.stripQueryParams=e.stripQueryParams!==!1,this.appVersion=e.appVersion,this.storage.get("schema_version")===null&&this.storage.set("schema_version","1");let i=this.storage.get("anonymous_id");if(i?this.anonymousId=i:(this.anonymousId=L("anon_"),this.storage.set("anonymous_id",this.anonymousId)),this.persistUserId){let o=this.storage.get("user_id");o&&(this.userId=o)}this.timer=setInterval(()=>this.flush(),this.flushInterval),typeof window<"u"&&window.addEventListener("beforeunload",()=>this.flush()),this.consent.subscribe(o=>{o&&this.flush()})}track(e,t){fe(e),t&&me(t),this.queue.length>=Ie&&this.queue.shift(),this.queue.push({eventType:e,properties:t??{},userId:this.userId??void 0,anonymousId:this.anonymousId,timestamp:new Date().toISOString(),identityToken:this.identityToken??void 0}),this.queue.length>=this.maxQueueSize&&this.flush()}page(e){if(typeof window>"u")return;let t=this.stripQueryParams?window.location.origin+window.location.pathname:window.location.href;this.track("page.viewed",{url:t,path:window.location.pathname,title:document.title,referrer:document.referrer||void 0,...e})}identify(e,t,r){if(typeof e!="string"||e.length===0||e.length>256)throw new Error("[SendoraCloud] userId must be a 1-256 char string.");this.userId=e,this.identityToken=r?.identityToken??null,this.persistUserId&&this.storage.set("user_id",e),this.track("user.identified",{...t})}reset(){this.userId=null,this.identityToken=null,this.storage.remove("user_id"),this.anonymousId=L("anon_"),this.storage.set("anonymous_id",this.anonymousId),this.storage.clearPrefix("iam_shown_"),this.storage.clearPrefix("survey_shown_")}dropQueue(){this.queue=[]}async flush(){if(this.queue.length===0||!this.consent.isGranted()||Date.now()<this.nextFlushAfter)return;this.consecutiveFailures>oe&&Date.now()-this.breakerOpenAt>=ae&&(this.consecutiveFailures=0,this.breakerOpenAt=0,this.breakerWarned=!1);let e=this.queue.splice(0,100),t=this.getSessionId(),r=e.map(a=>({projectId:this.projectId,module:"custom",eventType:a.eventType,userId:a.userId,anonymousId:a.anonymousId,identityToken:a.identityToken,timestamp:a.timestamp??new Date().toISOString(),properties:a.properties??{},context:this.getContext(),...t?{sessionId:t}:{},consent:["analytics"]}));if((await this.http.post("/events/batch",{events:r})).success){this.consecutiveFailures=0,this.breakerOpenAt=0,this.breakerWarned=!1;return}if(this.consecutiveFailures++,this.consecutiveFailures>oe){this.breakerWarned||(this.breakerOpenAt=Date.now(),this.breakerWarned=!0,console.warn(`[SendoraCloud] Analytics circuit breaker open after ${oe} consecutive failed flushes \u2014 events will be dropped until a flush succeeds or ${ae}ms elapse.`));return}let i=Math.min(ae,1e3*2**this.consecutiveFailures);this.nextFlushAfter=Date.now()+i;let o=Math.max(0,Ie-this.queue.length);o>0&&this.queue.unshift(...e.slice(0,o))}destroy(){this.timer&&clearInterval(this.timer),this.timer=null,this.flush()}getSessionId(){try{return typeof sessionStorage>"u"?void 0:sessionStorage.getItem("sendora_session_id")||void 0}catch{return}}getContext(){return typeof window>"u"?{sdk:{name:"@sendoracloud/sdk-web",version:b}}:{page:{url:this.stripQueryParams?window.location.origin+window.location.pathname:window.location.href,path:window.location.pathname,title:document.title},referrer:document.referrer||void 0,locale:typeof navigator<"u"?navigator.language:void 0,device:this.buildDevice(),sdk:{name:"@sendoracloud/sdk-web",version:b}}}buildDevice(){if(typeof navigator>"u")return;let e=navigator.userAgent||"",r=navigator.userAgentData?.mobile??/Mobi|Android|iPhone|iPod/i.test(e);return{type:/iPad|Tablet|(?:Android(?!.*Mobile))/i.test(e)?"tablet":r?"mobile":"desktop",os:"Web",osVersion:"",model:"",appVersion:this.resolveAppVersion(),browser:He(e)}}resolveAppVersion(){if(this.appVersion)return this.appVersion;if(typeof document<"u"){let r=document.querySelector('meta[name="sendora:app-version"]')?.getAttribute("content");if(r)return r}let e=globalThis.__SENDORA_APP_VERSION__;return typeof e=="string"?e:""}};function He(s){let e=[["Edge",/Edg\/(\d+)/],["Opera",/OPR\/(\d+)/],["Firefox",/Firefox\/(\d+)/],["Samsung Internet",/SamsungBrowser\/(\d+)/],["Chrome",/Chrome\/(\d+)/],["Safari",/Version\/(\d+)[^ ]* Safari/]];for(let[t,r]of e){let n=r.exec(s);if(n)return`${t} ${n[1]}`}return"Unknown"}var O=class{constructor(e,t,r){this.cache=new Map;this.refreshInterval=null;this.http=t,this.storage=r;let n=this.storage.get("flags");if(n)try{let i=JSON.parse(n);if(Array.isArray(i))for(let o of i)ce(o)&&this.cache.set(o.key,o)}catch{}}setContext(e){this.userId=e.userId,this.entityId=e.entityId,this.environment=e.environment}async getFlag(e){if(typeof e!="string"||e.length===0||e.length>128)return null;let t=this.cache.get(e);if(t)return t.value;let r=await this.http.post("/feature-flags/evaluate",{key:e,userId:this.userId,entityId:this.entityId,environment:this.environment});return r.success&&r.data&&ce(r.data)?(this.cache.set(e,r.data),this.persistCache(),r.data.value):null}async isEnabled(e,t=!1){let r=await this.getFlag(e);return typeof r=="boolean"?r:t}async refreshAll(){let e=await this.http.post("/feature-flags/evaluate-all",{userId:this.userId,entityId:this.entityId,environment:this.environment});if(e.success&&Array.isArray(e.data)){this.cache.clear();for(let t of e.data)ce(t)&&this.cache.set(t.key,t);this.persistCache()}}startAutoRefresh(e=6e4){this.stopAutoRefresh(),this.refreshAll(),this.refreshInterval=setInterval(()=>this.refreshAll(),e)}stopAutoRefresh(){this.refreshInterval&&clearInterval(this.refreshInterval),this.refreshInterval=null}getAllFlags(){let e={};for(let[t,r]of this.cache)e[t]=r.value;return e}reset(){this.cache.clear(),this.storage.remove("flags"),this.userId=void 0,this.entityId=void 0,this.environment=void 0}destroy(){this.stopAutoRefresh()}persistCache(){this.storage.set("flags",JSON.stringify(Array.from(this.cache.values())))}};function ce(s){if(s==null||typeof s!="object")return!1;let e=s;return Object.prototype.hasOwnProperty.call(e,"__proto__")||Object.prototype.hasOwnProperty.call(e,"constructor")?!1:typeof e.key=="string"&&"value"in e}var Fe=32*1024,M=class{constructor(e,t,r){this.activeSurvey=null;this.http=t,this.storage=r}onDisplay(e){this.onSurveyDisplay=e}async showSurvey(e){if(typeof e!="string"||!/^[A-Za-z0-9_-]{6,64}$/.test(e))return null;let t=`survey_shown_${e}`;if(this.storage.get(t))return null;let r=await this.http.get(`/surveys/${encodeURIComponent(e)}/config`);return!r.success||!r.data||!Ke(r.data)?null:(this.activeSurvey=r.data,this.storage.set(t,"1"),this.onSurveyDisplay&&this.onSurveyDisplay(r.data),r.data)}async submitResponse(e,t,r=!0){let n=0;try{n=JSON.stringify(t).length}catch{return!1}if(n>Fe)return!1;let i=await this.http.post("/surveys/responses",{surveyId:e,answers:t,completed:r});return i.success&&(this.activeSurvey=null),i.success}dismiss(){this.activeSurvey=null}getActiveSurvey(){return this.activeSurvey}};function Ke(s){if(s==null||typeof s!="object")return!1;let e=s;return typeof e.id!="string"||typeof e.name!="string"||!Array.isArray(e.questions)?!1:e.questions.every(Ve)}function Ve(s){if(s==null||typeof s!="object")return!1;let e=s;return typeof e.id=="string"&&typeof e.text=="string"}function E(s){if(!(!s||typeof s!="string"))try{let e=typeof window<"u"?window.location.href:"https://local/",t=new URL(s,e);return t.protocol==="http:"||t.protocol==="https:"?t.href:void 0}catch{return}}var We=new Set(["b","i","u","em","strong","a","p","br","span","ul","ol","li","h1","h2","h3","h4"]),$e={a:new Set(["href","target","rel"])};function ue(s){if(typeof DOMParser>"u")return"";let t=new DOMParser().parseFromString(`<div>${s}</div>`,"text/html").body.firstElementChild;return t?(Se(t),t.innerHTML):""}function Se(s){let e=Array.from(s.children);for(let t of e){let r=t.tagName.toLowerCase();if(!We.has(r)){let i=document.createTextNode(t.textContent??"");t.replaceWith(i);continue}let n=$e[r]??new Set;for(let i of Array.from(t.attributes)){if(!n.has(i.name.toLowerCase())){t.removeAttribute(i.name);continue}if(i.name.toLowerCase()==="href"){let o=E(i.value);o?t.setAttribute("href",o):t.removeAttribute(i.name)}}r==="a"&&(t.setAttribute("rel","noopener noreferrer"),t.setAttribute("target","_blank")),Se(t)}}var H=class{constructor(e,t,r){this.messages=[];this.pollInterval=null;this.http=t,this.storage=r}onDisplay(e){this.onMessageDisplay=e}async checkMessages(){let e=await this.http.get("/in-app-messages/active");if(!e.success||!Array.isArray(e.data))return null;this.messages=e.data.map(Be);for(let t of this.messages){let r=`iam_shown_${t.id}`;if(!this.storage.get(r))return this.storage.set(r,"1"),this.onMessageDisplay&&this.onMessageDisplay(t),t}return null}async recordAction(e,t){await this.http.post("/in-app-messages/impressions",{messageId:e,action:t})}startPolling(e=3e5){this.stopPolling(),this.checkMessages(),this.pollInterval=setInterval(()=>this.checkMessages(),e)}stopPolling(){this.pollInterval&&clearInterval(this.pollInterval),this.pollInterval=null}getMessages(){return this.messages}destroy(){this.stopPolling()}};function Be(s){return{...s,content:{...s.content,ctaUrl:E(s.content.ctaUrl),imageUrl:E(s.content.imageUrl)}}}var qe=4e3,F=class{constructor(e,t,r){this.chatbotId=null;this.messages=[];this.http=t,this.storage=r;let n=this.storage.get("chat_session_id");this.sessionId=n??L("chat_"),n||this.storage.set("chat_session_id",this.sessionId)}init(e){if(typeof e!="string"||!/^[A-Za-z0-9_-]{6,64}$/.test(e))throw new Error("[SendoraCloud] Invalid chatbotId.");this.chatbotId=e}onNewMessage(e){this.onMessage=e}async sendMessage(e,t){if(!this.chatbotId||typeof e!="string"||e.length===0||e.length>qe)return null;let r={role:"user",content:e,timestamp:new Date().toISOString()};this.messages.push(r);let n=await this.http.post("/chatbot/message",{chatbotId:this.chatbotId,sessionId:this.sessionId,message:e,userId:t});if(n.success&&n.data&&typeof n.data.reply=="string"){let i={role:"assistant",content:n.data.reply,timestamp:new Date().toISOString()};return this.messages.push(i),this.onMessage&&this.onMessage(i),i}return null}getMessages(){return[...this.messages]}newSession(){this.messages=[],this.sessionId=L("chat_"),this.storage.set("chat_session_id",this.sessionId)}getSessionId(){return this.sessionId}};var K=class{constructor(e,t){this.listeners=new Set;this.granted=e,this.http=t}isGranted(){return this.granted}grant(){this.granted||(this.granted=!0,this.emit())}revoke(){this.granted&&(this.granted=!1,this.emit())}subscribe(e){return this.listeners.add(e),()=>this.listeners.delete(e)}async record(e){if(!this.http)throw new Error("consent.record requires SDK init (HttpClient unavailable)");let t=await this.http.post("/consent/record",e);if(!t.success||!t.data)throw new Error(t.error?.message??"consent.record failed");return t.data}async requestDeletion(e){if(!this.http)throw new Error("consent.requestDeletion requires SDK init (HttpClient unavailable)");let t=await this.http.post("/consent/deletion-request",e);if(!t.success||!t.data)throw new Error(t.error?.message??"consent.requestDeletion failed");return t.data}emit(){for(let e of this.listeners)try{e(this.granted)}catch{}}};var T="auth_access_token",R="auth_access_expires",m="auth_refresh_token",_="auth_user",je=3e4,ze=/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;function y(s){if(!s||typeof s!="object")return!1;let e=s,t=e.user,r=e.tokens;return!(!t||typeof t.id!="string"||t.id.length===0||!r||typeof r.accessToken!="string"||r.accessToken.length===0||typeof r.refreshToken!="string"||r.refreshToken.length===0||typeof r.expiresIn!="number"||r.expiresIn<=0||t.isAnonymous!==void 0&&typeof t.isAnonymous!="boolean"||t.email!==void 0&&t.email!==null&&typeof t.email!="string"||t.emailVerified!==void 0&&typeof t.emailVerified!="boolean"||t.name!==void 0&&t.name!==null&&typeof t.name!="string"||t.signupMethod!==void 0&&t.signupMethod!==null&&typeof t.signupMethod!="string"||t.lastLoginMethod!==void 0&&t.lastLoginMethod!==null&&typeof t.lastLoginMethod!="string"||e.retiredAnonUserId!==void 0&&typeof e.retiredAnonUserId!="string")}var D=class extends Error{constructor(e="An account with this email already exists"){super(e),this.name="EmailAlreadyTakenError"}},W=class extends Error{constructor(e="Already signed in with an identity; use link*() to add a credential, or sign out first"){super(e),this.name="AlreadyIdentifiedError"}},U=class extends Error{constructor(t="This credential is already linked to a different account",r){super(t);this.code="CREDENTIAL_IN_USE";this.kind="credential_in_use";this.retryable=!1;this.name="CredentialInUseError",this.provider=r?.provider,this.collision=r?.collision,this.status=r?.status}};function be(s){let e=s?.error?.details;if(!e||typeof e!="object")return{};let t=e;return{provider:typeof t.provider=="string"?t.provider:void 0,collision:t.collision==="identity"||t.collision==="email"?t.collision:void 0}}var Ge=new Set(["network","server","rate_limited","account_locked"]);function Ye(s,e){switch(s){case"NETWORK_ERROR":case"NETWORK_TIMEOUT":return"network";case"PARSE_ERROR":return"server";case"RATE_LIMITED":case"RATE_LIMIT":case"RATE_LIMIT_EXCEEDED":case"QUOTA_EXCEEDED":return"rate_limited";case"ACCOUNT_LOCKED":return"account_locked";case"CREDENTIAL_IN_USE":return"credential_in_use";case"NOT_ANONYMOUS":case"FORBIDDEN_NON_ANONYMOUS":return"already_identified";case"PASSKEY_USER_CANCELLED":case"GAME_CENTER_CANCELLED":case"PLAY_GAMES_CANCELLED":case"SSO_CANCELLED":return"cancelled";case"GAME_CENTER_UNAVAILABLE":case"PLAY_GAMES_UNAVAILABLE":case"ENTITLEMENT_ERROR":case"tier_required":return"config";case"UNAUTHORIZED":case"INVALID_CREDENTIALS":case"INVALID_REFRESH_TOKEN":case"EMAIL_OTP_INVALID":case"MAGIC_LINK_INVALID":case"PASSKEY_RP_MISMATCH":case"NOT_SIGNED_IN":return"invalid_credential";default:break}if(typeof e=="number"){if(e===429)return"rate_limited";if(e>=500)return"server";if(e>=400)return"invalid_credential"}return"unknown"}var u=class extends Error{constructor(e,t,r){super(t),this.name="AuthError",this.code=e,this.kind=r?.kind??Ye(e,r?.status),this.retryable=Ge.has(this.kind),this.retryAfterSeconds=r?.retryAfterSeconds,this.status=r?.status}};function Xe(s){if(s instanceof u||s instanceof D||s instanceof W||s instanceof U)return s;let e=s?.name;return e==="AbortError"||e==="TimeoutError"||e==="NetworkTimeoutError"?new u("NETWORK_TIMEOUT",s instanceof Error?s.message:"timeout",{kind:"network"}):new u("UNKNOWN_ERROR",s instanceof Error?s.message:String(s),{kind:"unknown"})}function h(s){let e=s.error?.status,t=s.error?.details?.retryAfterSeconds;return{status:typeof e=="number"?e:void 0,retryAfterSeconds:typeof t=="number"&&Number.isFinite(t)&&t>0?t:void 0}}var $=class{constructor(e,t,r,n,i,o){this.user=null;this.accessToken=null;this.accessExpiresAt=0;this.inflight=Promise.resolve();this.refreshInflight=null;this.refreshFailureCount=0;this.refreshCooldownUntil=0;this.visibilityResetUnsub=null;this.proactiveRefreshTimer=null;this.proactiveRefreshCleanup=null;this.takeoverListeners=new Set;this.lastTakeover=null;this.deletionCancelledListeners=new Set;this.lastDeletionCancelled=null;this.authStateListeners=new Set;this.pendingAnonTakeover=null;this.hydrated=!1;this.http=t,this.storage=r,this.analytics=n,this.flags=i,this.chatbot=o;let a=this.storage.get(_),c=this.storage.get(T),l=this.storage.get(m),g=this.storage.get(R);if(a&&(c||l))try{let v=JSON.parse(a);if(typeof v.id!="string"||v.id.length===0)throw new Error("invalid cache");this.user=v,c?(this.accessToken=c,this.accessExpiresAt=g?Number(g):0):(this.accessToken=null,this.accessExpiresAt=0),this.analytics.identify(this.user.id)}catch{this.storage.remove(_),this.storage.remove(T),this.storage.remove(R)}this.hydrated=!0}getCurrentUser(){return this.user}onDeviceTakeover(e){return this.takeoverListeners.add(e),()=>{this.takeoverListeners.delete(e)}}getLastDeviceTakeover(){return this.lastTakeover}onAuthStateChanged(e){if(this.authStateListeners.add(e),this.hydrated&&this.user){let t=this.user;try{e({event:"signed_in",user:t})}catch{}}return()=>{this.authStateListeners.delete(e)}}emitAuthState(e){for(let t of this.authStateListeners)try{t(e)}catch{}}fireDeviceTakeover(e,t){if(!e||!ze.test(e))return;let r={retiredAnonUserId:e,identifiedUserId:t,at:new Date};this.lastTakeover=r;for(let n of this.takeoverListeners)try{n(r)}catch{}this.emitAuthState({event:"device_takeover",user:this.user,retiredAnonUserId:e})}onDeletionCancelled(e){return this.deletionCancelledListeners.add(e),()=>{this.deletionCancelledListeners.delete(e)}}getLastDeletionCancelled(){return this.lastDeletionCancelled}fireDeletionCancelled(e){let t={userId:e,at:new Date};this.lastDeletionCancelled=t;for(let r of this.deletionCancelledListeners)try{r(t)}catch{}this.emitAuthState({event:"deletion_cancelled",user:this.user})}takeoverHint(){if(this.user?.isAnonymous)return this.storage.get(m)??void 0}async getAccessToken(){return this.accessToken&&this.accessExpiresAt&&Date.now()<this.accessExpiresAt-je?this.accessToken:this.storage.get(m)?this.refreshInflight?this.refreshInflight:Date.now()<this.refreshCooldownUntil?null:this.refreshAccessToken():null}getAccessTokenSync(){return this.accessToken}signInAnonymously(e){return this.serialize(async()=>{let t=this.user;if(!e?.forceNew&&t?.isAnonymous&&this.storage.get(m))return t;let r=await this.http.post("/auth-service/anonymous",{name:e?.name,metadata:e?.metadata});if(!r.success||!y(r.data))throw new u(r.error?.code??"AUTH_ERROR",r.error?.message??"Anonymous signup failed",h(r));return this.persist(r.data),r.data.user})}signUp(e,t,r){return this.serialize(async()=>{let n=this.storage.get(m);if(n){let o=await this.http.post("/auth-service/upgrade",{refreshToken:n,email:e,password:t,name:r?.name});if(o.success&&y(o.data))return this.persist(o.data),o.data.user;let a=o.error?.code??"",c=h(o),l=c.status;if(a==="NOT_ANONYMOUS"||a==="FORBIDDEN_NON_ANONYMOUS")throw new W(o.error?.message);if(a==="CONFLICT"||a==="EMAIL_ALREADY_TAKEN"||l===409)throw new D(o.error?.message);if(a==="INVALID_REFRESH"||a==="UNAUTHORIZED"||a==="REFRESH_EXPIRED")await this.analytics.flush(),this.wipeLocalIdentity("session_expired");else throw new u(a||"AUTH_ERROR",o.error?.message??"Upgrade failed",c)}let i=await this.http.post("/auth-service/signup",{email:e,password:t,name:r?.name,metadata:r?.metadata});if(!i.success||!y(i.data)){let o=i.error?.code??"";throw o==="CONFLICT"?new D(i.error?.message):new u(o||"AUTH_ERROR",i.error?.message??"Signup failed",h(i))}return this.persist(i.data),i.data.user})}signIn(e,t){return this.serialize(async()=>{let r=this.takeoverHint(),n=await this.http.post("/auth-service/login",{email:e,password:t,...r?{prevAnonRefreshToken:r}:{}});if(!n.success||!n.data)throw new u(n.error?.code??"AUTH_ERROR",n.error?.message??"Login failed",h(n));let i=n.data;if(i.mfaRequired===!0&&typeof i.mfaChallengeToken=="string"){r&&(this.pendingAnonTakeover={challengeToken:i.mfaChallengeToken,prevAnonRefreshToken:r});let o=i.user;return{mfaRequired:!0,mfaChallengeToken:i.mfaChallengeToken,userId:o?.id??""}}if(!y(n.data))throw new u(n.error?.code??"AUTH_ERROR",n.error?.message??"Login failed",h(n));return await this.replaceLocalIdentity(),this.persist(n.data),n.data.user})}loginSocial(e){return this.serialize(async()=>{let t=this.takeoverHint(),{link:r,onCredentialInUse:n,...i}=e,o=await this.http.post("/auth-service/login/social",{...i,...t?{prevAnonRefreshToken:t}:{},...r?{linkAnonymous:!0}:{},...n?{onCredentialInUse:n}:{}});if(!o.success||!y(o.data))throw o.error?.code==="CREDENTIAL_IN_USE"?new U(o.error.message,{...be(o),status:h(o).status}):new u(o.error?.code??"SOCIAL_LOGIN_FAILED",o.error?.message??"Social login failed",h(o));return await this.replaceLocalIdentity(),this.persist(o.data),o.data.user})}signInWithGoogle(e,t,r){return this.loginSocial({provider:"google",code:e,redirectUri:t,link:r})}signInWithGitHub(e,t){return this.loginSocial({provider:"github",code:e,redirectUri:t})}signInWithApple(e){return this.loginSocial({provider:"apple",...e})}signInWithMicrosoft(e,t){return this.loginSocial({provider:"microsoft",code:e,redirectUri:t})}signInWithLinkedIn(e,t){return this.loginSocial({provider:"linkedin",code:e,redirectUri:t})}signInWithFacebook(e,t){return this.loginSocial({provider:"facebook",code:e,redirectUri:t})}linkEmailPassword(e,t){return this.linkCredential("/auth-service/me/link/email",{email:e,password:t})}linkSocial(e){return this.linkCredential("/auth-service/me/link/social",e)}linkGoogle(e){return this.linkSocial({provider:"google",...e})}linkApple(e){return this.linkSocial({provider:"apple",...e})}async linkCredential(e,t){let r=await this.getAccessToken();if(!r)throw new u("NOT_SIGNED_IN","Sign in before linking a credential");let n=await this.http.post(e,t,{Authorization:`Bearer ${r}`});if(!n.success||!n.data?.user){let i=n.error?.code??"LINK_FAILED";throw i==="CREDENTIAL_IN_USE"?new U(n.error?.message,{...be(n),status:h(n).status}):new u(i,n.error?.message??"Failed to link credential",h(n))}return n.data.upgraded&&y(n.data)?(this.persist(n.data),n.data.user):(this.updateLocalUser(n.data.user),n.data.user)}updateLocalUser(e){this.user=e,this.storage.set(_,JSON.stringify(e))}async listLinkedIdentities(){let e=await this.getAccessToken();if(!e)throw new u("NOT_SIGNED_IN","Sign in before listing linked identities");let t=await this.http.get("/auth-service/me/identities",{Authorization:`Bearer ${e}`});if(!t.success||!t.data)throw new u(t.error?.code??"IDENTITIES_FAILED",t.error?.message??"Failed to list linked identities",h(t));return{identities:t.data.identities??[],hasPassword:!!t.data.hasPassword}}signInWithDiscord(e,t){return this.loginSocial({provider:"discord",code:e,redirectUri:t})}challengeMfa(e,t){return this.serialize(async()=>{let n=(this.pendingAnonTakeover?.challengeToken===e?this.pendingAnonTakeover.prevAnonRefreshToken:void 0)??this.takeoverHint(),i=await this.http.post("/auth-service/mfa/challenge",{challengeToken:e,code:t,...n?{prevAnonRefreshToken:n}:{}});if(!i.success||!y(i.data))throw new u(i.error?.code??"MFA_CHALLENGE_FAILED",i.error?.message??"Invalid MFA code",h(i));return await this.replaceLocalIdentity(),this.persist(i.data),i.data.user})}async sendMagicLink(e){let t=await this.http.post("/auth-service/magic-link/request",{email:e});if(!t.success)throw new u(t.error?.code??"MAGIC_LINK_REQUEST_FAILED",t.error?.message??"Failed to send link")}async sendEmailOtp(e){let t=await this.http.post("/auth-service/email-otp/request",{email:e});if(!t.success)throw new u(t.error?.code??"EMAIL_OTP_REQUEST_FAILED",t.error?.message??"Failed to send code")}verifyEmailOtp(e,t){return this.serialize(async()=>{let r=this.takeoverHint(),n=await this.http.post("/auth-service/email-otp/verify",{email:e,code:t,...r?{prevAnonRefreshToken:r}:{}});if(!n.success||!y(n.data))throw new u(n.error?.code??"EMAIL_OTP_INVALID",n.error?.message??"Wrong or expired code",h(n));return await this.replaceLocalIdentity(),this.persist(n.data),n.data.user})}async requestPasswordReset(e){let t=await this.http.post("/auth-service/password/forgot",{email:e});if(!t.success)throw new u(t.error?.code??"PASSWORD_RESET_REQUEST_FAILED",t.error?.message??"Failed to send reset email")}async resetPassword(e,t){let r=await this.http.post("/auth-service/password/reset",{token:e,newPassword:t});if(!r.success)throw new u(r.error?.code??"PASSWORD_RESET_FAILED",r.error?.message??"Reset failed")}async verifyEmail(e){let t=await this.http.post("/auth-service/email/verify",{token:e});if(!t.success)throw new u(t.error?.code??"EMAIL_VERIFICATION_FAILED",t.error?.message??"Verification failed")}async sendVerificationEmail(){let e=await this.getAccessToken();if(!e)throw new u("UNAUTHORIZED","Sign in before requesting a verification email");let t=await this.http.post("/auth-service/email/verify/resend",{},{Authorization:`Bearer ${e}`});if(!t.success)throw new u(t.error?.code??"VERIFICATION_RESEND_FAILED",t.error?.message??"Failed to send verification email")}verifyMagicLink(e){return this.serialize(async()=>{let t=this.takeoverHint(),r=await this.http.post("/auth-service/magic-link/verify",{token:e,...t?{prevAnonRefreshToken:t}:{}});if(!r.success||!y(r.data))throw new u(r.error?.code??"MAGIC_LINK_INVALID",r.error?.message??"Invalid or expired magic link",h(r));return await this.replaceLocalIdentity(),this.persist(r.data),r.data.user})}async enrollMfa(){let e=await this.http.post("/auth-service/mfa/enroll/start",{},await this.bearerHeaders());if(!e.success||!e.data)throw new u(e.error?.code??"MFA_ENROLL_FAILED",e.error?.message??"MFA enrollment failed");return e.data}async confirmMfa(e){let t=await this.http.post("/auth-service/mfa/enroll/confirm",{code:e},await this.bearerHeaders());return!!(t.success&&t.data?.confirmed)}async disableMfa(){await this.http.post("/auth-service/mfa/disable",{},await this.bearerHeaders())}async registerPasskey(e){if(typeof navigator>"u"||!navigator.credentials)throw new u("PASSKEY_UNSUPPORTED","WebAuthn not supported in this environment");let t=await this.http.post("/auth-service/passkeys/register/start",{},await this.bearerHeaders());if(!t.success||!t.data)throw new u(t.error?.code??"PASSKEY_START_FAILED",t.error?.message??"Passkey start failed");let r=Je(t.data),n;try{let a=await navigator.credentials.create({publicKey:r});if(!a)throw new Error("create returned null");n=a}catch(a){throw new u("PASSKEY_USER_CANCELLED",a instanceof Error?a.message:"User cancelled passkey registration")}let i=et(n),o=await this.http.post("/auth-service/passkeys/register/finish",{response:i,name:e},await this.bearerHeaders());if(!o.success||!o.data)throw new u(o.error?.code??"PASSKEY_FINISH_FAILED",o.error?.message??"Passkey verification failed");return o.data}signInWithPasskey(e){return this.serialize(async()=>{if(typeof navigator>"u"||!navigator.credentials)throw new u("PASSKEY_UNSUPPORTED","WebAuthn not supported in this environment");let t=this.takeoverHint(),r=await this.http.post("/auth-service/passkeys/authenticate/start",{userId:e??null});if(!r.success||!r.data)throw new u(r.error?.code??"PASSKEY_START_FAILED",r.error?.message??"Passkey start failed",h(r));let n=Ze(r.data),i;try{let c=await navigator.credentials.get({publicKey:n});if(!c)throw new Error("get returned null");i=c}catch(c){throw new u("PASSKEY_USER_CANCELLED",c instanceof Error?c.message:"User cancelled passkey signin")}let o=st(i),a=await this.http.post("/auth-service/passkeys/authenticate/finish",{response:o,userId:e??null,...t?{prevAnonRefreshToken:t}:{}});if(!a.success||!y(a.data))throw new u(a.error?.code??"PASSKEY_AUTH_FAILED",a.error?.message??"Passkey signin failed",h(a));return await this.replaceLocalIdentity(),this.persist(a.data),a.data.user})}async listPasskeys(){let e=await this.http.get("/auth-service/passkeys",await this.bearerHeaders());return!e.success||!Array.isArray(e.data)?[]:e.data}async deletePasskey(e){await this.http.delete(`/auth-service/passkeys/${e}`,await this.bearerHeaders())}async listMySessions(){let e=await this.http.get("/auth-service/sessions/me",await this.bearerHeaders());return!e.success||!Array.isArray(e.data)?[]:e.data}async revokeSession(e){await this.http.delete(`/auth-service/sessions/me/${e}`,await this.bearerHeaders())}async revokeAllSessions(){await this.http.delete("/auth-service/sessions/me",await this.bearerHeaders())}async startSso(e){let t=this.takeoverHint(),r=await this.http.post("/auth-service/sso/oidc/start",{returnTo:e,...t?{prevAnonRefreshToken:t}:{}});if(!r.success||!r.data)throw new u(r.error?.code??"SSO_START_FAILED",r.error?.message??"SSO start failed");return r.data}async signInWithSso(e){let t=e??(typeof window<"u"?window.location.origin:"");if(!t)throw new u("SSO_NO_RETURN_TO","returnTo required outside browser context");let r=await this.startSso(t);typeof window<"u"&&(window.location.href=r.authorizationUrl)}async startSaml(e){let t=this.takeoverHint(),r=await this.http.post("/auth-service/sso/saml/start",{returnTo:e,...t?{prevAnonRefreshToken:t}:{}});if(!r.success||!r.data)throw new u(r.error?.code??"SAML_START_FAILED",r.error?.message??"SAML start failed");return r.data}async signInWithSaml(e){let t=e??(typeof window<"u"?window.location.origin:"");if(!t)throw new u("SAML_NO_RETURN_TO","returnTo required outside browser context");let r=await this.startSaml(t);typeof window<"u"&&(window.location.href=r.authorizationUrl)}consumeSsoTokenFromUrl(){return this.serialize(async()=>{if(typeof window>"u")return null;let e=window.location.hash.replace(/^#/,"");if(!e)return null;let t=new URLSearchParams(e),r=t.get("sendora_oidc_error")??t.get("sendora_saml_error");if(r)throw window.history.replaceState(null,"",window.location.pathname+window.location.search),new u("SSO_FAILED",decodeURIComponent(r));let n=t.get("sendora_oidc_token")??t.get("sendora_saml_token");if(!n)return null;let i=t.get("sendora_retired_anon");window.history.replaceState(null,"",window.location.pathname+window.location.search);let o=await this.http.post("/auth-service/token/refresh",{refreshToken:n}),a=o.success?Ee(o.data):null;if(!a)throw new u(o.error?.code??"SSO_EXCHANGE_FAILED",o.error?.message??"Could not exchange SSO token",h(o));let c={...a,tokenType:"Bearer"};await this.replaceLocalIdentity(),this.accessToken=c.accessToken,this.accessExpiresAt=Date.now()+c.expiresIn*1e3,this.storage.set(T,c.accessToken),this.storage.set(R,String(this.accessExpiresAt)),this.storage.set(m,c.refreshToken);let l=rt(c.accessToken),g={id:l?.sub??"",email:l?.email??null,emailVerified:l?.email_verified??!0,name:l?.name??null,isAnonymous:!1};return this.user=g,this.storage.set(_,JSON.stringify(g)),this.analytics.identify(g.id),this.flags.setContext({userId:g.id}),this.emitAuthState({event:"signed_in",user:g}),i&&this.fireDeviceTakeover(i,g.id),g})}async bearerHeaders(){let e=await this.getAccessToken();if(!e)throw new u("NOT_SIGNED_IN","Not signed in");return{Authorization:`Bearer ${e}`}}signOut(){return this.serialize(async()=>{let e=this.storage.get(m);this.wipeLocalIdentity("user"),e&&this.http.post("/auth-service/token/revoke",{refreshToken:e}).catch(()=>{})})}serialize(e){let t=async()=>{try{return await e()}catch(n){throw Xe(n)}},r=this.inflight.then(t,t);return this.inflight=r.catch(()=>{}),r}async refreshAccessToken(){if(this.refreshInflight)return this.refreshInflight;let e=this.storage.get(m);if(!e)return null;let t=typeof navigator<"u"&&"locks"in navigator,r=async()=>{let n=this.storage.get(m);if(n&&n!==e){let i=this.storage.get(T),o=this.storage.get(R);if(i&&o){let a=Number(o);if(Number.isFinite(a)&&a>Date.now())return this.accessToken=i,this.accessExpiresAt=a,i}}try{let i=n??e,o=await this.http.post("/auth-service/token/refresh",{refreshToken:i});if(!o.success)return Qe(o.error?.code)&&this.tokenStillCurrent(i)?(this.wipeLocalIdentity("session_expired"),this.refreshFailureCount=0,this.refreshCooldownUntil=0,null):(this.armBackoff(),null);let a=Ee(o.data);if(!a)return this.armBackoff(),null;if(!this.tokenStillCurrent(i))return null;if(this.accessToken=a.accessToken,this.accessExpiresAt=Date.now()+a.expiresIn*1e3,this.storage.set(T,a.accessToken),this.storage.set(R,String(this.accessExpiresAt)),this.storage.set(m,a.refreshToken),this.user===null){let c=tt(o.data);c&&(this.user=c,this.storage.set(_,JSON.stringify(c)),this.analytics.identify(c.id),this.emitAuthState({event:"signed_in",user:c}))}return this.refreshFailureCount=0,this.refreshCooldownUntil=0,a.accessToken}catch{return this.armBackoff(),null}};return this.refreshInflight=(async()=>{try{return t?await navigator.locks.request("sendora-authservice-refresh",{mode:"exclusive"},r):await r()}finally{this.refreshInflight=null}})(),this.refreshInflight}armBackoff(){this.refreshFailureCount+=1;let e=Math.min(3e4,1e3*Math.pow(2,this.refreshFailureCount-1));this.refreshCooldownUntil=Date.now()+e}resetRefreshState(){this.refreshFailureCount=0,this.refreshCooldownUntil=0}startVisibilityRefreshReset(){if(this.visibilityResetUnsub||typeof document>"u")return;let e=()=>{document.visibilityState==="visible"&&this.resetRefreshState()};document.addEventListener("visibilitychange",e),this.visibilityResetUnsub=()=>document.removeEventListener("visibilitychange",e)}stopVisibilityRefreshReset(){this.visibilityResetUnsub&&(this.visibilityResetUnsub(),this.visibilityResetUnsub=null)}startProactiveRefreshCron(){if(typeof window>"u"||this.proactiveRefreshTimer)return;let e=.8,t=6e4,r=3e4,n=async()=>{if(!this.accessToken||!this.accessExpiresAt)return;let o=Date.now(),a=this.accessExpiresAt-o;if(a<=0)return;let l=Math.max(a,5*6e4)*(1-e)+(Math.random()*r*2-r);a<=l&&await this.refreshAccessToken()};this.proactiveRefreshTimer=window.setInterval(()=>{n()},t);let i=()=>{document.visibilityState==="visible"&&n()};document.addEventListener("visibilitychange",i),this.proactiveRefreshCleanup=()=>{this.proactiveRefreshTimer&&(window.clearInterval(this.proactiveRefreshTimer),this.proactiveRefreshTimer=null),document.removeEventListener("visibilitychange",i)}}stopProactiveRefreshCron(){this.proactiveRefreshCleanup&&(this.proactiveRefreshCleanup(),this.proactiveRefreshCleanup=null)}persist(e){this.user=e.user,this.accessToken=e.tokens.accessToken,this.accessExpiresAt=Date.now()+e.tokens.expiresIn*1e3,this.storage.set(_,JSON.stringify(e.user)),this.storage.set(T,e.tokens.accessToken),this.storage.set(R,String(this.accessExpiresAt)),this.storage.set(m,e.tokens.refreshToken),this.analytics.identify(e.user.id),this.flags.setContext({userId:e.user.id}),this.startProactiveRefreshCron(),this.startVisibilityRefreshReset(),this.emitAuthState({event:"signed_in",user:e.user}),e.retiredAnonUserId&&this.fireDeviceTakeover(e.retiredAnonUserId,e.user.id),e.reactivatedFromDeletion&&this.fireDeletionCancelled(e.user.id)}async replaceLocalIdentity(){this.user!==null&&(await this.analytics.flush(),this.wipeLocalIdentity("replaced"))}tokenStillCurrent(e){return this.storage.get(m)===e}wipeLocalIdentity(e="replaced"){this.user=null,this.accessToken=null,this.accessExpiresAt=0,this.lastTakeover=null,this.lastDeletionCancelled=null,this.pendingAnonTakeover=null,this.storage.remove(_),this.storage.remove(T),this.storage.remove(R),this.storage.remove(m),this.analytics.reset(),this.analytics.dropQueue(),this.flags.reset(),this.chatbot.newSession(),this.stopProactiveRefreshCron(),this.stopVisibilityRefreshReset(),this.refreshFailureCount=0,this.refreshCooldownUntil=0,e!=="replaced"&&this.emitAuthState({event:"signed_out",user:null,reason:e})}};function Qe(s){return s?s==="INVALID_REFRESH_TOKEN":!1}function V(s){let e=(4-s.length%4)%4,t=(s+"===".slice(0,e)).replace(/-/g,"+").replace(/_/g,"/"),r=atob(t),n=new ArrayBuffer(r.length),i=new Uint8Array(n);for(let o=0;o<r.length;o++)i[o]=r.charCodeAt(o);return n}function S(s){let e=s instanceof Uint8Array?s:new Uint8Array(s),t="";for(let r=0;r<e.length;r++)t+=String.fromCharCode(e[r]);return btoa(t).replace(/\+/g,"-").replace(/\//g,"_").replace(/=+$/,"")}function Je(s){return{challenge:V(s.challenge),rp:s.rp,user:{id:V(s.user.id),name:s.user.name,displayName:s.user.displayName},pubKeyCredParams:s.pubKeyCredParams,timeout:s.timeout,attestation:s.attestation,excludeCredentials:s.excludeCredentials?.map(e=>({id:V(e.id),type:"public-key",transports:e.transports})),authenticatorSelection:s.authenticatorSelection}}function Ze(s){return{challenge:V(s.challenge),timeout:s.timeout,rpId:s.rpId,allowCredentials:s.allowCredentials?.map(e=>({id:V(e.id),type:"public-key",transports:e.transports})),userVerification:s.userVerification}}function et(s){let e=s.response;return{id:s.id,rawId:S(s.rawId),type:s.type,response:{clientDataJSON:S(e.clientDataJSON),attestationObject:S(e.attestationObject),transports:typeof e.getTransports=="function"?e.getTransports():void 0},clientExtensionResults:s.getClientExtensionResults()}}function Ee(s){if(!s||typeof s!="object")return null;let e=s.tokens,t=e&&typeof e=="object"?e:s;return typeof t.accessToken!="string"||!t.accessToken||typeof t.refreshToken!="string"||!t.refreshToken||typeof t.expiresIn!="number"||t.expiresIn<=0?null:{accessToken:t.accessToken,refreshToken:t.refreshToken,expiresIn:t.expiresIn}}function tt(s){if(!s||typeof s!="object")return null;let e=s.user;if(!e||typeof e!="object")return null;let t=e.id;return typeof t!="string"||t.length===0?null:e}function rt(s){try{let e=s.split(".");if(e.length!==3)return null;let t=e[1].replace(/-/g,"+").replace(/_/g,"/"),r=atob(t+"===".slice(0,(4-t.length%4)%4));return JSON.parse(r)}catch{return null}}function st(s){let e=s.response;return{id:s.id,rawId:S(s.rawId),type:s.type,response:{clientDataJSON:S(e.clientDataJSON),authenticatorData:S(e.authenticatorData),signature:S(e.signature),userHandle:e.userHandle?S(e.userHandle):null},clientExtensionResults:s.getClientExtensionResults()}}function nt(s){return s===!1?{pageViews:!1,sessionStart:!1,appOpen:!1,appBackground:!1,engagement:!1}:s===!0||s===void 0?{pageViews:!0,sessionStart:!0,appOpen:!0,appBackground:!0,engagement:!0}:{pageViews:s.pageViews??!0,sessionStart:s.sessionStart??!0,appOpen:s.appOpen??!0,appBackground:s.appBackground??!0,engagement:s.engagement??!0}}var it=250,Te=360*60*1e3;function Ce(s,e){if(typeof window>"u")return()=>{};let t=nt(e.autoTrack),r=e.sessionIdleMs??18e5,n=[],i="sendora_session_id",o="sendora_session_last_activity";function a(){return Re(i)??""}function c(d){_e(i,d)}function l(){let d=Re(o);return d&&Number(d)||0}function g(d){_e(o,String(d))}function v(){return`sess_${Date.now().toString(36)}_${Math.random().toString(36).slice(2,10)}`}function w(d){let I=Date.now(),N=l();if(!a()||N&&I-N>r){let G=v();c(G),d&&t.sessionStart&&s.track("session.start",{sessionId:G})}g(I)}w(t.sessionStart),t.appOpen&&s.track("app.opened",{sessionId:a()||void 0,url:window.location.origin+window.location.pathname,referrer:document.referrer||void 0});let k=window.location.pathname,C=0,A=document.visibilityState==="visible"?Date.now():0;function re(){A>0&&(C+=Date.now()-A,A=0)}function z(){if(!t.engagement)return;re();let d=C;if(C=0,!(d<it)){d>Te&&(d=Te);try{s.track("app.engagement",{durationMs:Math.round(d),screen:k,sessionId:a()||void 0})}catch{}}}function Pe(d){k=d,C=0,A=document.visibilityState==="visible"?Date.now():0}if(t.pageViews||t.engagement){t.pageViews&&s.page();let d=()=>{z(),w(!0),t.pageViews&&s.page(),Pe(window.location.pathname)},I=()=>d();window.addEventListener("popstate",I),n.push(()=>window.removeEventListener("popstate",I));let N=window;if(!N.__sendoraHistoryPatched){N.__sendoraHistoryPatched=!0;let G=history.pushState,Le=history.replaceState;history.pushState=function(...ne){let ie=G.apply(this,ne);return window.dispatchEvent(new Event("sendora:locationchange")),ie},history.replaceState=function(...ne){let ie=Le.apply(this,ne);return window.dispatchEvent(new Event("sendora:locationchange")),ie}}let se=()=>d();window.addEventListener("sendora:locationchange",se),n.push(()=>window.removeEventListener("sendora:locationchange",se))}if(t.appBackground||t.engagement){let d=()=>{if(document.visibilityState==="hidden"){z(),t.appBackground&&(w(!0),s.track("app.backgrounded",{sessionId:a()||void 0}));try{s.flush()}catch{}}else document.visibilityState==="visible"&&(t.appBackground&&(w(!0),s.track("app.foregrounded",{sessionId:a()||void 0})),t.engagement&&A===0&&(A=Date.now()))};if(document.addEventListener("visibilitychange",d),n.push(()=>document.removeEventListener("visibilitychange",d)),t.engagement){let I=()=>{z();try{s.flush()}catch{}};window.addEventListener("pagehide",I),n.push(()=>window.removeEventListener("pagehide",I))}}let pe=0,P=()=>{let d=Date.now();d-pe<5e3||(pe=d,w(!0))};return window.addEventListener("click",P,{passive:!0}),window.addEventListener("keydown",P,{passive:!0}),window.addEventListener("scroll",P,{passive:!0}),n.push(()=>{window.removeEventListener("click",P),window.removeEventListener("keydown",P),window.removeEventListener("scroll",P)}),()=>{z();for(let d of n)try{d()}catch{}}}function Re(s){try{return window.sessionStorage?.getItem(s)??null}catch{return null}}function _e(s,e){try{window.sessionStorage?.setItem(s,e)}catch{}}var B=class s{constructor(e){this.http=e}static isSupported(){return typeof window>"u"?!1:"serviceWorker"in navigator&&"PushManager"in window&&"Notification"in window}static permission(){return typeof Notification>"u"?"denied":Notification.permission}async subscribe(e){if(!s.isSupported())throw new Error("[sendoracloud] Web Push not supported in this browser.");let t=Notification.permission;if(t==="default"&&(t=await Notification.requestPermission()),t!=="granted")throw new Error(`[sendoracloud] Notification permission ${t}.`);let r=await navigator.serviceWorker.getRegistration(e.scope??"/");r||(r=await navigator.serviceWorker.register(e.serviceWorkerPath,{scope:e.scope??"/"})),await navigator.serviceWorker.ready;let i=(await this.http.get("/push/vapid"))?.data?.publicKey;if(!i)throw new Error("[sendoracloud] backend returned no VAPID public key.");let a=await r.pushManager.getSubscription()??await r.pushManager.subscribe({userVisibleOnly:!0,applicationServerKey:ot(i)}),c=a.toJSON(),l=c.endpoint,g=c.keys?.p256dh??"",v=c.keys?.auth??"",w=await at(l),k=await this.http.post("/push/tokens",{platform:"web",token:w,webEndpoint:l,webP256dh:g,webAuth:v,userAgent:typeof navigator<"u"?navigator.userAgent:void 0,locale:typeof navigator<"u"?navigator.language:void 0,timezone:Intl?.DateTimeFormat?.().resolvedOptions?.().timeZone,...e.userId?{userId:e.userId}:{}});if(!k?.data?.tokenId)throw new Error("[sendoracloud] backend returned no tokenId on subscribe.");return{tokenId:k.data.tokenId,subscription:a}}async unsubscribe(e="/"){if(!s.isSupported())return!1;let t=await navigator.serviceWorker.getRegistration(e);if(!t)return!1;let r=await t.pushManager.getSubscription();return r?await r.unsubscribe():!1}async trackOpen(e,t){let r={sendId:e};t!==void 0&&(r.clickAction=t);let n=await this.http.post("/push/track-open",r);if(!n.success)throw new Error(n.error?.message??"push.trackOpen failed")}};function ot(s){let e="=".repeat((4-s.length%4)%4),t=(s+e).replace(/-/g,"+").replace(/_/g,"/"),r=atob(t),n=new Uint8Array(r.length);for(let i=0;i<r.length;i++)n[i]=r.charCodeAt(i);return n}async function at(s){let e=new TextEncoder().encode(s),t=await crypto.subtle.digest("SHA-256",e);return Array.from(new Uint8Array(t)).map(n=>n.toString(16).padStart(2,"0")).join("")}var q=class{constructor(e){}unsupported(){throw new Error("[SendoraCloud] sendora.audiences is not available in the browser SDK. Audiences require a secret (sk_*) key with the audiences:read/write scopes and must be managed from a trusted server, not the browser.")}list(){return this.unsupported()}get(e){return this.unsupported()}create(e){return this.unsupported()}update(e,t){return this.unsupported()}delete(e){return this.unsupported()}};var p=class extends Error{constructor(e,t,r){super(t),this.name="LinkError",this.code=e,this.statusCode=r}};function le(s,e){let t=(s??"").toUpperCase();return t==="NOT_FOUND"?new p("NOT_FOUND",e):t==="FORBIDDEN"||t==="UNAUTHORIZED"?new p("UNAUTHORIZED",e):t==="RATE_LIMITED"?new p("RATE_LIMITED",e):t==="ENTITLEMENT_ERROR"||/plan limit/i.test(e)?new p("PLAN_LIMIT",e):t==="VALIDATION_ERROR"?/iOS bundle|Android package/i.test(e)?new p("BUNDLE_MISMATCH",e):/2KB|10KB|linkData/i.test(e)?new p("DATA_TOO_LARGE",e):/fallbackUrl/i.test(e)&&/apps/i.test(e)?new p("FALLBACK_REQUIRED",e):new p("INVALID_INPUT",e):t==="PARSE_ERROR"||t==="HTTP_ERROR"||/HTTP \d{3}/.test(e)?new p("NETWORK",e):new p("UNKNOWN",e||`Unknown LinkError (code=${s??"none"})`)}var de=/^[a-z0-9-]{3,20}$/;function ct(s){try{let t=new URL(s).pathname.split("/").filter(Boolean);if(t.length===0)return null;let r=t[t.length-1];return de.test(r)?r:null}catch{return null}}var ut=5*6e4,lt=50,dt=5,j=class{constructor(e){this.http=e;this.prewarmCache=new Map;this.prewarmInflight=0}cacheKey(e,t){if(t)return`k:${t}`;let r={},n=e;for(let i of Object.keys(n).sort())r[i]=n[i];return`s:${JSON.stringify(r)}`}evictExpired(){let e=Date.now();for(let[t,r]of this.prewarmCache)e-r.ts>ut&&this.prewarmCache.delete(t);for(;this.prewarmCache.size>lt;){let t=this.prewarmCache.keys().next().value;if(t===void 0)break;this.prewarmCache.delete(t)}}prewarm(e,t){this.evictExpired();let r=this.cacheKey(e,t?.key);if(this.prewarmCache.has(r)||this.prewarmInflight>=dt)return;this.prewarmInflight++;let n=this.doCreate(e).catch(i=>{throw this.prewarmCache.delete(r),i}).finally(()=>{this.prewarmInflight--});this.prewarmCache.set(r,{promise:n,ts:Date.now()})}async create(e,t){if(!e.title)throw new p("INVALID_INPUT","links.create: title is required");this.evictExpired();let r=this.cacheKey(e,t?.key),n=this.prewarmCache.get(r);return n?(this.prewarmCache.delete(r),n.promise):this.doCreate(e)}async doCreate(e){let t={title:e.title};e.fallbackUrl&&(t.fallbackUrl=e.fallbackUrl),e.iosDeepLinkPath&&(t.iosDeepLinkPath=e.iosDeepLinkPath),e.androidDeepLinkPath&&(t.androidDeepLinkPath=e.androidDeepLinkPath),e.linkData&&(t.linkData=e.linkData),e.ogTitle&&(t.ogTitle=e.ogTitle),e.ogDescription&&(t.ogDescription=e.ogDescription),e.ogImageUrl&&(t.ogImageUrl=e.ogImageUrl),e.campaign&&(t.campaign=e.campaign),e.source&&(t.source=e.source),e.medium&&(t.medium=e.medium),e.channel&&(t.channel=e.channel),e.tags&&(t.tags=e.tags),e.expiresAt&&(t.expiresAt=e.expiresAt),e.iosBundleId&&(t.iosBundleId=e.iosBundleId),e.androidPackageName&&(t.androidPackageName=e.androidPackageName);let r=await this.http.post("/sdk/links",t);if(!r.success||!r.data)throw le(r.error?.code,r.error?.message??"links.create failed");return r.data}async revoke(e){if(!de.test(e))throw new p("INVALID_INPUT",`links.revoke: '${e}' is not a valid shortcode`);let t=await this.http.post(`/sdk/links/${encodeURIComponent(e)}/revoke`,{});if(!t.success||!t.data)throw le(t.error?.code,t.error?.message??"links.revoke failed");return{shortcode:t.data.shortcode,revoked:!0}}async getStats(e){if(!de.test(e))throw new p("INVALID_INPUT",`links.getStats: '${e}' is not a valid shortcode`);let t=await this.http.get(`/sdk/links/${encodeURIComponent(e)}/stats`);if(!t.success||!t.data)throw le(t.error?.code,t.error?.message??"links.getStats failed");return t.data}};var J=class{constructor(e){this.http=e}async reportInstall(e={}){let t=await this.http.post("/attribution/install",e);if(!t.success||!t.data)throw new Error(t.error?.message??"attribution.reportInstall failed");return t.data}async checkDeferred(e={}){let t=await this.http.post("/attribution/deferred",e);if(!t.success)throw new Error(t.error?.message??"attribution.checkDeferred failed");return t.data??null}};var Z=class{constructor(e){this.http=e}async createTicket(e){let t=await this.http.post("/support/tickets",e);if(!t.success||!t.data)throw new Error(t.error?.message??"support.createTicket failed");return t.data}async submitCsat(e){let t=await this.http.post("/support/csat",e);if(!t.success||!t.data)throw new Error(t.error?.message??"support.submitCsat failed");return t.data}};var ee=class{constructor(e){this.http=e}async search(e){if(!e.query||e.query.trim().length===0)return[];let t=new URLSearchParams({query:e.query});e.limit&&t.set("limit",String(e.limit));let r=await this.http.get(`/kb/search?${t.toString()}`);if(!r.success||!Array.isArray(r.data))throw new Error(r.error?.message??"kb.search failed");return r.data}};var te=class te{constructor(e){this.autoTrackCleanup=null;this.http=new Y(e),this.storage=new Q,this.consent=new K(e.defaultConsent===!0,this.http),this.analytics=new x(e,this.http,this.storage,this.consent),this.flags=new O(e,this.http,this.storage),this.surveys=new M(e,this.http,this.storage),this.messages=new H(e,this.http,this.storage),this.chatbot=new F(e,this.http,this.storage),this.auth=new $(e,this.http,this.storage,this.analytics,this.flags,this.chatbot),this.webPush=new B(this.http),this.audiences=new q(this.http),this.links=new j(this.http),this.attribution=new J(this.http),this.support=new Z(this.http),this.kb=new ee(this.http),e.autoTrack!==!1&&(this.autoTrackCleanup=Ce(this.analytics,e))}static init(e){return we(e.apiKey),new te(e)}static sanitizeHtml(e){return ue(e)}static safeUrl(e){return E(e)}track(e,t){this.analytics.track(e,t)}page(e){this.analytics.page(e)}identify(e,t,r){this.analytics.identify(e,t,r),this.flags.setContext({userId:e})}reset(){this.analytics.reset(),this.flags.reset(),this.chatbot.newSession()}async flush(){await this.analytics.flush()}destroy(){this.autoTrackCleanup&&(this.autoTrackCleanup(),this.autoTrackCleanup=null),this.analytics.destroy(),this.flags.destroy(),this.messages.destroy()}};te.version=b;var he=te,or=he;export{W as AlreadyIdentifiedError,x as Analytics,q as Audiences,$ as Auth,u as AuthError,F as Chatbot,K as Consent,U as CredentialInUseError,D as EmailAlreadyTakenError,O as FeatureFlags,H as InAppMessaging,p as LinkError,j as Links,he as SendoraCloud,M as Surveys,f as ValidationError,B as WebPush,or as default,ct as extractShortcodeFromUrl,E as safeUrl,ue as sanitizeHtml};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sendoracloud/sdk-web",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.14.0",
|
|
4
4
|
"description": "Sendora Cloud Web SDK — analytics, feature flags, surveys, in-app messaging, chatbot, auth, deep links (Branch / Firebase parity). Zero dependencies, TypeScript, SSR-safe.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.cjs",
|