@sendoracloud/sdk-web 3.9.0 → 3.10.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 +22 -0
- package/dist/index.d.ts +22 -0
- 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.9.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 u=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 l;try{l=await u.json()}catch{l={success:!1,error:{code:"PARSE_ERROR",message:`HTTP ${u.status}`}}}if(this.debug&&console.debug(`[SendoraCloud] ${e} ${t} \u2192 ${u.status}`),!u.ok)if(t.startsWith("/auth-service/")&&!this.debug)console.warn(`[SendoraCloud] ${e} ${t} \u2192 HTTP ${u.status}`);else{let f=l?.error?.code??"HTTP_ERROR",m=l?.error?.message??`HTTP ${u.status}`,w=l?.error?.details?.fieldErrors,R=w?` (${Object.entries(w).map(([v,se])=>`${v}: ${(se??[]).join(", ")}`).join("; ")})`:"";console.warn(`[SendoraCloud] ${e} ${t} \u2192 HTTP ${u.status} \u2014 ${f}: ${m}${R}`)}return l}catch(u){a&&clearTimeout(a);let l=u instanceof Error&&u.name==="AbortError"?"timeout":"error";return console.warn(`[SendoraCloud] ${e} ${t} network ${l}`),{success:!1,error:{code:"NETWORK_ERROR",message:l}}}}};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 be=500,ce=10,ue=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>=be&&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>=ue&&(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 ${ue}ms elapse.`));return}let i=Math.min(ue,1e3*2**this.consecutiveFailures);this.nextFlushAfter=Date.now()+i;let o=Math.max(0,be-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)le(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&&le(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)le(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 le(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?(Ie(t),t.innerHTML):""}function Ie(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")),Ie(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.http=t,this.storage=r,this.analytics=n,this.flags=i,this.chatbot=o;let a=this.storage.get(N),u=this.storage.get(T),l=this.storage.get(g),I=this.storage.get(E);if(a&&(u||l))try{let f=JSON.parse(a);if(typeof f.id!="string"||f.id.length===0)throw new Error("invalid cache");this.user=f,u?(this.accessToken=u,this.accessExpiresAt=I?Number(I):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{}}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??"",u=o.error?.status;if(a==="NOT_ANONYMOUS"||a==="FORBIDDEN_NON_ANONYMOUS")throw new q(o.error?.message);if(a==="CONFLICT"||a==="EMAIL_ALREADY_TAKEN"||u===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))}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 l=await navigator.credentials.get({publicKey:i});if(!l)throw new Error("get returned null");o=l}catch(l){throw new c("PASSKEY_USER_CANCELLED",l instanceof Error?l.message:"User cancelled passkey signin")}let a=nt(o),u=await this.http.post("/auth-service/passkeys/authenticate/finish",{response:a,userId:e??null,...r?{prevAnonRefreshToken:r}:{}});if(!u.success||!y(u.data))throw new c(u.error?.code??"PASSKEY_AUTH_FAILED",u.error?.message??"Passkey signin failed");return this.persist(u.data),u.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 u=st(a.accessToken),l={id:u?.sub??"",email:u?.email??null,emailVerified:u?.email_verified??!0,name:u?.name??null,isAnonymous:!1};return this.user=l,this.storage.set(N,JSON.stringify(l)),this.analytics.identify(l.id),this.flags.setContext({userId:l.id}),i&&this.fireDeviceTakeover(i,l.id),l})}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 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(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)}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 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 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: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 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: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 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 u(d){Ee(i,d)}function l(){let d=Te(o);return d&&Number(d)||0}function I(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(),$=l();if(!a()||$&&k-$>r){let j=f();u(j),d&&t.sessionStart&&s.track("session.start",{sessionId:j})}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 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)}),u=a.toJSON(),l=u.endpoint,I=u.keys?.p256dh??"",f=u.keys?.auth??"",m=await ct(l),w=await this.http.post("/push/tokens",{platform:"web",token:m,webEndpoint:l,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 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 ut=5*6e4,lt=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>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 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 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.10.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 be=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>=be&&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,be-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?(Ie(t),t.innerHTML):""}function Ie(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")),Ie(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),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(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))}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 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 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: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 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: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 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 I(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})}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 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,I=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: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 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});
|
package/dist/index.d.cts
CHANGED
|
@@ -435,6 +435,16 @@ interface DeviceTakeoverEvent {
|
|
|
435
435
|
at: Date;
|
|
436
436
|
}
|
|
437
437
|
type DeviceTakeoverListener = (evt: DeviceTakeoverEvent) => void;
|
|
438
|
+
/**
|
|
439
|
+
* Detail handed to `onDeletionCancelled` subscribers (s58.269). Fires once
|
|
440
|
+
* per sign-in that cancelled a pending self-service deletion within grace —
|
|
441
|
+
* the account is restored with the SAME user_id.
|
|
442
|
+
*/
|
|
443
|
+
interface DeletionCancelledEvent {
|
|
444
|
+
userId: string;
|
|
445
|
+
at: Date;
|
|
446
|
+
}
|
|
447
|
+
type DeletionCancelledListener = (evt: DeletionCancelledEvent) => void;
|
|
438
448
|
declare class EmailAlreadyTakenError extends Error {
|
|
439
449
|
constructor(message?: string);
|
|
440
450
|
}
|
|
@@ -485,6 +495,8 @@ declare class Auth {
|
|
|
485
495
|
private proactiveRefreshCleanup;
|
|
486
496
|
private takeoverListeners;
|
|
487
497
|
private lastTakeover;
|
|
498
|
+
private deletionCancelledListeners;
|
|
499
|
+
private lastDeletionCancelled;
|
|
488
500
|
constructor(_config: SendoraConfig, http: HttpClient, storage: Storage, analytics: Analytics, flags: FeatureFlags, chatbot: Chatbot);
|
|
489
501
|
/** Returns the cached current user or null if signed out. */
|
|
490
502
|
getCurrentUser(): AuthUser | null;
|
|
@@ -521,6 +533,16 @@ declare class Auth {
|
|
|
521
533
|
* sink in the host app.
|
|
522
534
|
*/
|
|
523
535
|
private fireDeviceTakeover;
|
|
536
|
+
/**
|
|
537
|
+
* Subscribe to deletion-cancelled events (s58.269): fires when a sign-in
|
|
538
|
+
* cancelled a pending self-service account deletion within its grace window
|
|
539
|
+
* (the account is restored, same user_id). Surface "your deletion was
|
|
540
|
+
* cancelled" + reconcile local state. Returns an unsubscribe function.
|
|
541
|
+
*/
|
|
542
|
+
onDeletionCancelled(listener: DeletionCancelledListener): () => void;
|
|
543
|
+
/** The most recent deletion-cancelled event this session, or null. Cleared on signOut. */
|
|
544
|
+
getLastDeletionCancelled(): DeletionCancelledEvent | null;
|
|
545
|
+
private fireDeletionCancelled;
|
|
524
546
|
/**
|
|
525
547
|
* Returns the stored anon refresh token iff the local subject is
|
|
526
548
|
* currently anonymous. Used by every identified-session-mint path
|
package/dist/index.d.ts
CHANGED
|
@@ -435,6 +435,16 @@ interface DeviceTakeoverEvent {
|
|
|
435
435
|
at: Date;
|
|
436
436
|
}
|
|
437
437
|
type DeviceTakeoverListener = (evt: DeviceTakeoverEvent) => void;
|
|
438
|
+
/**
|
|
439
|
+
* Detail handed to `onDeletionCancelled` subscribers (s58.269). Fires once
|
|
440
|
+
* per sign-in that cancelled a pending self-service deletion within grace —
|
|
441
|
+
* the account is restored with the SAME user_id.
|
|
442
|
+
*/
|
|
443
|
+
interface DeletionCancelledEvent {
|
|
444
|
+
userId: string;
|
|
445
|
+
at: Date;
|
|
446
|
+
}
|
|
447
|
+
type DeletionCancelledListener = (evt: DeletionCancelledEvent) => void;
|
|
438
448
|
declare class EmailAlreadyTakenError extends Error {
|
|
439
449
|
constructor(message?: string);
|
|
440
450
|
}
|
|
@@ -485,6 +495,8 @@ declare class Auth {
|
|
|
485
495
|
private proactiveRefreshCleanup;
|
|
486
496
|
private takeoverListeners;
|
|
487
497
|
private lastTakeover;
|
|
498
|
+
private deletionCancelledListeners;
|
|
499
|
+
private lastDeletionCancelled;
|
|
488
500
|
constructor(_config: SendoraConfig, http: HttpClient, storage: Storage, analytics: Analytics, flags: FeatureFlags, chatbot: Chatbot);
|
|
489
501
|
/** Returns the cached current user or null if signed out. */
|
|
490
502
|
getCurrentUser(): AuthUser | null;
|
|
@@ -521,6 +533,16 @@ declare class Auth {
|
|
|
521
533
|
* sink in the host app.
|
|
522
534
|
*/
|
|
523
535
|
private fireDeviceTakeover;
|
|
536
|
+
/**
|
|
537
|
+
* Subscribe to deletion-cancelled events (s58.269): fires when a sign-in
|
|
538
|
+
* cancelled a pending self-service account deletion within its grace window
|
|
539
|
+
* (the account is restored, same user_id). Surface "your deletion was
|
|
540
|
+
* cancelled" + reconcile local state. Returns an unsubscribe function.
|
|
541
|
+
*/
|
|
542
|
+
onDeletionCancelled(listener: DeletionCancelledListener): () => void;
|
|
543
|
+
/** The most recent deletion-cancelled event this session, or null. Cleared on signOut. */
|
|
544
|
+
getLastDeletionCancelled(): DeletionCancelledEvent | null;
|
|
545
|
+
private fireDeletionCancelled;
|
|
524
546
|
/**
|
|
525
547
|
* Returns the stored anon refresh token iff the local subject is
|
|
526
548
|
* currently anonymous. Used by every identified-session-mint path
|
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.9.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 I=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 u=await fetch(i,{method:e,headers:{"Content-Type":"application/json","X-API-Key":this.apiKey,"X-Sendora-SDK-Name":xe,"X-Sendora-SDK-Version":I,...n??{}},body:r?JSON.stringify(r):void 0,signal:o?o.signal:void 0});a&&clearTimeout(a);let l;try{l=await u.json()}catch{l={success:!1,error:{code:"PARSE_ERROR",message:`HTTP ${u.status}`}}}if(this.debug&&console.debug(`[SendoraCloud] ${e} ${t} \u2192 ${u.status}`),!u.ok)if(t.startsWith("/auth-service/")&&!this.debug)console.warn(`[SendoraCloud] ${e} ${t} \u2192 HTTP ${u.status}`);else{let f=l?.error?.code??"HTTP_ERROR",m=l?.error?.message??`HTTP ${u.status}`,w=l?.error?.details?.fieldErrors,R=w?` (${Object.entries(w).map(([v,te])=>`${v}: ${(te??[]).join(", ")}`).join("; ")})`:"";console.warn(`[SendoraCloud] ${e} ${t} \u2192 HTTP ${u.status} \u2014 ${f}: ${m}${R}`)}return l}catch(u){a&&clearTimeout(a);let l=u instanceof Error&&u.name==="AbortError"?"timeout":"error";return console.warn(`[SendoraCloud] ${e} ${t} network ${l}`),{success:!1,error:{code:"NETWORK_ERROR",message:l}}}}};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:I}}:{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:I}}}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?(be(t),t.innerHTML):""}function be(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")),be(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.http=t,this.storage=r,this.analytics=n,this.flags=i,this.chatbot=o;let a=this.storage.get(P),u=this.storage.get(T),l=this.storage.get(g),b=this.storage.get(E);if(a&&(u||l))try{let f=JSON.parse(a);if(typeof f.id!="string"||f.id.length===0)throw new Error("invalid cache");this.user=f,u?(this.accessToken=u,this.accessExpiresAt=b?Number(b):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{}}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??"",u=o.error?.status;if(a==="NOT_ANONYMOUS"||a==="FORBIDDEN_NON_ANONYMOUS")throw new Y(o.error?.message);if(a==="CONFLICT"||a==="EMAIL_ALREADY_TAKEN"||u===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))}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 l=await navigator.credentials.get({publicKey:i});if(!l)throw new Error("get returned null");o=l}catch(l){throw new c("PASSKEY_USER_CANCELLED",l instanceof Error?l.message:"User cancelled passkey signin")}let a=Xe(o),u=await this.http.post("/auth-service/passkeys/authenticate/finish",{response:a,userId:e??null,...r?{prevAnonRefreshToken:r}:{}});if(!u.success||!y(u.data))throw new c(u.error?.code??"PASSKEY_AUTH_FAILED",u.error?.message??"Passkey signin failed");return this.persist(u.data),u.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 u=Ye(a.accessToken),l={id:u?.sub??"",email:u?.email??null,emailVerified:u?.email_verified??!0,name:u?.name??null,isAnonymous:!1};return this.user=l,this.storage.set(P,JSON.stringify(l)),this.analytics.identify(l.id),this.flags.setContext({userId:l.id}),i&&this.fireDeviceTakeover(i,l.id),l})}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 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(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)}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,Ie=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 u(d){Te(i,d)}function l(){let d=Se(o);return d&&Number(d)||0}function b(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=l();if(!a()||D&&k-D>r){let B=f();u(B),d&&t.sessionStart&&s.track("session.start",{sessionId:B})}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 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>Ie&&(d=Ie);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)}),u=a.toJSON(),l=u.endpoint,b=u.keys?.p256dh??"",f=u.keys?.auth??"",m=await et(l),w=await this.http.post("/push/tokens",{platform:"web",token:m,webEndpoint:l,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 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 ue(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 le=/^[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 le.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 ue(r.error?.code,r.error?.message??"links.create failed");return r.data}async revoke(e){if(!le.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 ue(t.error?.code,t.error?.message??"links.revoke failed");return{shortcode:t.data.shortcode,revoked:!0}}async getStats(e){if(!le.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 ue(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=I;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 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.10.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 I=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":I,...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:I}}:{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:I}}}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?(be(t),t.innerHTML):""}function be(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")),be(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),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(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))}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,Ie=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 b(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})}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 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>Ie&&(d=Ie);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,b=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: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 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=I;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};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sendoracloud/sdk-web",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.10.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",
|