@sendoracloud/sdk-web 3.2.0 → 3.2.1
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 +50 -36
- package/dist/index.d.ts +50 -36
- package/dist/index.js +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";var ne=Object.defineProperty;var Re=Object.getOwnPropertyDescriptor;var Ee=Object.getOwnPropertyNames;var Ce=Object.prototype.hasOwnProperty;var _e=(r,e)=>{for(var t in e)ne(r,t,{get:e[t],enumerable:!0})},Pe=(r,e,t,s)=>{if(e&&typeof e=="object"||typeof e=="function")for(let n of Ee(e))!Ce.call(r,n)&&n!==t&&ne(r,n,{get:()=>e[n],enumerable:!(s=Re(e,n))||s.enumerable});return r};var Le=r=>Pe(ne({},"__esModule",{value:!0}),r);var ct={};_e(ct,{Analytics:()=>_,Audiences:()=>H,Auth:()=>N,AuthError:()=>c,Chatbot:()=>U,Consent:()=>x,EmailAlreadyTakenError:()=>O,FeatureFlags:()=>P,InAppMessaging:()=>D,LinkError:()=>h,Links:()=>F,SendoraCloud:()=>J,Surveys:()=>L,ValidationError:()=>p,WebPush:()=>M,default:()=>at,extractShortcodeFromUrl:()=>be,safeUrl:()=>A,sanitizeHtml:()=>G});module.exports=Le(ct);var ue=/^[a-zA-Z][a-zA-Z0-9._:-]{0,127}$/;var De=new Set(["__proto__","constructor","prototype"]),p=class extends Error{constructor(e){super(`[SendoraCloud] ${e}`),this.name="SendoraValidationError"}};function le(r){if(typeof r!="string"||!ue.test(r))throw new p(`Invalid event name: must match ${ue}, got "${String(r)}".`)}function de(r){if(r==null)return;if(typeof r!="object"||Array.isArray(r))throw new p("Properties must be a plain object, not an array or primitive.");let e=he(r);if(e>5)throw new p(`Properties nested too deeply (${e} levels, max 5).`);pe(r);let t;try{t=JSON.stringify(r).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 he(r,e=0){if(e>6||r==null||typeof r!="object")return e;let t=e;for(let s of Object.keys(r)){let n=he(r[s],e+1);n>t&&(t=n)}return t}function pe(r){if(!(r==null||typeof r!="object"))for(let e of Object.keys(r)){if(De.has(e))throw new p(`Forbidden property key "${e}" (prototype pollution guard).`);pe(r[e])}}var Ue=/^pk_[A-Za-z0-9_-]{16,}$/;function ge(r){if(!r||typeof r!="string")throw new p("apiKey is required.");if(r.startsWith("sk_")||r.startsWith("sendora_secret_"))throw new p("Secret keys must NEVER be used in browser SDK. Use a publishable key (pk_...).");if(!Ue.test(r))throw new p("Invalid publishable key format. Expected pk_... (17+ chars).")}function fe(r){let e=r.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 xe="https://api.sendoracloud.com",Oe=1e4,B=class{constructor(e){let t=(e.apiUrl??xe).replace(/\/$/,"");fe(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,s){return this.request("POST",e,t,s)}patch(e,t,s){return this.request("PATCH",e,t,s)}delete(e,t){return this.request("DELETE",e,void 0,t)}async request(e,t,s,n){let i=`${this.baseUrl}/api/v1${t}`,o=typeof AbortController<"u"?new AbortController:null,a=o?setTimeout(()=>o.abort(),Oe):null;try{let u=await fetch(i,{method:e,headers:{"Content-Type":"application/json","X-API-Key":this.apiKey,...n??{}},body:s?JSON.stringify(s):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,ee])=>`${v}: ${(ee??[]).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 z="sendora_",j=class{constructor(){this.memoryStore=new Map;this.useLocalStorage=this.checkLocalStorage()}get(e){let t=z+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 s=z+e;if(this.useLocalStorage)try{localStorage.setItem(s,t);return}catch{}this.memoryStore.set(s,t)}remove(e){let t=z+e;if(this.useLocalStorage)try{localStorage.removeItem(t)}catch{}this.memoryStore.delete(t)}clearPrefix(e){let t=z+e;if(this.useLocalStorage)try{for(let s=localStorage.length-1;s>=0;s--){let n=localStorage.key(s);n&&n.startsWith(t)&&localStorage.removeItem(n)}}catch{}for(let s of Array.from(this.memoryStore.keys()))s.startsWith(t)&&this.memoryStore.delete(s)}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 C(r=""){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 s="";for(let n of e)s+=n.toString(16).padStart(2,"0");return r+s}var me="2.9.0",ye=500,Ne=10,Me=6e4,_=class{constructor(e,t,s,n){this.queue=[];this.timer=null;this.userId=null;this.identityToken=null;this.consecutiveFailures=0;this.nextFlushAfter=0;this.http=t,this.storage=s,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;let i=this.storage.get("anonymous_id");if(i?this.anonymousId=i:(this.anonymousId=C("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){le(e),t&&de(t),this.queue.length>=ye&&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,s){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=s?.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=C("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;let e=this.queue.splice(0,100),t=e.map(o=>({projectId:this.projectId,module:"custom",eventType:o.eventType,userId:o.userId,anonymousId:o.anonymousId,identityToken:o.identityToken,timestamp:o.timestamp??new Date().toISOString(),properties:o.properties??{},context:this.getContext(),consent:["analytics"]}));if((await this.http.post("/events/batch",{events:t})).success){this.consecutiveFailures=0;return}if(this.consecutiveFailures++,this.consecutiveFailures>Ne)return;let n=Math.min(Me,1e3*2**this.consecutiveFailures);this.nextFlushAfter=Date.now()+n;let i=Math.max(0,ye-this.queue.length);i>0&&this.queue.unshift(...e.slice(0,i))}destroy(){this.timer&&clearInterval(this.timer),this.timer=null,this.flush()}getContext(){return typeof window>"u"?{sdk:{name:"@sendoracloud/sdk-web",version:me}}:{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,sdk:{name:"@sendoracloud/sdk-web",version:me}}}};var P=class{constructor(e,t,s){this.cache=new Map;this.refreshInterval=null;this.http=t,this.storage=s;let n=this.storage.get("flags");if(n)try{let i=JSON.parse(n);if(Array.isArray(i))for(let o of i)ie(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 s=await this.http.post("/feature-flags/evaluate",{key:e,userId:this.userId,entityId:this.entityId,environment:this.environment});return s.success&&s.data&&ie(s.data)?(this.cache.set(e,s.data),this.persistCache(),s.data.value):null}async isEnabled(e,t=!1){let s=await this.getFlag(e);return typeof s=="boolean"?s: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)ie(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,s]of this.cache)e[t]=s.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 ie(r){if(r==null||typeof r!="object")return!1;let e=r;return Object.prototype.hasOwnProperty.call(e,"__proto__")||Object.prototype.hasOwnProperty.call(e,"constructor")?!1:typeof e.key=="string"&&"value"in e}var He=32*1024,L=class{constructor(e,t,s){this.activeSurvey=null;this.http=t,this.storage=s}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 s=await this.http.get(`/surveys/${encodeURIComponent(e)}/config`);return!s.success||!s.data||!Fe(s.data)?null:(this.activeSurvey=s.data,this.storage.set(t,"1"),this.onSurveyDisplay&&this.onSurveyDisplay(s.data),s.data)}async submitResponse(e,t,s=!0){let n=0;try{n=JSON.stringify(t).length}catch{return!1}if(n>He)return!1;let i=await this.http.post("/surveys/responses",{surveyId:e,answers:t,completed:s});return i.success&&(this.activeSurvey=null),i.success}dismiss(){this.activeSurvey=null}getActiveSurvey(){return this.activeSurvey}};function Fe(r){if(r==null||typeof r!="object")return!1;let e=r;return typeof e.id!="string"||typeof e.name!="string"||!Array.isArray(e.questions)?!1:e.questions.every(Ke)}function Ke(r){if(r==null||typeof r!="object")return!1;let e=r;return typeof e.id=="string"&&typeof e.text=="string"}function A(r){if(!(!r||typeof r!="string"))try{let e=typeof window<"u"?window.location.href:"https://local/",t=new URL(r,e);return t.protocol==="http:"||t.protocol==="https:"?t.href:void 0}catch{return}}var $e=new Set(["b","i","u","em","strong","a","p","br","span","ul","ol","li","h1","h2","h3","h4"]),Ve={a:new Set(["href","target","rel"])};function G(r){if(typeof DOMParser>"u")return"";let t=new DOMParser().parseFromString(`<div>${r}</div>`,"text/html").body.firstElementChild;return t?(we(t),t.innerHTML):""}function we(r){let e=Array.from(r.children);for(let t of e){let s=t.tagName.toLowerCase();if(!$e.has(s)){let i=document.createTextNode(t.textContent??"");t.replaceWith(i);continue}let n=Ve[s]??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)}}s==="a"&&(t.setAttribute("rel","noopener noreferrer"),t.setAttribute("target","_blank")),we(t)}}var D=class{constructor(e,t,s){this.messages=[];this.pollInterval=null;this.http=t,this.storage=s}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(qe);for(let t of this.messages){let s=`iam_shown_${t.id}`;if(!this.storage.get(s))return this.storage.set(s,"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 qe(r){return{...r,content:{...r.content,ctaUrl:A(r.content.ctaUrl),imageUrl:A(r.content.imageUrl)}}}var We=4e3,U=class{constructor(e,t,s){this.chatbotId=null;this.messages=[];this.http=t,this.storage=s;let n=this.storage.get("chat_session_id");this.sessionId=n??C("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>We)return null;let s={role:"user",content:e,timestamp:new Date().toISOString()};this.messages.push(s);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=C("chat_"),this.storage.set("chat_session_id",this.sessionId)}getSessionId(){return this.sessionId}};var x=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 S="auth_access_token",T="auth_access_expires",g="auth_refresh_token",$="auth_user",Be=3e4,ze=/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;function y(r){if(!r||typeof r!="object")return!1;let e=r,t=e.user,s=e.tokens;return!(!t||typeof t.id!="string"||t.id.length===0||!s||typeof s.accessToken!="string"||s.accessToken.length===0||typeof s.refreshToken!="string"||s.refreshToken.length===0||typeof s.expiresIn!="number"||s.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"||e.retiredAnonUserId!==void 0&&typeof e.retiredAnonUserId!="string")}var O=class extends Error{constructor(e="An account with this email already exists"){super(e),this.name="EmailAlreadyTakenError"}},c=class extends Error{constructor(e,t){super(t),this.name="AuthError",this.code=e}},N=class{constructor(e,t,s,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=s,this.analytics=n,this.flags=i,this.chatbot=o;let a=this.storage.get($),u=this.storage.get(S),l=this.storage.get(g),b=this.storage.get(T);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($),this.storage.remove(S),this.storage.remove(T),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||!ze.test(e))return;let s={retiredAnonUserId:e,identifiedUserId:t,at:new Date};this.lastTakeover=s;for(let n of this.takeoverListeners)try{n(s)}catch{}}takeoverHint(){if(this.user?.isAnonymous)return this.storage.get(g)??void 0}async getAccessToken(){return this.accessToken?this.accessExpiresAt&&Date.now()<this.accessExpiresAt-Be?this.accessToken:this.refreshInflight?this.accessToken:Date.now()<this.refreshCooldownUntil?this.accessToken: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,s){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:s?.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==="CONFLICT"||a==="EMAIL_ALREADY_TAKEN"||u===409)throw new O(o.error?.message);if(a==="FORBIDDEN_NON_ANONYMOUS"||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:s?.name,metadata:s?.metadata});if(!i.success||!y(i.data)){let o=i.error?.code??"";throw o==="CONFLICT"?new O(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 s=this.user!==null,n=this.takeoverHint();s&&(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,s=this.takeoverHint();t&&(await this.analytics.flush(),this.wipeLocalIdentity());let n=await this.http.post("/auth-service/login/social",{...e,...s?{prevAnonRefreshToken:s}:{}});if(!n.success||!y(n.data))throw new c(n.error?.code??"SOCIAL_LOGIN_FAILED",n.error?.message??"Social login failed");return this.persist(n.data),n.data.user})}signInWithGoogle(e,t){return this.loginSocial({provider:"google",code:e,redirectUri:t})}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})}signInWithDiscord(e,t){return this.loginSocial({provider:"discord",code:e,redirectUri:t})}challengeMfa(e,t){return this.serialize(async()=>{let s=this.takeoverHint(),n=await this.http.post("/auth-service/mfa/challenge",{challengeToken:e,code:t,...s?{prevAnonRefreshToken:s}:{}});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 s=this.user!==null,n=this.takeoverHint();s&&(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 s=await this.http.post("/auth-service/password/reset",{token:e,newPassword:t});if(!s.success)throw new c(s.error?.code??"PASSWORD_RESET_FAILED",s.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,s=this.takeoverHint();t&&(await this.analytics.flush(),this.wipeLocalIdentity());let n=await this.http.post("/auth-service/magic-link/verify",{token:e,...s?{prevAnonRefreshToken:s}:{}});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",{},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},this.bearerHeaders());return!!(t.success&&t.data?.confirmed)}async disableMfa(){await this.http.post("/auth-service/mfa/disable",{},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",{},this.bearerHeaders());if(!t.success||!t.data)throw new c(t.error?.code??"PASSKEY_START_FAILED",t.error?.message??"Passkey start failed");let s=Ge(t.data),n;try{let a=await navigator.credentials.create({publicKey:s});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=Qe(n),o=await this.http.post("/auth-service/passkeys/register/finish",{response:i,name:e},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,s=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=Ye(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=Je(o),u=await this.http.post("/auth-service/passkeys/authenticate/finish",{response:a,userId:e??null,...s?{prevAnonRefreshToken:s}:{}});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",this.bearerHeaders());return!e.success||!Array.isArray(e.data)?[]:e.data}async deletePasskey(e){await this.http.delete(`/auth-service/passkeys/${e}`,this.bearerHeaders())}async listMySessions(){let e=await this.http.get("/auth-service/sessions/me",this.bearerHeaders());return!e.success||!Array.isArray(e.data)?[]:e.data}async revokeSession(e){await this.http.delete(`/auth-service/sessions/me/${e}`,this.bearerHeaders())}async revokeAllSessions(){await this.http.delete("/auth-service/sessions/me",this.bearerHeaders())}async startSso(e){let t=this.takeoverHint(),s=await this.http.post("/auth-service/sso/oidc/start",{returnTo:e,...t?{prevAnonRefreshToken:t}:{}});if(!s.success||!s.data)throw new c(s.error?.code??"SSO_START_FAILED",s.error?.message??"SSO start failed");return s.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 s=await this.startSso(t);typeof window<"u"&&(window.location.href=s.authorizationUrl)}async startSaml(e){let t=this.takeoverHint(),s=await this.http.post("/auth-service/sso/saml/start",{returnTo:e,...t?{prevAnonRefreshToken:t}:{}});if(!s.success||!s.data)throw new c(s.error?.code??"SAML_START_FAILED",s.error?.message??"SAML start failed");return s.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 s=await this.startSaml(t);typeof window<"u"&&(window.location.href=s.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),s=t.get("sendora_oidc_error")??t.get("sendora_saml_error");if(s)throw window.history.replaceState(null,"",window.location.pathname+window.location.search),new c("SSO_FAILED",decodeURIComponent(s));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(S,a.accessToken),this.storage.set(T,String(this.accessExpiresAt)),this.storage.set(g,a.refreshToken);let u=Xe(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($,JSON.stringify(l)),this.analytics.identify(l.id),this.flags.setContext({userId:l.id}),i&&this.fireDeviceTakeover(i,l.id),l})}bearerHeaders(){if(!this.accessToken)throw new c("NOT_SIGNED_IN","Not signed in");return{Authorization:`Bearer ${this.accessToken}`}}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,s=async()=>{let n=this.storage.get(g);if(n&&n!==e){let i=this.storage.get(S),o=this.storage.get(T);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 je(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(S,o.accessToken),this.storage.set(T,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"},s):await s()}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,s=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()*s*2-s);a<=l&&await this.refreshAccessToken()};this.proactiveRefreshTimer=window.setInterval(()=>{n()},t);let i=()=>{document.visibilityState==="visible"&&n()};document.addEventListener("visibilitychange",i),this.proactiveRefreshCleanup=()=>{this.proactiveRefreshTimer&&(window.clearInterval(this.proactiveRefreshTimer),this.proactiveRefreshTimer=null),document.removeEventListener("visibilitychange",i)}}stopProactiveRefreshCron(){this.proactiveRefreshCleanup&&(this.proactiveRefreshCleanup(),this.proactiveRefreshCleanup=null)}persist(e){this.user=e.user,this.accessToken=e.tokens.accessToken,this.accessExpiresAt=Date.now()+e.tokens.expiresIn*1e3,this.storage.set($,JSON.stringify(e.user)),this.storage.set(S,e.tokens.accessToken),this.storage.set(T,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($),this.storage.remove(S),this.storage.remove(T),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 je(r){return r?r==="INVALID_REFRESH_TOKEN"||r==="UNAUTHORIZED"||r==="HTTP_401"||r==="RATE_LIMIT_EXCEEDED"||r==="RATE_LIMIT":!1}function V(r){let e=(4-r.length%4)%4,t=(r+"===".slice(0,e)).replace(/-/g,"+").replace(/_/g,"/"),s=atob(t),n=new ArrayBuffer(s.length),i=new Uint8Array(n);for(let o=0;o<s.length;o++)i[o]=s.charCodeAt(o);return n}function I(r){let e=r instanceof Uint8Array?r:new Uint8Array(r),t="";for(let s=0;s<e.length;s++)t+=String.fromCharCode(e[s]);return btoa(t).replace(/\+/g,"-").replace(/\//g,"_").replace(/=+$/,"")}function Ge(r){return{challenge:V(r.challenge),rp:r.rp,user:{id:V(r.user.id),name:r.user.name,displayName:r.user.displayName},pubKeyCredParams:r.pubKeyCredParams,timeout:r.timeout,attestation:r.attestation,excludeCredentials:r.excludeCredentials?.map(e=>({id:V(e.id),type:"public-key",transports:e.transports})),authenticatorSelection:r.authenticatorSelection}}function Ye(r){return{challenge:V(r.challenge),timeout:r.timeout,rpId:r.rpId,allowCredentials:r.allowCredentials?.map(e=>({id:V(e.id),type:"public-key",transports:e.transports})),userVerification:r.userVerification}}function Qe(r){let e=r.response;return{id:r.id,rawId:I(r.rawId),type:r.type,response:{clientDataJSON:I(e.clientDataJSON),attestationObject:I(e.attestationObject),transports:typeof e.getTransports=="function"?e.getTransports():void 0},clientExtensionResults:r.getClientExtensionResults()}}function Xe(r){try{let e=r.split(".");if(e.length!==3)return null;let t=e[1].replace(/-/g,"+").replace(/_/g,"/"),s=atob(t+"===".slice(0,(4-t.length%4)%4));return JSON.parse(s)}catch{return null}}function Je(r){let e=r.response;return{id:r.id,rawId:I(r.rawId),type:r.type,response:{clientDataJSON:I(e.clientDataJSON),authenticatorData:I(e.authenticatorData),signature:I(e.signature),userHandle:e.userHandle?I(e.userHandle):null},clientExtensionResults:r.getClientExtensionResults()}}function Ze(r){return r===!1?{pageViews:!1,sessionStart:!1,appOpen:!1,appBackground:!1,engagement:!1}:r===!0||r===void 0?{pageViews:!0,sessionStart:!0,appOpen:!0,appBackground:!0,engagement:!0}:{pageViews:r.pageViews??!0,sessionStart:r.sessionStart??!0,appOpen:r.appOpen??!0,appBackground:r.appBackground??!0,engagement:r.engagement??!0}}var et=250,ve=360*60*1e3;function Ie(r,e){if(typeof window>"u")return()=>{};let t=Ze(e.autoTrack),s=e.sessionIdleMs??18e5,n=[],i="sendora_session_id",o="sendora_session_last_activity";function a(){return ke(i)??""}function u(d){Ae(i,d)}function l(){let d=ke(o);return d&&Number(d)||0}function b(d){Ae(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(),K=l();if(!a()||K&&k-K>s){let W=f();u(W),d&&t.sessionStart&&r.track("session.start",{sessionId:W})}b(k)}m(t.sessionStart),t.appOpen&&r.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 ee(){v>0&&(R+=Date.now()-v,v=0)}function q(){if(!t.engagement)return;ee();let d=R;if(R=0,!(d<et)){d>ve&&(d=ve);try{r.track("app.engagement",{durationMs:Math.round(d),screen:w,sessionId:a()||void 0})}catch{}}}function Se(d){w=d,R=0,v=document.visibilityState==="visible"?Date.now():0}if(t.pageViews||t.engagement){t.pageViews&&r.page();let d=()=>{q(),m(!0),t.pageViews&&r.page(),Se(window.location.pathname)},k=()=>d();window.addEventListener("popstate",k),n.push(()=>window.removeEventListener("popstate",k));let K=window;if(!K.__sendoraHistoryPatched){K.__sendoraHistoryPatched=!0;let W=history.pushState,Te=history.replaceState;history.pushState=function(...se){let re=W.apply(this,se);return window.dispatchEvent(new Event("sendora:locationchange")),re},history.replaceState=function(...se){let re=Te.apply(this,se);return window.dispatchEvent(new Event("sendora:locationchange")),re}}let te=()=>d();window.addEventListener("sendora:locationchange",te),n.push(()=>window.removeEventListener("sendora:locationchange",te))}if(t.appBackground||t.engagement){let d=()=>{if(document.visibilityState==="hidden"){q(),t.appBackground&&(m(!0),r.track("app.backgrounded",{sessionId:a()||void 0}));try{r.flush()}catch{}}else document.visibilityState==="visible"&&(t.appBackground&&(m(!0),r.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{r.flush()}catch{}};window.addEventListener("pagehide",k),n.push(()=>window.removeEventListener("pagehide",k))}}let ce=0,E=()=>{let d=Date.now();d-ce<5e3||(ce=d,m(!0))};return window.addEventListener("click",E,{passive:!0}),window.addEventListener("keydown",E,{passive:!0}),window.addEventListener("scroll",E,{passive:!0}),n.push(()=>{window.removeEventListener("click",E),window.removeEventListener("keydown",E),window.removeEventListener("scroll",E)}),()=>{q();for(let d of n)try{d()}catch{}}}function ke(r){try{return window.sessionStorage?.getItem(r)??null}catch{return null}}function Ae(r,e){try{window.sessionStorage?.setItem(r,e)}catch{}}var M=class r{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(!r.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 s=await navigator.serviceWorker.getRegistration(e.scope??"/");s||(s=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 s.pushManager.getSubscription()??await s.pushManager.subscribe({userVisibleOnly:!0,applicationServerKey:tt(i)}),u=a.toJSON(),l=u.endpoint,b=u.keys?.p256dh??"",f=u.keys?.auth??"",m=await st(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(!r.isSupported())return!1;let t=await navigator.serviceWorker.getRegistration(e);if(!t)return!1;let s=await t.pushManager.getSubscription();return s?await s.unsubscribe():!1}async trackOpen(e,t){let s={sendId:e};t!==void 0&&(s.clickAction=t);let n=await this.http.post("/push/track-open",s);if(!n.success)throw new Error(n.error?.message??"push.trackOpen failed")}};function tt(r){let e="=".repeat((4-r.length%4)%4),t=(r+e).replace(/-/g,"+").replace(/_/g,"/"),s=atob(t),n=new Uint8Array(s.length);for(let i=0;i<s.length;i++)n[i]=s.charCodeAt(i);return n}async function st(r){let e=new TextEncoder().encode(r),t=await crypto.subtle.digest("SHA-256",e);return Array.from(new Uint8Array(t)).map(n=>n.toString(16).padStart(2,"0")).join("")}var H=class{constructor(e){this.http=e}base(){return"/audiences"}async list(){let e=await this.http.get(this.base());return e.success?e.data??[]:[]}async get(e){let t=await this.http.get(`${this.base()}/${e}`);return t.success?t.data??null:null}async create(e){let t=await this.http.post(this.base(),e);return t.success?t.data??null:null}async update(e,t){let s=await this.http.patch(`${this.base()}/${e}`,t);return s.success?s.data??null:null}async delete(e){return(await this.http.delete(`${this.base()}/${e}`)).success}};var h=class extends Error{constructor(e,t,s){super(t),this.name="LinkError",this.code=e,this.statusCode=s}};function oe(r,e){let t=(r??"").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=${r??"none"})`)}var ae=/^[a-z0-9-]{3,20}$/;function be(r){try{let t=new URL(r).pathname.split("/").filter(Boolean);if(t.length===0)return null;let s=t[t.length-1];return ae.test(s)?s:null}catch{return null}}var rt=5*6e4,nt=50,it=5,F=class{constructor(e){this.http=e;this.prewarmCache=new Map;this.prewarmInflight=0}cacheKey(e,t){if(t)return`k:${t}`;let s={},n=e;for(let i of Object.keys(n).sort())s[i]=n[i];return`s:${JSON.stringify(s)}`}evictExpired(){let e=Date.now();for(let[t,s]of this.prewarmCache)e-s.ts>rt&&this.prewarmCache.delete(t);for(;this.prewarmCache.size>nt;){let t=this.prewarmCache.keys().next().value;if(t===void 0)break;this.prewarmCache.delete(t)}}prewarm(e,t){this.evictExpired();let s=this.cacheKey(e,t?.key);if(this.prewarmCache.has(s)||this.prewarmInflight>=it)return;this.prewarmInflight++;let n=this.doCreate(e).catch(i=>{throw this.prewarmCache.delete(s),i}).finally(()=>{this.prewarmInflight--});this.prewarmCache.set(s,{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 s=this.cacheKey(e,t?.key),n=this.prewarmCache.get(s);return n?(this.prewarmCache.delete(s),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 s=await this.http.post("/sdk/links",t);if(!s.success||!s.data)throw oe(s.error?.code,s.error?.message??"links.create failed");return s.data}async revoke(e){if(!ae.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 oe(t.error?.code,t.error?.message??"links.revoke failed");return{shortcode:t.data.shortcode,revoked:!0}}async getStats(e){if(!ae.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 oe(t.error?.code,t.error?.message??"links.getStats failed");return t.data}};var Y=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 Q=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 X=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 s=await this.http.get(`/kb/search?${t.toString()}`);if(!s.success||!Array.isArray(s.data))throw new Error(s.error?.message??"kb.search failed");return s.data}};var ot="3.0.1",Z=class Z{constructor(e){this.autoTrackCleanup=null;this.http=new B(e),this.storage=new j,this.consent=new x(e.defaultConsent===!0,this.http),this.analytics=new _(e,this.http,this.storage,this.consent),this.flags=new P(e,this.http,this.storage),this.surveys=new L(e,this.http,this.storage),this.messages=new D(e,this.http,this.storage),this.chatbot=new U(e,this.http,this.storage),this.auth=new N(e,this.http,this.storage,this.analytics,this.flags,this.chatbot),this.webPush=new M(this.http),this.audiences=new H(this.http),this.links=new F(this.http),this.attribution=new Y(this.http),this.support=new Q(this.http),this.kb=new X(this.http),e.autoTrack!==!1&&(this.autoTrackCleanup=Ie(this.analytics,e))}static init(e){return ge(e.apiKey),new Z(e)}static sanitizeHtml(e){return G(e)}static safeUrl(e){return A(e)}track(e,t){this.analytics.track(e,t)}page(e){this.analytics.page(e)}identify(e,t,s){this.analytics.identify(e,t,s),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()}};Z.version=ot;var J=Z,at=J;0&&(module.exports={Analytics,Audiences,Auth,AuthError,Chatbot,Consent,EmailAlreadyTakenError,FeatureFlags,InAppMessaging,LinkError,Links,SendoraCloud,Surveys,ValidationError,WebPush,extractShortcodeFromUrl,safeUrl,sanitizeHtml});
|
|
1
|
+
"use strict";var ie=Object.defineProperty;var _e=Object.getOwnPropertyDescriptor;var Pe=Object.getOwnPropertyNames;var De=Object.prototype.hasOwnProperty;var Le=(s,e)=>{for(var t in e)ie(s,t,{get:e[t],enumerable:!0})},Ue=(s,e,t,r)=>{if(e&&typeof e=="object"||typeof e=="function")for(let n of Pe(e))!De.call(s,n)&&n!==t&&ie(s,n,{get:()=>e[n],enumerable:!(r=_e(e,n))||r.enumerable});return s};var xe=s=>Ue(ie({},"__esModule",{value:!0}),s);var ut={};Le(ut,{Analytics:()=>_,Audiences:()=>H,Auth:()=>N,AuthError:()=>c,Chatbot:()=>U,Consent:()=>x,EmailAlreadyTakenError:()=>O,FeatureFlags:()=>P,InAppMessaging:()=>L,LinkError:()=>h,Links:()=>F,SendoraCloud:()=>Z,Surveys:()=>D,ValidationError:()=>p,WebPush:()=>M,default:()=>ct,extractShortcodeFromUrl:()=>Ee,safeUrl:()=>A,sanitizeHtml:()=>Y});module.exports=xe(ut);var he=/^[a-zA-Z][a-zA-Z0-9._:-]{0,127}$/;var Oe=new Set(["__proto__","constructor","prototype"]),p=class extends Error{constructor(e){super(`[SendoraCloud] ${e}`),this.name="SendoraValidationError"}};function pe(s){if(typeof s!="string"||!he.test(s))throw new p(`Invalid event name: must match ${he}, got "${String(s)}".`)}function ge(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=fe(s);if(e>5)throw new p(`Properties nested too deeply (${e} levels, max 5).`);me(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 fe(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=fe(s[r],e+1);n>t&&(t=n)}return t}function me(s){if(!(s==null||typeof s!="object"))for(let e of Object.keys(s)){if(Oe.has(e))throw new p(`Forbidden property key "${e}" (prototype pollution guard).`);me(s[e])}}var Ne=/^pk_[A-Za-z0-9_-]{16,}$/;function ye(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(!Ne.test(s))throw new p("Invalid publishable key format. Expected pk_... (17+ chars).")}function we(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 Me="https://api.sendoracloud.com",He=1e4,z=class{constructor(e){let t=(e.apiUrl??Me).replace(/\/$/,"");we(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(),He):null;try{let u=await fetch(i,{method:e,headers:{"Content-Type":"application/json","X-API-Key":this.apiKey,...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,E=w?` (${Object.entries(w).map(([v,te])=>`${v}: ${(te??[]).join(", ")}`).join("; ")})`:"";console.warn(`[SendoraCloud] ${e} ${t} \u2192 HTTP ${u.status} \u2014 ${f}: ${m}${E}`)}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 C(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 ve={name:"@sendoracloud/sdk-web",version:"3.2.1",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 V=ve.version;var ke=500,oe=10,ae=6e4,_=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;let i=this.storage.get("anonymous_id");if(i?this.anonymousId=i:(this.anonymousId=C("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){pe(e),t&&ge(t),this.queue.length>=ke&&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=C("anon_"),this.storage.set("anonymous_id",this.anonymousId),this.storage.clearPrefix("iam_shown_"),this.storage.clearPrefix("survey_shown_")}dropQueue(){this.queue=[]}async flush(){if(this.queue.length===0||!this.consent.isGranted()||Date.now()<this.nextFlushAfter)return;this.consecutiveFailures>oe&&Date.now()-this.breakerOpenAt>=ae&&(this.consecutiveFailures=0,this.breakerOpenAt=0,this.breakerWarned=!1);let e=this.queue.splice(0,100),t=e.map(o=>({projectId:this.projectId,module:"custom",eventType:o.eventType,userId:o.userId,anonymousId:o.anonymousId,identityToken:o.identityToken,timestamp:o.timestamp??new Date().toISOString(),properties:o.properties??{},context:this.getContext(),consent:["analytics"]}));if((await this.http.post("/events/batch",{events:t})).success){this.consecutiveFailures=0,this.breakerOpenAt=0,this.breakerWarned=!1;return}if(this.consecutiveFailures++,this.consecutiveFailures>oe){this.breakerWarned||(this.breakerOpenAt=Date.now(),this.breakerWarned=!0,console.warn(`[SendoraCloud] Analytics circuit breaker open after ${oe} consecutive failed flushes \u2014 events will be dropped until a flush succeeds or ${ae}ms elapse.`));return}let n=Math.min(ae,1e3*2**this.consecutiveFailures);this.nextFlushAfter=Date.now()+n;let i=Math.max(0,ke-this.queue.length);i>0&&this.queue.unshift(...e.slice(0,i))}destroy(){this.timer&&clearInterval(this.timer),this.timer=null,this.flush()}getContext(){return typeof window>"u"?{sdk:{name:"@sendoracloud/sdk-web",version:V}}:{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,sdk:{name:"@sendoracloud/sdk-web",version:V}}}};var P=class{constructor(e,t,r){this.cache=new Map;this.refreshInterval=null;this.http=t,this.storage=r;let n=this.storage.get("flags");if(n)try{let i=JSON.parse(n);if(Array.isArray(i))for(let o of i)ce(o)&&this.cache.set(o.key,o)}catch{}}setContext(e){this.userId=e.userId,this.entityId=e.entityId,this.environment=e.environment}async getFlag(e){if(typeof e!="string"||e.length===0||e.length>128)return null;let t=this.cache.get(e);if(t)return t.value;let r=await this.http.post("/feature-flags/evaluate",{key:e,userId:this.userId,entityId:this.entityId,environment:this.environment});return r.success&&r.data&&ce(r.data)?(this.cache.set(e,r.data),this.persistCache(),r.data.value):null}async isEnabled(e,t=!1){let r=await this.getFlag(e);return typeof r=="boolean"?r:t}async refreshAll(){let e=await this.http.post("/feature-flags/evaluate-all",{userId:this.userId,entityId:this.entityId,environment:this.environment});if(e.success&&Array.isArray(e.data)){this.cache.clear();for(let t of e.data)ce(t)&&this.cache.set(t.key,t);this.persistCache()}}startAutoRefresh(e=6e4){this.stopAutoRefresh(),this.refreshAll(),this.refreshInterval=setInterval(()=>this.refreshAll(),e)}stopAutoRefresh(){this.refreshInterval&&clearInterval(this.refreshInterval),this.refreshInterval=null}getAllFlags(){let e={};for(let[t,r]of this.cache)e[t]=r.value;return e}reset(){this.cache.clear(),this.storage.remove("flags"),this.userId=void 0,this.entityId=void 0,this.environment=void 0}destroy(){this.stopAutoRefresh()}persistCache(){this.storage.set("flags",JSON.stringify(Array.from(this.cache.values())))}};function ce(s){if(s==null||typeof s!="object")return!1;let e=s;return Object.prototype.hasOwnProperty.call(e,"__proto__")||Object.prototype.hasOwnProperty.call(e,"constructor")?!1:typeof e.key=="string"&&"value"in e}var Ke=32*1024,D=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||!Ve(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>Ke)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 Ve(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($e)}function $e(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 We=new Set(["b","i","u","em","strong","a","p","br","span","ul","ol","li","h1","h2","h3","h4"]),qe={a:new Set(["href","target","rel"])};function Y(s){if(typeof DOMParser>"u")return"";let t=new DOMParser().parseFromString(`<div>${s}</div>`,"text/html").body.firstElementChild;return t?(Ae(t),t.innerHTML):""}function Ae(s){let e=Array.from(s.children);for(let t of e){let r=t.tagName.toLowerCase();if(!We.has(r)){let i=document.createTextNode(t.textContent??"");t.replaceWith(i);continue}let n=qe[r]??new Set;for(let i of Array.from(t.attributes)){if(!n.has(i.name.toLowerCase())){t.removeAttribute(i.name);continue}if(i.name.toLowerCase()==="href"){let o=A(i.value);o?t.setAttribute("href",o):t.removeAttribute(i.name)}}r==="a"&&(t.setAttribute("rel","noopener noreferrer"),t.setAttribute("target","_blank")),Ae(t)}}var L=class{constructor(e,t,r){this.messages=[];this.pollInterval=null;this.http=t,this.storage=r}onDisplay(e){this.onMessageDisplay=e}async checkMessages(){let e=await this.http.get("/in-app-messages/active");if(!e.success||!Array.isArray(e.data))return null;this.messages=e.data.map(Be);for(let t of this.messages){let r=`iam_shown_${t.id}`;if(!this.storage.get(r))return this.storage.set(r,"1"),this.onMessageDisplay&&this.onMessageDisplay(t),t}return null}async recordAction(e,t){await this.http.post("/in-app-messages/impressions",{messageId:e,action:t})}startPolling(e=3e5){this.stopPolling(),this.checkMessages(),this.pollInterval=setInterval(()=>this.checkMessages(),e)}stopPolling(){this.pollInterval&&clearInterval(this.pollInterval),this.pollInterval=null}getMessages(){return this.messages}destroy(){this.stopPolling()}};function Be(s){return{...s,content:{...s.content,ctaUrl:A(s.content.ctaUrl),imageUrl:A(s.content.imageUrl)}}}var ze=4e3,U=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??C("chat_"),n||this.storage.set("chat_session_id",this.sessionId)}init(e){if(typeof e!="string"||!/^[A-Za-z0-9_-]{6,64}$/.test(e))throw new Error("[SendoraCloud] Invalid chatbotId.");this.chatbotId=e}onNewMessage(e){this.onMessage=e}async sendMessage(e,t){if(!this.chatbotId||typeof e!="string"||e.length===0||e.length>ze)return null;let r={role:"user",content:e,timestamp:new Date().toISOString()};this.messages.push(r);let n=await this.http.post("/chatbot/message",{chatbotId:this.chatbotId,sessionId:this.sessionId,message:e,userId:t});if(n.success&&n.data&&typeof n.data.reply=="string"){let i={role:"assistant",content:n.data.reply,timestamp:new Date().toISOString()};return this.messages.push(i),this.onMessage&&this.onMessage(i),i}return null}getMessages(){return[...this.messages]}newSession(){this.messages=[],this.sessionId=C("chat_"),this.storage.set("chat_session_id",this.sessionId)}getSessionId(){return this.sessionId}};var x=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 S="auth_access_token",T="auth_access_expires",g="auth_refresh_token",$="auth_user",je=3e4,Ge=/^[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"||e.retiredAnonUserId!==void 0&&typeof e.retiredAnonUserId!="string")}var O=class extends Error{constructor(e="An account with this email already exists"){super(e),this.name="EmailAlreadyTakenError"}},c=class extends Error{constructor(e,t){super(t),this.name="AuthError",this.code=e}},N=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($),u=this.storage.get(S),l=this.storage.get(g),b=this.storage.get(T);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($),this.storage.remove(S),this.storage.remove(T),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||!Ge.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-je?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==="CONFLICT"||a==="EMAIL_ALREADY_TAKEN"||u===409)throw new O(o.error?.message);if(a==="FORBIDDEN_NON_ANONYMOUS"||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 O(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 n=await this.http.post("/auth-service/login/social",{...e,...r?{prevAnonRefreshToken:r}:{}});if(!n.success||!y(n.data))throw new c(n.error?.code??"SOCIAL_LOGIN_FAILED",n.error?.message??"Social login failed");return this.persist(n.data),n.data.user})}signInWithGoogle(e,t){return this.loginSocial({provider:"google",code:e,redirectUri:t})}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})}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=Qe(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=Je(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=Xe(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=et(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(S,a.accessToken),this.storage.set(T,String(this.accessExpiresAt)),this.storage.set(g,a.refreshToken);let u=Ze(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($,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(S),o=this.storage.get(T);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 Ye(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(S,o.accessToken),this.storage.set(T,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($,JSON.stringify(e.user)),this.storage.set(S,e.tokens.accessToken),this.storage.set(T,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($),this.storage.remove(S),this.storage.remove(T),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 Ye(s){return s?s==="INVALID_REFRESH_TOKEN"||s==="UNAUTHORIZED"||s==="HTTP_401"||s==="RATE_LIMIT_EXCEEDED"||s==="RATE_LIMIT":!1}function W(s){let e=(4-s.length%4)%4,t=(s+"===".slice(0,e)).replace(/-/g,"+").replace(/_/g,"/"),r=atob(t),n=new ArrayBuffer(r.length),i=new Uint8Array(n);for(let o=0;o<r.length;o++)i[o]=r.charCodeAt(o);return n}function I(s){let e=s instanceof Uint8Array?s:new Uint8Array(s),t="";for(let r=0;r<e.length;r++)t+=String.fromCharCode(e[r]);return btoa(t).replace(/\+/g,"-").replace(/\//g,"_").replace(/=+$/,"")}function Qe(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 Xe(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 Je(s){let e=s.response;return{id:s.id,rawId:I(s.rawId),type:s.type,response:{clientDataJSON:I(e.clientDataJSON),attestationObject:I(e.attestationObject),transports:typeof e.getTransports=="function"?e.getTransports():void 0},clientExtensionResults:s.getClientExtensionResults()}}function Ze(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 et(s){let e=s.response;return{id:s.id,rawId:I(s.rawId),type:s.type,response:{clientDataJSON:I(e.clientDataJSON),authenticatorData:I(e.authenticatorData),signature:I(e.signature),userHandle:e.userHandle?I(e.userHandle):null},clientExtensionResults:s.getClientExtensionResults()}}function tt(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 rt=250,Ie=360*60*1e3;function Te(s,e){if(typeof window>"u")return()=>{};let t=tt(e.autoTrack),r=e.sessionIdleMs??18e5,n=[],i="sendora_session_id",o="sendora_session_last_activity";function a(){return be(i)??""}function u(d){Se(i,d)}function l(){let d=be(o);return d&&Number(d)||0}function b(d){Se(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(),K=l();if(!a()||K&&k-K>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,E=0,v=document.visibilityState==="visible"?Date.now():0;function te(){v>0&&(E+=Date.now()-v,v=0)}function q(){if(!t.engagement)return;te();let d=E;if(E=0,!(d<rt)){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,E=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 K=window;if(!K.__sendoraHistoryPatched){K.__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 de=0,R=()=>{let d=Date.now();d-de<5e3||(de=d,m(!0))};return window.addEventListener("click",R,{passive:!0}),window.addEventListener("keydown",R,{passive:!0}),window.addEventListener("scroll",R,{passive:!0}),n.push(()=>{window.removeEventListener("click",R),window.removeEventListener("keydown",R),window.removeEventListener("scroll",R)}),()=>{q();for(let d of n)try{d()}catch{}}}function be(s){try{return window.sessionStorage?.getItem(s)??null}catch{return null}}function Se(s,e){try{window.sessionStorage?.setItem(s,e)}catch{}}var M=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:st(i)}),u=a.toJSON(),l=u.endpoint,b=u.keys?.p256dh??"",f=u.keys?.auth??"",m=await nt(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 st(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 nt(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 H=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 Ee(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 it=5*6e4,ot=50,at=5,F=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>it&&this.prewarmCache.delete(t);for(;this.prewarmCache.size>ot;){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>=at)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 X=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 J=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 x(e.defaultConsent===!0,this.http),this.analytics=new _(e,this.http,this.storage,this.consent),this.flags=new P(e,this.http,this.storage),this.surveys=new D(e,this.http,this.storage),this.messages=new L(e,this.http,this.storage),this.chatbot=new U(e,this.http,this.storage),this.auth=new N(e,this.http,this.storage,this.analytics,this.flags,this.chatbot),this.webPush=new M(this.http),this.audiences=new H(this.http),this.links=new F(this.http),this.attribution=new Q(this.http),this.support=new X(this.http),this.kb=new J(this.http),e.autoTrack!==!1&&(this.autoTrackCleanup=Te(this.analytics,e))}static init(e){return ye(e.apiKey),new ee(e)}static sanitizeHtml(e){return Y(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()}};ee.version=V;var Z=ee,ct=Z;0&&(module.exports={Analytics,Audiences,Auth,AuthError,Chatbot,Consent,EmailAlreadyTakenError,FeatureFlags,InAppMessaging,LinkError,Links,SendoraCloud,Surveys,ValidationError,WebPush,extractShortcodeFromUrl,safeUrl,sanitizeHtml});
|
package/dist/index.d.cts
CHANGED
|
@@ -246,6 +246,8 @@ declare class Analytics {
|
|
|
246
246
|
private anonymousId;
|
|
247
247
|
private consecutiveFailures;
|
|
248
248
|
private nextFlushAfter;
|
|
249
|
+
private breakerOpenAt;
|
|
250
|
+
private breakerWarned;
|
|
249
251
|
constructor(config: SendoraConfig, http: HttpClient, storage: Storage, consent: Consent);
|
|
250
252
|
track(eventType: string, properties?: Record<string, unknown>): void;
|
|
251
253
|
page(properties?: Record<string, unknown>): void;
|
|
@@ -470,22 +472,32 @@ declare class Auth {
|
|
|
470
472
|
*/
|
|
471
473
|
private takeoverHint;
|
|
472
474
|
/**
|
|
473
|
-
* Returns
|
|
474
|
-
* sdk-react-native 1.1.0):
|
|
475
|
+
* Returns a VALID (non-expired) access token, or null.
|
|
475
476
|
*
|
|
476
|
-
*
|
|
477
|
-
*
|
|
478
|
-
*
|
|
479
|
-
*
|
|
480
|
-
*
|
|
481
|
-
*
|
|
482
|
-
*
|
|
483
|
-
*
|
|
484
|
-
* either rides backend grace or 401s fast; far better UX than
|
|
485
|
-
* blocking on a refresh we already know will time out.
|
|
486
|
-
* 4. Else → fire refresh.
|
|
477
|
+
* Hard contract (parity with sdk-react-native / sdk-ios / sdk-android
|
|
478
|
+
* and the industry token-client pattern — Supabase
|
|
479
|
+
* `_callRefreshToken`, Auth0 `getTokenSilently`, Firebase
|
|
480
|
+
* `getIdToken`): NEVER returns a token past its `exp`. Callers attach
|
|
481
|
+
* the result as a Bearer to arbitrary resource servers — including
|
|
482
|
+
* third-party backends (PostgREST/Supabase) that have NO knowledge of
|
|
483
|
+
* Sendora's own refresh grace window — so a token must be either
|
|
484
|
+
* fresh or absent.
|
|
487
485
|
*
|
|
488
|
-
*
|
|
486
|
+
* 1. Cached token with comfortable life left (>= REFRESH_SAFETY_MS
|
|
487
|
+
* skew before exp) → return it. Fast path, no I/O.
|
|
488
|
+
* 2. No cached token AND no refresh token in storage → null.
|
|
489
|
+
* 3. Expired/near-expiry but a refresh chain exists:
|
|
490
|
+
* a. A refresh is already inflight → AWAIT it and return the
|
|
491
|
+
* fresh token. Single-flight: concurrent callers coalesce
|
|
492
|
+
* onto one `/token/refresh`, nobody is handed a stale token.
|
|
493
|
+
* The inflight fetch is AbortController-bounded (http.ts caps
|
|
494
|
+
* `/auth-service/*` at 10s) so awaiting never hangs — that
|
|
495
|
+
* bounded fetch, not the old stale-token return, is the real
|
|
496
|
+
* cold-launch hang fix.
|
|
497
|
+
* b. In backoff cooldown after a failed refresh → null (honest
|
|
498
|
+
* "no usable token", not a provably-expired one; the
|
|
499
|
+
* `visibilitychange` hook clears the cooldown on tab focus).
|
|
500
|
+
* c. Otherwise → fire the refresh and await it.
|
|
489
501
|
*/
|
|
490
502
|
getAccessToken(): Promise<string | null>;
|
|
491
503
|
/** Sync read — returns whatever's cached even if expired. Prefer the async variant. */
|
|
@@ -728,8 +740,12 @@ declare class Auth {
|
|
|
728
740
|
consumeSsoTokenFromUrl(): Promise<AuthUser | null>;
|
|
729
741
|
/**
|
|
730
742
|
* Build the Authorization header for end-user-authenticated routes.
|
|
731
|
-
*
|
|
732
|
-
*
|
|
743
|
+
*
|
|
744
|
+
* Awaits `getAccessToken()` so a cold-start (re-hydrated from storage
|
|
745
|
+
* with no cached access token) or an expired token transparently
|
|
746
|
+
* refreshes via the stored refresh token before the call — matching
|
|
747
|
+
* `sendVerificationEmail`. Throws if there's genuinely no usable
|
|
748
|
+
* session; the caller should handle this as an unauthenticated state.
|
|
733
749
|
*/
|
|
734
750
|
private bearerHeaders;
|
|
735
751
|
/**
|
|
@@ -898,30 +914,28 @@ interface UpdateAudienceInput {
|
|
|
898
914
|
rules?: AudienceRuleGroup;
|
|
899
915
|
}
|
|
900
916
|
/**
|
|
901
|
-
* Audiences
|
|
917
|
+
* Audiences are a **server-side, secret-key surface** and are NOT callable
|
|
918
|
+
* from the browser SDK.
|
|
902
919
|
*
|
|
903
|
-
*
|
|
920
|
+
* The backend routes (`/orgs/:orgId/audiences`) require an `sk_*` (secret)
|
|
921
|
+
* key with the `audiences:read` / `audiences:write` scopes. This SDK
|
|
922
|
+
* deliberately refuses `sk_*` keys (`SendoraCloud.init()` throws on one),
|
|
923
|
+
* so there is no key type with which these methods could ever succeed.
|
|
904
924
|
*
|
|
905
|
-
*
|
|
906
|
-
*
|
|
907
|
-
*
|
|
908
|
-
*
|
|
909
|
-
*
|
|
910
|
-
* { trait: "plan", op: "eq", value: "pro" },
|
|
911
|
-
* { event: "feature_used", op: "occurred_in_last", value: "30d" },
|
|
912
|
-
* ]},
|
|
913
|
-
* });
|
|
914
|
-
* ```
|
|
925
|
+
* The surface is retained only for backwards-compatible typing. Every
|
|
926
|
+
* method throws a clear error rather than silently returning empty data,
|
|
927
|
+
* so the false-capability is surfaced to the caller instead of failing
|
|
928
|
+
* invisibly. Manage audiences from a trusted server using a secret key
|
|
929
|
+
* (e.g. via the dashboard or a server-side integration).
|
|
915
930
|
*/
|
|
916
931
|
declare class Audiences {
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
private base;
|
|
932
|
+
constructor(_http: HttpClient);
|
|
933
|
+
private unsupported;
|
|
920
934
|
list(): Promise<Audience[]>;
|
|
921
|
-
get(
|
|
922
|
-
create(
|
|
923
|
-
update(
|
|
924
|
-
delete(
|
|
935
|
+
get(_audienceId: string): Promise<Audience | null>;
|
|
936
|
+
create(_input: CreateAudienceInput): Promise<Audience | null>;
|
|
937
|
+
update(_audienceId: string, _patch: UpdateAudienceInput): Promise<Audience | null>;
|
|
938
|
+
delete(_audienceId: string): Promise<boolean>;
|
|
925
939
|
}
|
|
926
940
|
|
|
927
941
|
/**
|
|
@@ -1266,7 +1280,7 @@ declare class SendoraCloud {
|
|
|
1266
1280
|
/** Return the URL if it's http(s); undefined otherwise. */
|
|
1267
1281
|
static safeUrl(u?: string): string | undefined;
|
|
1268
1282
|
/** SDK version. */
|
|
1269
|
-
static readonly version
|
|
1283
|
+
static readonly version: string;
|
|
1270
1284
|
track(eventType: string, properties?: Record<string, unknown>): void;
|
|
1271
1285
|
page(properties?: Record<string, unknown>): void;
|
|
1272
1286
|
identify(userId: string, traits?: UserTraits, opts?: IdentifyOptions): void;
|
package/dist/index.d.ts
CHANGED
|
@@ -246,6 +246,8 @@ declare class Analytics {
|
|
|
246
246
|
private anonymousId;
|
|
247
247
|
private consecutiveFailures;
|
|
248
248
|
private nextFlushAfter;
|
|
249
|
+
private breakerOpenAt;
|
|
250
|
+
private breakerWarned;
|
|
249
251
|
constructor(config: SendoraConfig, http: HttpClient, storage: Storage, consent: Consent);
|
|
250
252
|
track(eventType: string, properties?: Record<string, unknown>): void;
|
|
251
253
|
page(properties?: Record<string, unknown>): void;
|
|
@@ -470,22 +472,32 @@ declare class Auth {
|
|
|
470
472
|
*/
|
|
471
473
|
private takeoverHint;
|
|
472
474
|
/**
|
|
473
|
-
* Returns
|
|
474
|
-
* sdk-react-native 1.1.0):
|
|
475
|
+
* Returns a VALID (non-expired) access token, or null.
|
|
475
476
|
*
|
|
476
|
-
*
|
|
477
|
-
*
|
|
478
|
-
*
|
|
479
|
-
*
|
|
480
|
-
*
|
|
481
|
-
*
|
|
482
|
-
*
|
|
483
|
-
*
|
|
484
|
-
* either rides backend grace or 401s fast; far better UX than
|
|
485
|
-
* blocking on a refresh we already know will time out.
|
|
486
|
-
* 4. Else → fire refresh.
|
|
477
|
+
* Hard contract (parity with sdk-react-native / sdk-ios / sdk-android
|
|
478
|
+
* and the industry token-client pattern — Supabase
|
|
479
|
+
* `_callRefreshToken`, Auth0 `getTokenSilently`, Firebase
|
|
480
|
+
* `getIdToken`): NEVER returns a token past its `exp`. Callers attach
|
|
481
|
+
* the result as a Bearer to arbitrary resource servers — including
|
|
482
|
+
* third-party backends (PostgREST/Supabase) that have NO knowledge of
|
|
483
|
+
* Sendora's own refresh grace window — so a token must be either
|
|
484
|
+
* fresh or absent.
|
|
487
485
|
*
|
|
488
|
-
*
|
|
486
|
+
* 1. Cached token with comfortable life left (>= REFRESH_SAFETY_MS
|
|
487
|
+
* skew before exp) → return it. Fast path, no I/O.
|
|
488
|
+
* 2. No cached token AND no refresh token in storage → null.
|
|
489
|
+
* 3. Expired/near-expiry but a refresh chain exists:
|
|
490
|
+
* a. A refresh is already inflight → AWAIT it and return the
|
|
491
|
+
* fresh token. Single-flight: concurrent callers coalesce
|
|
492
|
+
* onto one `/token/refresh`, nobody is handed a stale token.
|
|
493
|
+
* The inflight fetch is AbortController-bounded (http.ts caps
|
|
494
|
+
* `/auth-service/*` at 10s) so awaiting never hangs — that
|
|
495
|
+
* bounded fetch, not the old stale-token return, is the real
|
|
496
|
+
* cold-launch hang fix.
|
|
497
|
+
* b. In backoff cooldown after a failed refresh → null (honest
|
|
498
|
+
* "no usable token", not a provably-expired one; the
|
|
499
|
+
* `visibilitychange` hook clears the cooldown on tab focus).
|
|
500
|
+
* c. Otherwise → fire the refresh and await it.
|
|
489
501
|
*/
|
|
490
502
|
getAccessToken(): Promise<string | null>;
|
|
491
503
|
/** Sync read — returns whatever's cached even if expired. Prefer the async variant. */
|
|
@@ -728,8 +740,12 @@ declare class Auth {
|
|
|
728
740
|
consumeSsoTokenFromUrl(): Promise<AuthUser | null>;
|
|
729
741
|
/**
|
|
730
742
|
* Build the Authorization header for end-user-authenticated routes.
|
|
731
|
-
*
|
|
732
|
-
*
|
|
743
|
+
*
|
|
744
|
+
* Awaits `getAccessToken()` so a cold-start (re-hydrated from storage
|
|
745
|
+
* with no cached access token) or an expired token transparently
|
|
746
|
+
* refreshes via the stored refresh token before the call — matching
|
|
747
|
+
* `sendVerificationEmail`. Throws if there's genuinely no usable
|
|
748
|
+
* session; the caller should handle this as an unauthenticated state.
|
|
733
749
|
*/
|
|
734
750
|
private bearerHeaders;
|
|
735
751
|
/**
|
|
@@ -898,30 +914,28 @@ interface UpdateAudienceInput {
|
|
|
898
914
|
rules?: AudienceRuleGroup;
|
|
899
915
|
}
|
|
900
916
|
/**
|
|
901
|
-
* Audiences
|
|
917
|
+
* Audiences are a **server-side, secret-key surface** and are NOT callable
|
|
918
|
+
* from the browser SDK.
|
|
902
919
|
*
|
|
903
|
-
*
|
|
920
|
+
* The backend routes (`/orgs/:orgId/audiences`) require an `sk_*` (secret)
|
|
921
|
+
* key with the `audiences:read` / `audiences:write` scopes. This SDK
|
|
922
|
+
* deliberately refuses `sk_*` keys (`SendoraCloud.init()` throws on one),
|
|
923
|
+
* so there is no key type with which these methods could ever succeed.
|
|
904
924
|
*
|
|
905
|
-
*
|
|
906
|
-
*
|
|
907
|
-
*
|
|
908
|
-
*
|
|
909
|
-
*
|
|
910
|
-
* { trait: "plan", op: "eq", value: "pro" },
|
|
911
|
-
* { event: "feature_used", op: "occurred_in_last", value: "30d" },
|
|
912
|
-
* ]},
|
|
913
|
-
* });
|
|
914
|
-
* ```
|
|
925
|
+
* The surface is retained only for backwards-compatible typing. Every
|
|
926
|
+
* method throws a clear error rather than silently returning empty data,
|
|
927
|
+
* so the false-capability is surfaced to the caller instead of failing
|
|
928
|
+
* invisibly. Manage audiences from a trusted server using a secret key
|
|
929
|
+
* (e.g. via the dashboard or a server-side integration).
|
|
915
930
|
*/
|
|
916
931
|
declare class Audiences {
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
private base;
|
|
932
|
+
constructor(_http: HttpClient);
|
|
933
|
+
private unsupported;
|
|
920
934
|
list(): Promise<Audience[]>;
|
|
921
|
-
get(
|
|
922
|
-
create(
|
|
923
|
-
update(
|
|
924
|
-
delete(
|
|
935
|
+
get(_audienceId: string): Promise<Audience | null>;
|
|
936
|
+
create(_input: CreateAudienceInput): Promise<Audience | null>;
|
|
937
|
+
update(_audienceId: string, _patch: UpdateAudienceInput): Promise<Audience | null>;
|
|
938
|
+
delete(_audienceId: string): Promise<boolean>;
|
|
925
939
|
}
|
|
926
940
|
|
|
927
941
|
/**
|
|
@@ -1266,7 +1280,7 @@ declare class SendoraCloud {
|
|
|
1266
1280
|
/** Return the URL if it's http(s); undefined otherwise. */
|
|
1267
1281
|
static safeUrl(u?: string): string | undefined;
|
|
1268
1282
|
/** SDK version. */
|
|
1269
|
-
static readonly version
|
|
1283
|
+
static readonly version: string;
|
|
1270
1284
|
track(eventType: string, properties?: Record<string, unknown>): void;
|
|
1271
1285
|
page(properties?: Record<string, unknown>): void;
|
|
1272
1286
|
identify(userId: string, traits?: UserTraits, opts?: IdentifyOptions): void;
|
package/dist/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
var ce=/^[a-zA-Z][a-zA-Z0-9._:-]{0,127}$/;var Se=new Set(["__proto__","constructor","prototype"]),p=class extends Error{constructor(e){super(`[SendoraCloud] ${e}`),this.name="SendoraValidationError"}};function ue(r){if(typeof r!="string"||!ce.test(r))throw new p(`Invalid event name: must match ${ce}, got "${String(r)}".`)}function le(r){if(r==null)return;if(typeof r!="object"||Array.isArray(r))throw new p("Properties must be a plain object, not an array or primitive.");let e=de(r);if(e>5)throw new p(`Properties nested too deeply (${e} levels, max 5).`);he(r);let t;try{t=JSON.stringify(r).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 de(r,e=0){if(e>6||r==null||typeof r!="object")return e;let t=e;for(let s of Object.keys(r)){let n=de(r[s],e+1);n>t&&(t=n)}return t}function he(r){if(!(r==null||typeof r!="object"))for(let e of Object.keys(r)){if(Se.has(e))throw new p(`Forbidden property key "${e}" (prototype pollution guard).`);he(r[e])}}var Te=/^pk_[A-Za-z0-9_-]{16,}$/;function pe(r){if(!r||typeof r!="string")throw new p("apiKey is required.");if(r.startsWith("sk_")||r.startsWith("sendora_secret_"))throw new p("Secret keys must NEVER be used in browser SDK. Use a publishable key (pk_...).");if(!Te.test(r))throw new p("Invalid publishable key format. Expected pk_... (17+ chars).")}function ge(r){let e=r.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 Re="https://api.sendoracloud.com",Ee=1e4,B=class{constructor(e){let t=(e.apiUrl??Re).replace(/\/$/,"");ge(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,s){return this.request("POST",e,t,s)}patch(e,t,s){return this.request("PATCH",e,t,s)}delete(e,t){return this.request("DELETE",e,void 0,t)}async request(e,t,s,n){let i=`${this.baseUrl}/api/v1${t}`,o=typeof AbortController<"u"?new AbortController:null,a=o?setTimeout(()=>o.abort(),Ee):null;try{let c=await fetch(i,{method:e,headers:{"Content-Type":"application/json","X-API-Key":this.apiKey,...n??{}},body:s?JSON.stringify(s):void 0,signal:o?o.signal:void 0});a&&clearTimeout(a);let l;try{l=await c.json()}catch{l={success:!1,error:{code:"PARSE_ERROR",message:`HTTP ${c.status}`}}}if(this.debug&&console.debug(`[SendoraCloud] ${e} ${t} \u2192 ${c.status}`),!c.ok)if(t.startsWith("/auth-service/")&&!this.debug)console.warn(`[SendoraCloud] ${e} ${t} \u2192 HTTP ${c.status}`);else{let f=l?.error?.code??"HTTP_ERROR",m=l?.error?.message??`HTTP ${c.status}`,w=l?.error?.details?.fieldErrors,R=w?` (${Object.entries(w).map(([v,J])=>`${v}: ${(J??[]).join(", ")}`).join("; ")})`:"";console.warn(`[SendoraCloud] ${e} ${t} \u2192 HTTP ${c.status} \u2014 ${f}: ${m}${R}`)}return l}catch(c){a&&clearTimeout(a);let l=c instanceof Error&&c.name==="AbortError"?"timeout":"error";return console.warn(`[SendoraCloud] ${e} ${t} network ${l}`),{success:!1,error:{code:"NETWORK_ERROR",message:l}}}}};var z="sendora_",j=class{constructor(){this.memoryStore=new Map;this.useLocalStorage=this.checkLocalStorage()}get(e){let t=z+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 s=z+e;if(this.useLocalStorage)try{localStorage.setItem(s,t);return}catch{}this.memoryStore.set(s,t)}remove(e){let t=z+e;if(this.useLocalStorage)try{localStorage.removeItem(t)}catch{}this.memoryStore.delete(t)}clearPrefix(e){let t=z+e;if(this.useLocalStorage)try{for(let s=localStorage.length-1;s>=0;s--){let n=localStorage.key(s);n&&n.startsWith(t)&&localStorage.removeItem(n)}}catch{}for(let s of Array.from(this.memoryStore.keys()))s.startsWith(t)&&this.memoryStore.delete(s)}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 C(r=""){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 s="";for(let n of e)s+=n.toString(16).padStart(2,"0");return r+s}var fe="2.9.0",me=500,Ce=10,_e=6e4,P=class{constructor(e,t,s,n){this.queue=[];this.timer=null;this.userId=null;this.identityToken=null;this.consecutiveFailures=0;this.nextFlushAfter=0;this.http=t,this.storage=s,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;let i=this.storage.get("anonymous_id");if(i?this.anonymousId=i:(this.anonymousId=C("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){ue(e),t&&le(t),this.queue.length>=me&&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,s){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=s?.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=C("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;let e=this.queue.splice(0,100),t=e.map(o=>({projectId:this.projectId,module:"custom",eventType:o.eventType,userId:o.userId,anonymousId:o.anonymousId,identityToken:o.identityToken,timestamp:o.timestamp??new Date().toISOString(),properties:o.properties??{},context:this.getContext(),consent:["analytics"]}));if((await this.http.post("/events/batch",{events:t})).success){this.consecutiveFailures=0;return}if(this.consecutiveFailures++,this.consecutiveFailures>Ce)return;let n=Math.min(_e,1e3*2**this.consecutiveFailures);this.nextFlushAfter=Date.now()+n;let i=Math.max(0,me-this.queue.length);i>0&&this.queue.unshift(...e.slice(0,i))}destroy(){this.timer&&clearInterval(this.timer),this.timer=null,this.flush()}getContext(){return typeof window>"u"?{sdk:{name:"@sendoracloud/sdk-web",version:fe}}:{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,sdk:{name:"@sendoracloud/sdk-web",version:fe}}}};var L=class{constructor(e,t,s){this.cache=new Map;this.refreshInterval=null;this.http=t,this.storage=s;let n=this.storage.get("flags");if(n)try{let i=JSON.parse(n);if(Array.isArray(i))for(let o of i)se(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 s=await this.http.post("/feature-flags/evaluate",{key:e,userId:this.userId,entityId:this.entityId,environment:this.environment});return s.success&&s.data&&se(s.data)?(this.cache.set(e,s.data),this.persistCache(),s.data.value):null}async isEnabled(e,t=!1){let s=await this.getFlag(e);return typeof s=="boolean"?s: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)se(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,s]of this.cache)e[t]=s.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 se(r){if(r==null||typeof r!="object")return!1;let e=r;return Object.prototype.hasOwnProperty.call(e,"__proto__")||Object.prototype.hasOwnProperty.call(e,"constructor")?!1:typeof e.key=="string"&&"value"in e}var Pe=32*1024,D=class{constructor(e,t,s){this.activeSurvey=null;this.http=t,this.storage=s}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 s=await this.http.get(`/surveys/${encodeURIComponent(e)}/config`);return!s.success||!s.data||!Le(s.data)?null:(this.activeSurvey=s.data,this.storage.set(t,"1"),this.onSurveyDisplay&&this.onSurveyDisplay(s.data),s.data)}async submitResponse(e,t,s=!0){let n=0;try{n=JSON.stringify(t).length}catch{return!1}if(n>Pe)return!1;let i=await this.http.post("/surveys/responses",{surveyId:e,answers:t,completed:s});return i.success&&(this.activeSurvey=null),i.success}dismiss(){this.activeSurvey=null}getActiveSurvey(){return this.activeSurvey}};function Le(r){if(r==null||typeof r!="object")return!1;let e=r;return typeof e.id!="string"||typeof e.name!="string"||!Array.isArray(e.questions)?!1:e.questions.every(De)}function De(r){if(r==null||typeof r!="object")return!1;let e=r;return typeof e.id=="string"&&typeof e.text=="string"}function b(r){if(!(!r||typeof r!="string"))try{let e=typeof window<"u"?window.location.href:"https://local/",t=new URL(r,e);return t.protocol==="http:"||t.protocol==="https:"?t.href:void 0}catch{return}}var Ue=new Set(["b","i","u","em","strong","a","p","br","span","ul","ol","li","h1","h2","h3","h4"]),xe={a:new Set(["href","target","rel"])};function re(r){if(typeof DOMParser>"u")return"";let t=new DOMParser().parseFromString(`<div>${r}</div>`,"text/html").body.firstElementChild;return t?(ye(t),t.innerHTML):""}function ye(r){let e=Array.from(r.children);for(let t of e){let s=t.tagName.toLowerCase();if(!Ue.has(s)){let i=document.createTextNode(t.textContent??"");t.replaceWith(i);continue}let n=xe[s]??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=b(i.value);o?t.setAttribute("href",o):t.removeAttribute(i.name)}}s==="a"&&(t.setAttribute("rel","noopener noreferrer"),t.setAttribute("target","_blank")),ye(t)}}var U=class{constructor(e,t,s){this.messages=[];this.pollInterval=null;this.http=t,this.storage=s}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(Oe);for(let t of this.messages){let s=`iam_shown_${t.id}`;if(!this.storage.get(s))return this.storage.set(s,"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 Oe(r){return{...r,content:{...r.content,ctaUrl:b(r.content.ctaUrl),imageUrl:b(r.content.imageUrl)}}}var Ne=4e3,x=class{constructor(e,t,s){this.chatbotId=null;this.messages=[];this.http=t,this.storage=s;let n=this.storage.get("chat_session_id");this.sessionId=n??C("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>Ne)return null;let s={role:"user",content:e,timestamp:new Date().toISOString()};this.messages.push(s);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=C("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 S="auth_access_token",T="auth_access_expires",g="auth_refresh_token",N="auth_user",Me=3e4,He=/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;function y(r){if(!r||typeof r!="object")return!1;let e=r,t=e.user,s=e.tokens;return!(!t||typeof t.id!="string"||t.id.length===0||!s||typeof s.accessToken!="string"||s.accessToken.length===0||typeof s.refreshToken!="string"||s.refreshToken.length===0||typeof s.expiresIn!="number"||s.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"||e.retiredAnonUserId!==void 0&&typeof e.retiredAnonUserId!="string")}var H=class extends Error{constructor(e="An account with this email already exists"){super(e),this.name="EmailAlreadyTakenError"}},u=class extends Error{constructor(e,t){super(t),this.name="AuthError",this.code=e}},F=class{constructor(e,t,s,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=s,this.analytics=n,this.flags=i,this.chatbot=o;let a=this.storage.get(N),c=this.storage.get(S),l=this.storage.get(g),I=this.storage.get(T);if(a&&(c||l))try{let f=JSON.parse(a);if(typeof f.id!="string"||f.id.length===0)throw new Error("invalid cache");this.user=f,c?(this.accessToken=c,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(S),this.storage.remove(T),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||!He.test(e))return;let s={retiredAnonUserId:e,identifiedUserId:t,at:new Date};this.lastTakeover=s;for(let n of this.takeoverListeners)try{n(s)}catch{}}takeoverHint(){if(this.user?.isAnonymous)return this.storage.get(g)??void 0}async getAccessToken(){return this.accessToken?this.accessExpiresAt&&Date.now()<this.accessExpiresAt-Me?this.accessToken:this.refreshInflight?this.accessToken:Date.now()<this.refreshCooldownUntil?this.accessToken: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 u(t.error?.code??"AUTH_ERROR",t.error?.message??"Anonymous signup failed");return this.persist(t.data),t.data.user})}signUp(e,t,s){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:s?.name});if(o.success&&y(o.data))return this.persist(o.data),o.data.user;let a=o.error?.code??"",c=o.error?.status;if(a==="CONFLICT"||a==="EMAIL_ALREADY_TAKEN"||c===409)throw new H(o.error?.message);if(a==="FORBIDDEN_NON_ANONYMOUS"||a==="INVALID_REFRESH"||a==="UNAUTHORIZED"||a==="REFRESH_EXPIRED")await this.analytics.flush(),this.wipeLocalIdentity();else throw new u(a||"AUTH_ERROR",o.error?.message??"Upgrade failed")}let i=await this.http.post("/auth-service/signup",{email:e,password:t,name:s?.name,metadata:s?.metadata});if(!i.success||!y(i.data)){let o=i.error?.code??"";throw o==="CONFLICT"?new H(i.error?.message):new u(o||"AUTH_ERROR",i.error?.message??"Signup failed")}return this.persist(i.data),i.data.user})}signIn(e,t){return this.serialize(async()=>{let s=this.user!==null,n=this.takeoverHint();s&&(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 u(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 u(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,s=this.takeoverHint();t&&(await this.analytics.flush(),this.wipeLocalIdentity());let n=await this.http.post("/auth-service/login/social",{...e,...s?{prevAnonRefreshToken:s}:{}});if(!n.success||!y(n.data))throw new u(n.error?.code??"SOCIAL_LOGIN_FAILED",n.error?.message??"Social login failed");return this.persist(n.data),n.data.user})}signInWithGoogle(e,t){return this.loginSocial({provider:"google",code:e,redirectUri:t})}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})}signInWithDiscord(e,t){return this.loginSocial({provider:"discord",code:e,redirectUri:t})}challengeMfa(e,t){return this.serialize(async()=>{let s=this.takeoverHint(),n=await this.http.post("/auth-service/mfa/challenge",{challengeToken:e,code:t,...s?{prevAnonRefreshToken:s}:{}});if(!n.success||!y(n.data))throw new u(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 u(t.error?.code??"MAGIC_LINK_REQUEST_FAILED",t.error?.message??"Failed to send link")}async sendEmailOtp(e){let t=await this.http.post("/auth-service/email-otp/request",{email:e});if(!t.success)throw new u(t.error?.code??"EMAIL_OTP_REQUEST_FAILED",t.error?.message??"Failed to send code")}verifyEmailOtp(e,t){return this.serialize(async()=>{let s=this.user!==null,n=this.takeoverHint();s&&(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 u(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 u(t.error?.code??"PASSWORD_RESET_REQUEST_FAILED",t.error?.message??"Failed to send reset email")}async resetPassword(e,t){let s=await this.http.post("/auth-service/password/reset",{token:e,newPassword:t});if(!s.success)throw new u(s.error?.code??"PASSWORD_RESET_FAILED",s.error?.message??"Reset failed")}async verifyEmail(e){let t=await this.http.post("/auth-service/email/verify",{token:e});if(!t.success)throw new u(t.error?.code??"EMAIL_VERIFICATION_FAILED",t.error?.message??"Verification failed")}async sendVerificationEmail(){let e=await this.getAccessToken();if(!e)throw new u("UNAUTHORIZED","Sign in before requesting a verification email");let t=await this.http.post("/auth-service/email/verify/resend",{},{Authorization:`Bearer ${e}`});if(!t.success)throw new u(t.error?.code??"VERIFICATION_RESEND_FAILED",t.error?.message??"Failed to send verification email")}verifyMagicLink(e){return this.serialize(async()=>{let t=this.user!==null,s=this.takeoverHint();t&&(await this.analytics.flush(),this.wipeLocalIdentity());let n=await this.http.post("/auth-service/magic-link/verify",{token:e,...s?{prevAnonRefreshToken:s}:{}});if(!n.success||!y(n.data))throw new u(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",{},this.bearerHeaders());if(!e.success||!e.data)throw new u(e.error?.code??"MFA_ENROLL_FAILED",e.error?.message??"MFA enrollment failed");return e.data}async confirmMfa(e){let t=await this.http.post("/auth-service/mfa/enroll/confirm",{code:e},this.bearerHeaders());return!!(t.success&&t.data?.confirmed)}async disableMfa(){await this.http.post("/auth-service/mfa/disable",{},this.bearerHeaders())}async registerPasskey(e){if(typeof navigator>"u"||!navigator.credentials)throw new u("PASSKEY_UNSUPPORTED","WebAuthn not supported in this environment");let t=await this.http.post("/auth-service/passkeys/register/start",{},this.bearerHeaders());if(!t.success||!t.data)throw new u(t.error?.code??"PASSKEY_START_FAILED",t.error?.message??"Passkey start failed");let s=Ke(t.data),n;try{let a=await navigator.credentials.create({publicKey:s});if(!a)throw new Error("create returned null");n=a}catch(a){throw new u("PASSKEY_USER_CANCELLED",a instanceof Error?a.message:"User cancelled passkey registration")}let i=Ve(n),o=await this.http.post("/auth-service/passkeys/register/finish",{response:i,name:e},this.bearerHeaders());if(!o.success||!o.data)throw new u(o.error?.code??"PASSKEY_FINISH_FAILED",o.error?.message??"Passkey verification failed");return o.data}signInWithPasskey(e){return this.serialize(async()=>{if(typeof navigator>"u"||!navigator.credentials)throw new u("PASSKEY_UNSUPPORTED","WebAuthn not supported in this environment");let t=this.user!==null,s=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 u(n.error?.code??"PASSKEY_START_FAILED",n.error?.message??"Passkey start failed");let i=$e(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 u("PASSKEY_USER_CANCELLED",l instanceof Error?l.message:"User cancelled passkey signin")}let a=We(o),c=await this.http.post("/auth-service/passkeys/authenticate/finish",{response:a,userId:e??null,...s?{prevAnonRefreshToken:s}:{}});if(!c.success||!y(c.data))throw new u(c.error?.code??"PASSKEY_AUTH_FAILED",c.error?.message??"Passkey signin failed");return this.persist(c.data),c.data.user})}async listPasskeys(){let e=await this.http.get("/auth-service/passkeys",this.bearerHeaders());return!e.success||!Array.isArray(e.data)?[]:e.data}async deletePasskey(e){await this.http.delete(`/auth-service/passkeys/${e}`,this.bearerHeaders())}async listMySessions(){let e=await this.http.get("/auth-service/sessions/me",this.bearerHeaders());return!e.success||!Array.isArray(e.data)?[]:e.data}async revokeSession(e){await this.http.delete(`/auth-service/sessions/me/${e}`,this.bearerHeaders())}async revokeAllSessions(){await this.http.delete("/auth-service/sessions/me",this.bearerHeaders())}async startSso(e){let t=this.takeoverHint(),s=await this.http.post("/auth-service/sso/oidc/start",{returnTo:e,...t?{prevAnonRefreshToken:t}:{}});if(!s.success||!s.data)throw new u(s.error?.code??"SSO_START_FAILED",s.error?.message??"SSO start failed");return s.data}async signInWithSso(e){let t=e??(typeof window<"u"?window.location.origin:"");if(!t)throw new u("SSO_NO_RETURN_TO","returnTo required outside browser context");let s=await this.startSso(t);typeof window<"u"&&(window.location.href=s.authorizationUrl)}async startSaml(e){let t=this.takeoverHint(),s=await this.http.post("/auth-service/sso/saml/start",{returnTo:e,...t?{prevAnonRefreshToken:t}:{}});if(!s.success||!s.data)throw new u(s.error?.code??"SAML_START_FAILED",s.error?.message??"SAML start failed");return s.data}async signInWithSaml(e){let t=e??(typeof window<"u"?window.location.origin:"");if(!t)throw new u("SAML_NO_RETURN_TO","returnTo required outside browser context");let s=await this.startSaml(t);typeof window<"u"&&(window.location.href=s.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),s=t.get("sendora_oidc_error")??t.get("sendora_saml_error");if(s)throw window.history.replaceState(null,"",window.location.pathname+window.location.search),new u("SSO_FAILED",decodeURIComponent(s));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 u(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(S,a.accessToken),this.storage.set(T,String(this.accessExpiresAt)),this.storage.set(g,a.refreshToken);let c=qe(a.accessToken),l={id:c?.sub??"",email:c?.email??null,emailVerified:c?.email_verified??!0,name:c?.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})}bearerHeaders(){if(!this.accessToken)throw new u("NOT_SIGNED_IN","Not signed in");return{Authorization:`Bearer ${this.accessToken}`}}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,s=async()=>{let n=this.storage.get(g);if(n&&n!==e){let i=this.storage.get(S),o=this.storage.get(T);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 Fe(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(S,o.accessToken),this.storage.set(T,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"},s):await s()}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,s=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()*s*2-s);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(S,e.tokens.accessToken),this.storage.set(T,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(S),this.storage.remove(T),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 Fe(r){return r?r==="INVALID_REFRESH_TOKEN"||r==="UNAUTHORIZED"||r==="HTTP_401"||r==="RATE_LIMIT_EXCEEDED"||r==="RATE_LIMIT":!1}function M(r){let e=(4-r.length%4)%4,t=(r+"===".slice(0,e)).replace(/-/g,"+").replace(/_/g,"/"),s=atob(t),n=new ArrayBuffer(s.length),i=new Uint8Array(n);for(let o=0;o<s.length;o++)i[o]=s.charCodeAt(o);return n}function A(r){let e=r instanceof Uint8Array?r:new Uint8Array(r),t="";for(let s=0;s<e.length;s++)t+=String.fromCharCode(e[s]);return btoa(t).replace(/\+/g,"-").replace(/\//g,"_").replace(/=+$/,"")}function Ke(r){return{challenge:M(r.challenge),rp:r.rp,user:{id:M(r.user.id),name:r.user.name,displayName:r.user.displayName},pubKeyCredParams:r.pubKeyCredParams,timeout:r.timeout,attestation:r.attestation,excludeCredentials:r.excludeCredentials?.map(e=>({id:M(e.id),type:"public-key",transports:e.transports})),authenticatorSelection:r.authenticatorSelection}}function $e(r){return{challenge:M(r.challenge),timeout:r.timeout,rpId:r.rpId,allowCredentials:r.allowCredentials?.map(e=>({id:M(e.id),type:"public-key",transports:e.transports})),userVerification:r.userVerification}}function Ve(r){let e=r.response;return{id:r.id,rawId:A(r.rawId),type:r.type,response:{clientDataJSON:A(e.clientDataJSON),attestationObject:A(e.attestationObject),transports:typeof e.getTransports=="function"?e.getTransports():void 0},clientExtensionResults:r.getClientExtensionResults()}}function qe(r){try{let e=r.split(".");if(e.length!==3)return null;let t=e[1].replace(/-/g,"+").replace(/_/g,"/"),s=atob(t+"===".slice(0,(4-t.length%4)%4));return JSON.parse(s)}catch{return null}}function We(r){let e=r.response;return{id:r.id,rawId:A(r.rawId),type:r.type,response:{clientDataJSON:A(e.clientDataJSON),authenticatorData:A(e.authenticatorData),signature:A(e.signature),userHandle:e.userHandle?A(e.userHandle):null},clientExtensionResults:r.getClientExtensionResults()}}function Be(r){return r===!1?{pageViews:!1,sessionStart:!1,appOpen:!1,appBackground:!1,engagement:!1}:r===!0||r===void 0?{pageViews:!0,sessionStart:!0,appOpen:!0,appBackground:!0,engagement:!0}:{pageViews:r.pageViews??!0,sessionStart:r.sessionStart??!0,appOpen:r.appOpen??!0,appBackground:r.appBackground??!0,engagement:r.engagement??!0}}var ze=250,we=360*60*1e3;function Ae(r,e){if(typeof window>"u")return()=>{};let t=Be(e.autoTrack),s=e.sessionIdleMs??18e5,n=[],i="sendora_session_id",o="sendora_session_last_activity";function a(){return ve(i)??""}function c(d){ke(i,d)}function l(){let d=ve(o);return d&&Number(d)||0}function I(d){ke(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-_>s){let W=f();c(W),d&&t.sessionStart&&r.track("session.start",{sessionId:W})}I(k)}m(t.sessionStart),t.appOpen&&r.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 J(){v>0&&(R+=Date.now()-v,v=0)}function q(){if(!t.engagement)return;J();let d=R;if(R=0,!(d<ze)){d>we&&(d=we);try{r.track("app.engagement",{durationMs:Math.round(d),screen:w,sessionId:a()||void 0})}catch{}}}function Ie(d){w=d,R=0,v=document.visibilityState==="visible"?Date.now():0}if(t.pageViews||t.engagement){t.pageViews&&r.page();let d=()=>{q(),m(!0),t.pageViews&&r.page(),Ie(window.location.pathname)},k=()=>d();window.addEventListener("popstate",k),n.push(()=>window.removeEventListener("popstate",k));let _=window;if(!_.__sendoraHistoryPatched){_.__sendoraHistoryPatched=!0;let W=history.pushState,be=history.replaceState;history.pushState=function(...ee){let te=W.apply(this,ee);return window.dispatchEvent(new Event("sendora:locationchange")),te},history.replaceState=function(...ee){let te=be.apply(this,ee);return window.dispatchEvent(new Event("sendora:locationchange")),te}}let Z=()=>d();window.addEventListener("sendora:locationchange",Z),n.push(()=>window.removeEventListener("sendora:locationchange",Z))}if(t.appBackground||t.engagement){let d=()=>{if(document.visibilityState==="hidden"){q(),t.appBackground&&(m(!0),r.track("app.backgrounded",{sessionId:a()||void 0}));try{r.flush()}catch{}}else document.visibilityState==="visible"&&(t.appBackground&&(m(!0),r.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{r.flush()}catch{}};window.addEventListener("pagehide",k),n.push(()=>window.removeEventListener("pagehide",k))}}let ae=0,E=()=>{let d=Date.now();d-ae<5e3||(ae=d,m(!0))};return window.addEventListener("click",E,{passive:!0}),window.addEventListener("keydown",E,{passive:!0}),window.addEventListener("scroll",E,{passive:!0}),n.push(()=>{window.removeEventListener("click",E),window.removeEventListener("keydown",E),window.removeEventListener("scroll",E)}),()=>{q();for(let d of n)try{d()}catch{}}}function ve(r){try{return window.sessionStorage?.getItem(r)??null}catch{return null}}function ke(r,e){try{window.sessionStorage?.setItem(r,e)}catch{}}var K=class r{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(!r.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 s=await navigator.serviceWorker.getRegistration(e.scope??"/");s||(s=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 s.pushManager.getSubscription()??await s.pushManager.subscribe({userVisibleOnly:!0,applicationServerKey:je(i)}),c=a.toJSON(),l=c.endpoint,I=c.keys?.p256dh??"",f=c.keys?.auth??"",m=await Ge(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(!r.isSupported())return!1;let t=await navigator.serviceWorker.getRegistration(e);if(!t)return!1;let s=await t.pushManager.getSubscription();return s?await s.unsubscribe():!1}async trackOpen(e,t){let s={sendId:e};t!==void 0&&(s.clickAction=t);let n=await this.http.post("/push/track-open",s);if(!n.success)throw new Error(n.error?.message??"push.trackOpen failed")}};function je(r){let e="=".repeat((4-r.length%4)%4),t=(r+e).replace(/-/g,"+").replace(/_/g,"/"),s=atob(t),n=new Uint8Array(s.length);for(let i=0;i<s.length;i++)n[i]=s.charCodeAt(i);return n}async function Ge(r){let e=new TextEncoder().encode(r),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){this.http=e}base(){return"/audiences"}async list(){let e=await this.http.get(this.base());return e.success?e.data??[]:[]}async get(e){let t=await this.http.get(`${this.base()}/${e}`);return t.success?t.data??null:null}async create(e){let t=await this.http.post(this.base(),e);return t.success?t.data??null:null}async update(e,t){let s=await this.http.patch(`${this.base()}/${e}`,t);return s.success?s.data??null:null}async delete(e){return(await this.http.delete(`${this.base()}/${e}`)).success}};var h=class extends Error{constructor(e,t,s){super(t),this.name="LinkError",this.code=e,this.statusCode=s}};function ne(r,e){let t=(r??"").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=${r??"none"})`)}var ie=/^[a-z0-9-]{3,20}$/;function Ye(r){try{let t=new URL(r).pathname.split("/").filter(Boolean);if(t.length===0)return null;let s=t[t.length-1];return ie.test(s)?s:null}catch{return null}}var Qe=5*6e4,Xe=50,Je=5,V=class{constructor(e){this.http=e;this.prewarmCache=new Map;this.prewarmInflight=0}cacheKey(e,t){if(t)return`k:${t}`;let s={},n=e;for(let i of Object.keys(n).sort())s[i]=n[i];return`s:${JSON.stringify(s)}`}evictExpired(){let e=Date.now();for(let[t,s]of this.prewarmCache)e-s.ts>Qe&&this.prewarmCache.delete(t);for(;this.prewarmCache.size>Xe;){let t=this.prewarmCache.keys().next().value;if(t===void 0)break;this.prewarmCache.delete(t)}}prewarm(e,t){this.evictExpired();let s=this.cacheKey(e,t?.key);if(this.prewarmCache.has(s)||this.prewarmInflight>=Je)return;this.prewarmInflight++;let n=this.doCreate(e).catch(i=>{throw this.prewarmCache.delete(s),i}).finally(()=>{this.prewarmInflight--});this.prewarmCache.set(s,{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 s=this.cacheKey(e,t?.key),n=this.prewarmCache.get(s);return n?(this.prewarmCache.delete(s),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 s=await this.http.post("/sdk/links",t);if(!s.success||!s.data)throw ne(s.error?.code,s.error?.message??"links.create failed");return s.data}async revoke(e){if(!ie.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 ne(t.error?.code,t.error?.message??"links.revoke failed");return{shortcode:t.data.shortcode,revoked:!0}}async getStats(e){if(!ie.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 ne(t.error?.code,t.error?.message??"links.getStats failed");return t.data}};var G=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 Y=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 Q=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 s=await this.http.get(`/kb/search?${t.toString()}`);if(!s.success||!Array.isArray(s.data))throw new Error(s.error?.message??"kb.search failed");return s.data}};var Ze="3.0.1",X=class X{constructor(e){this.autoTrackCleanup=null;this.http=new B(e),this.storage=new j,this.consent=new O(e.defaultConsent===!0,this.http),this.analytics=new P(e,this.http,this.storage,this.consent),this.flags=new L(e,this.http,this.storage),this.surveys=new D(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 F(e,this.http,this.storage,this.analytics,this.flags,this.chatbot),this.webPush=new K(this.http),this.audiences=new $(this.http),this.links=new V(this.http),this.attribution=new G(this.http),this.support=new Y(this.http),this.kb=new Q(this.http),e.autoTrack!==!1&&(this.autoTrackCleanup=Ae(this.analytics,e))}static init(e){return pe(e.apiKey),new X(e)}static sanitizeHtml(e){return re(e)}static safeUrl(e){return b(e)}track(e,t){this.analytics.track(e,t)}page(e){this.analytics.page(e)}identify(e,t,s){this.analytics.identify(e,t,s),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()}};X.version=Ze;var oe=X,Vt=oe;export{P as Analytics,$ as Audiences,F as Auth,u as AuthError,x as Chatbot,O as Consent,H as EmailAlreadyTakenError,L as FeatureFlags,U as InAppMessaging,h as LinkError,V as Links,oe as SendoraCloud,D as Surveys,p as ValidationError,K as WebPush,Vt as default,Ye as extractShortcodeFromUrl,b as safeUrl,re as sanitizeHtml};
|
|
1
|
+
var de=/^[a-zA-Z][a-zA-Z0-9._:-]{0,127}$/;var Re=new Set(["__proto__","constructor","prototype"]),p=class extends Error{constructor(e){super(`[SendoraCloud] ${e}`),this.name="SendoraValidationError"}};function he(s){if(typeof s!="string"||!de.test(s))throw new p(`Invalid event name: must match ${de}, got "${String(s)}".`)}function pe(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=ge(s);if(e>5)throw new p(`Properties nested too deeply (${e} levels, max 5).`);fe(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 ge(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=ge(s[r],e+1);n>t&&(t=n)}return t}function fe(s){if(!(s==null||typeof s!="object"))for(let e of Object.keys(s)){if(Re.has(e))throw new p(`Forbidden property key "${e}" (prototype pollution guard).`);fe(s[e])}}var Ce=/^pk_[A-Za-z0-9_-]{16,}$/;function me(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(!Ce.test(s))throw new p("Invalid publishable key format. Expected pk_... (17+ chars).")}function ye(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 _e="https://api.sendoracloud.com",Pe=1e4,z=class{constructor(e){let t=(e.apiUrl??_e).replace(/\/$/,"");ye(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(),Pe):null;try{let c=await fetch(i,{method:e,headers:{"Content-Type":"application/json","X-API-Key":this.apiKey,...n??{}},body:r?JSON.stringify(r):void 0,signal:o?o.signal:void 0});a&&clearTimeout(a);let l;try{l=await c.json()}catch{l={success:!1,error:{code:"PARSE_ERROR",message:`HTTP ${c.status}`}}}if(this.debug&&console.debug(`[SendoraCloud] ${e} ${t} \u2192 ${c.status}`),!c.ok)if(t.startsWith("/auth-service/")&&!this.debug)console.warn(`[SendoraCloud] ${e} ${t} \u2192 HTTP ${c.status}`);else{let f=l?.error?.code??"HTTP_ERROR",m=l?.error?.message??`HTTP ${c.status}`,w=l?.error?.details?.fieldErrors,E=w?` (${Object.entries(w).map(([v,Z])=>`${v}: ${(Z??[]).join(", ")}`).join("; ")})`:"";console.warn(`[SendoraCloud] ${e} ${t} \u2192 HTTP ${c.status} \u2014 ${f}: ${m}${E}`)}return l}catch(c){a&&clearTimeout(a);let l=c instanceof Error&&c.name==="AbortError"?"timeout":"error";return console.warn(`[SendoraCloud] ${e} ${t} network ${l}`),{success:!1,error:{code:"NETWORK_ERROR",message:l}}}}};var 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 C(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 we={name:"@sendoracloud/sdk-web",version:"3.2.1",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 P=we.version;var ve=500,se=10,ne=6e4,D=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;let i=this.storage.get("anonymous_id");if(i?this.anonymousId=i:(this.anonymousId=C("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){he(e),t&&pe(t),this.queue.length>=ve&&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=C("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>se&&Date.now()-this.breakerOpenAt>=ne&&(this.consecutiveFailures=0,this.breakerOpenAt=0,this.breakerWarned=!1);let e=this.queue.splice(0,100),t=e.map(o=>({projectId:this.projectId,module:"custom",eventType:o.eventType,userId:o.userId,anonymousId:o.anonymousId,identityToken:o.identityToken,timestamp:o.timestamp??new Date().toISOString(),properties:o.properties??{},context:this.getContext(),consent:["analytics"]}));if((await this.http.post("/events/batch",{events:t})).success){this.consecutiveFailures=0,this.breakerOpenAt=0,this.breakerWarned=!1;return}if(this.consecutiveFailures++,this.consecutiveFailures>se){this.breakerWarned||(this.breakerOpenAt=Date.now(),this.breakerWarned=!0,console.warn(`[SendoraCloud] Analytics circuit breaker open after ${se} consecutive failed flushes \u2014 events will be dropped until a flush succeeds or ${ne}ms elapse.`));return}let n=Math.min(ne,1e3*2**this.consecutiveFailures);this.nextFlushAfter=Date.now()+n;let i=Math.max(0,ve-this.queue.length);i>0&&this.queue.unshift(...e.slice(0,i))}destroy(){this.timer&&clearInterval(this.timer),this.timer=null,this.flush()}getContext(){return typeof window>"u"?{sdk:{name:"@sendoracloud/sdk-web",version:P}}:{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,sdk:{name:"@sendoracloud/sdk-web",version:P}}}};var L=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)ie(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&&ie(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)ie(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 ie(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 Le=32*1024,U=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||!Ue(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>Le)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 Ue(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(xe)}function xe(s){if(s==null||typeof s!="object")return!1;let e=s;return typeof e.id=="string"&&typeof e.text=="string"}function b(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 Oe=new Set(["b","i","u","em","strong","a","p","br","span","ul","ol","li","h1","h2","h3","h4"]),Ne={a:new Set(["href","target","rel"])};function oe(s){if(typeof DOMParser>"u")return"";let t=new DOMParser().parseFromString(`<div>${s}</div>`,"text/html").body.firstElementChild;return t?(ke(t),t.innerHTML):""}function ke(s){let e=Array.from(s.children);for(let t of e){let r=t.tagName.toLowerCase();if(!Oe.has(r)){let i=document.createTextNode(t.textContent??"");t.replaceWith(i);continue}let n=Ne[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=b(i.value);o?t.setAttribute("href",o):t.removeAttribute(i.name)}}r==="a"&&(t.setAttribute("rel","noopener noreferrer"),t.setAttribute("target","_blank")),ke(t)}}var x=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(Me);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 Me(s){return{...s,content:{...s.content,ctaUrl:b(s.content.ctaUrl),imageUrl:b(s.content.imageUrl)}}}var He=4e3,O=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??C("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>He)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=C("chat_"),this.storage.set("chat_session_id",this.sessionId)}getSessionId(){return this.sessionId}};var N=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 S="auth_access_token",T="auth_access_expires",g="auth_refresh_token",M="auth_user",Fe=3e4,Ke=/^[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"||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"}},u=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(M),c=this.storage.get(S),l=this.storage.get(g),I=this.storage.get(T);if(a&&(c||l))try{let f=JSON.parse(a);if(typeof f.id!="string"||f.id.length===0)throw new Error("invalid cache");this.user=f,c?(this.accessToken=c,this.accessExpiresAt=I?Number(I):0):(this.accessToken=null,this.accessExpiresAt=0),this.analytics.identify(this.user.id)}catch{this.storage.remove(M),this.storage.remove(S),this.storage.remove(T),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||!Ke.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-Fe?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 u(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??"",c=o.error?.status;if(a==="CONFLICT"||a==="EMAIL_ALREADY_TAKEN"||c===409)throw new F(o.error?.message);if(a==="FORBIDDEN_NON_ANONYMOUS"||a==="INVALID_REFRESH"||a==="UNAUTHORIZED"||a==="REFRESH_EXPIRED")await this.analytics.flush(),this.wipeLocalIdentity();else throw new u(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 u(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 u(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 u(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 n=await this.http.post("/auth-service/login/social",{...e,...r?{prevAnonRefreshToken:r}:{}});if(!n.success||!y(n.data))throw new u(n.error?.code??"SOCIAL_LOGIN_FAILED",n.error?.message??"Social login failed");return this.persist(n.data),n.data.user})}signInWithGoogle(e,t){return this.loginSocial({provider:"google",code:e,redirectUri:t})}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})}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 u(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 u(t.error?.code??"MAGIC_LINK_REQUEST_FAILED",t.error?.message??"Failed to send link")}async sendEmailOtp(e){let t=await this.http.post("/auth-service/email-otp/request",{email:e});if(!t.success)throw new u(t.error?.code??"EMAIL_OTP_REQUEST_FAILED",t.error?.message??"Failed to send code")}verifyEmailOtp(e,t){return this.serialize(async()=>{let r=this.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 u(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 u(t.error?.code??"PASSWORD_RESET_REQUEST_FAILED",t.error?.message??"Failed to send reset email")}async resetPassword(e,t){let r=await this.http.post("/auth-service/password/reset",{token:e,newPassword:t});if(!r.success)throw new u(r.error?.code??"PASSWORD_RESET_FAILED",r.error?.message??"Reset failed")}async verifyEmail(e){let t=await this.http.post("/auth-service/email/verify",{token:e});if(!t.success)throw new u(t.error?.code??"EMAIL_VERIFICATION_FAILED",t.error?.message??"Verification failed")}async sendVerificationEmail(){let e=await this.getAccessToken();if(!e)throw new u("UNAUTHORIZED","Sign in before requesting a verification email");let t=await this.http.post("/auth-service/email/verify/resend",{},{Authorization:`Bearer ${e}`});if(!t.success)throw new u(t.error?.code??"VERIFICATION_RESEND_FAILED",t.error?.message??"Failed to send verification email")}verifyMagicLink(e){return this.serialize(async()=>{let t=this.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 u(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 u(e.error?.code??"MFA_ENROLL_FAILED",e.error?.message??"MFA enrollment failed");return e.data}async confirmMfa(e){let t=await this.http.post("/auth-service/mfa/enroll/confirm",{code:e},await this.bearerHeaders());return!!(t.success&&t.data?.confirmed)}async disableMfa(){await this.http.post("/auth-service/mfa/disable",{},await this.bearerHeaders())}async registerPasskey(e){if(typeof navigator>"u"||!navigator.credentials)throw new u("PASSKEY_UNSUPPORTED","WebAuthn not supported in this environment");let t=await this.http.post("/auth-service/passkeys/register/start",{},await this.bearerHeaders());if(!t.success||!t.data)throw new u(t.error?.code??"PASSKEY_START_FAILED",t.error?.message??"Passkey start failed");let r=$e(t.data),n;try{let a=await navigator.credentials.create({publicKey:r});if(!a)throw new Error("create returned null");n=a}catch(a){throw new u("PASSKEY_USER_CANCELLED",a instanceof Error?a.message:"User cancelled passkey registration")}let i=qe(n),o=await this.http.post("/auth-service/passkeys/register/finish",{response:i,name:e},await this.bearerHeaders());if(!o.success||!o.data)throw new u(o.error?.code??"PASSKEY_FINISH_FAILED",o.error?.message??"Passkey verification failed");return o.data}signInWithPasskey(e){return this.serialize(async()=>{if(typeof navigator>"u"||!navigator.credentials)throw new u("PASSKEY_UNSUPPORTED","WebAuthn not supported in this environment");let t=this.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 u(n.error?.code??"PASSKEY_START_FAILED",n.error?.message??"Passkey start failed");let i=We(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 u("PASSKEY_USER_CANCELLED",l instanceof Error?l.message:"User cancelled passkey signin")}let a=ze(o),c=await this.http.post("/auth-service/passkeys/authenticate/finish",{response:a,userId:e??null,...r?{prevAnonRefreshToken:r}:{}});if(!c.success||!y(c.data))throw new u(c.error?.code??"PASSKEY_AUTH_FAILED",c.error?.message??"Passkey signin failed");return this.persist(c.data),c.data.user})}async listPasskeys(){let e=await this.http.get("/auth-service/passkeys",await this.bearerHeaders());return!e.success||!Array.isArray(e.data)?[]:e.data}async deletePasskey(e){await this.http.delete(`/auth-service/passkeys/${e}`,await this.bearerHeaders())}async listMySessions(){let e=await this.http.get("/auth-service/sessions/me",await this.bearerHeaders());return!e.success||!Array.isArray(e.data)?[]:e.data}async revokeSession(e){await this.http.delete(`/auth-service/sessions/me/${e}`,await this.bearerHeaders())}async revokeAllSessions(){await this.http.delete("/auth-service/sessions/me",await this.bearerHeaders())}async startSso(e){let t=this.takeoverHint(),r=await this.http.post("/auth-service/sso/oidc/start",{returnTo:e,...t?{prevAnonRefreshToken:t}:{}});if(!r.success||!r.data)throw new u(r.error?.code??"SSO_START_FAILED",r.error?.message??"SSO start failed");return r.data}async signInWithSso(e){let t=e??(typeof window<"u"?window.location.origin:"");if(!t)throw new u("SSO_NO_RETURN_TO","returnTo required outside browser context");let r=await this.startSso(t);typeof window<"u"&&(window.location.href=r.authorizationUrl)}async startSaml(e){let t=this.takeoverHint(),r=await this.http.post("/auth-service/sso/saml/start",{returnTo:e,...t?{prevAnonRefreshToken:t}:{}});if(!r.success||!r.data)throw new u(r.error?.code??"SAML_START_FAILED",r.error?.message??"SAML start failed");return r.data}async signInWithSaml(e){let t=e??(typeof window<"u"?window.location.origin:"");if(!t)throw new u("SAML_NO_RETURN_TO","returnTo required outside browser context");let r=await this.startSaml(t);typeof window<"u"&&(window.location.href=r.authorizationUrl)}consumeSsoTokenFromUrl(){return this.serialize(async()=>{if(typeof window>"u")return null;let e=window.location.hash.replace(/^#/,"");if(!e)return null;let t=new URLSearchParams(e),r=t.get("sendora_oidc_error")??t.get("sendora_saml_error");if(r)throw window.history.replaceState(null,"",window.location.pathname+window.location.search),new u("SSO_FAILED",decodeURIComponent(r));let n=t.get("sendora_oidc_token")??t.get("sendora_saml_token");if(!n)return null;let i=t.get("sendora_retired_anon");window.history.replaceState(null,"",window.location.pathname+window.location.search),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 u(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(S,a.accessToken),this.storage.set(T,String(this.accessExpiresAt)),this.storage.set(g,a.refreshToken);let c=Be(a.accessToken),l={id:c?.sub??"",email:c?.email??null,emailVerified:c?.email_verified??!0,name:c?.name??null,isAnonymous:!1};return this.user=l,this.storage.set(M,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 u("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(S),o=this.storage.get(T);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 Ve(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(S,o.accessToken),this.storage.set(T,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(M,JSON.stringify(e.user)),this.storage.set(S,e.tokens.accessToken),this.storage.set(T,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(M),this.storage.remove(S),this.storage.remove(T),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 Ve(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 $e(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 We(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 qe(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 Be(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 ze(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 je(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 Ge=250,Ae=360*60*1e3;function Se(s,e){if(typeof window>"u")return()=>{};let t=je(e.autoTrack),r=e.sessionIdleMs??18e5,n=[],i="sendora_session_id",o="sendora_session_last_activity";function a(){return Ie(i)??""}function c(d){be(i,d)}function l(){let d=Ie(o);return d&&Number(d)||0}function I(d){be(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 B=f();c(B),d&&t.sessionStart&&s.track("session.start",{sessionId:B})}I(k)}m(t.sessionStart),t.appOpen&&s.track("app.opened",{sessionId:a()||void 0,url:window.location.origin+window.location.pathname,referrer:document.referrer||void 0});let w=window.location.pathname,E=0,v=document.visibilityState==="visible"?Date.now():0;function Z(){v>0&&(E+=Date.now()-v,v=0)}function q(){if(!t.engagement)return;Z();let d=E;if(E=0,!(d<Ge)){d>Ae&&(d=Ae);try{s.track("app.engagement",{durationMs:Math.round(d),screen:w,sessionId:a()||void 0})}catch{}}}function Te(d){w=d,E=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(),Te(window.location.pathname)},k=()=>d();window.addEventListener("popstate",k),n.push(()=>window.removeEventListener("popstate",k));let _=window;if(!_.__sendoraHistoryPatched){_.__sendoraHistoryPatched=!0;let B=history.pushState,Ee=history.replaceState;history.pushState=function(...te){let re=B.apply(this,te);return window.dispatchEvent(new Event("sendora:locationchange")),re},history.replaceState=function(...te){let re=Ee.apply(this,te);return window.dispatchEvent(new Event("sendora:locationchange")),re}}let ee=()=>d();window.addEventListener("sendora:locationchange",ee),n.push(()=>window.removeEventListener("sendora:locationchange",ee))}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 le=0,R=()=>{let d=Date.now();d-le<5e3||(le=d,m(!0))};return window.addEventListener("click",R,{passive:!0}),window.addEventListener("keydown",R,{passive:!0}),window.addEventListener("scroll",R,{passive:!0}),n.push(()=>{window.removeEventListener("click",R),window.removeEventListener("keydown",R),window.removeEventListener("scroll",R)}),()=>{q();for(let d of n)try{d()}catch{}}}function Ie(s){try{return window.sessionStorage?.getItem(s)??null}catch{return null}}function be(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:Ye(i)}),c=a.toJSON(),l=c.endpoint,I=c.keys?.p256dh??"",f=c.keys?.auth??"",m=await Qe(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 Ye(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 Qe(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 ae(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 ce=/^[a-z0-9-]{3,20}$/;function Xe(s){try{let t=new URL(s).pathname.split("/").filter(Boolean);if(t.length===0)return null;let r=t[t.length-1];return ce.test(r)?r:null}catch{return null}}var Je=5*6e4,Ze=50,et=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>Je&&this.prewarmCache.delete(t);for(;this.prewarmCache.size>Ze;){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>=et)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 ae(r.error?.code,r.error?.message??"links.create failed");return r.data}async revoke(e){if(!ce.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 ae(t.error?.code,t.error?.message??"links.revoke failed");return{shortcode:t.data.shortcode,revoked:!0}}async getStats(e){if(!ce.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 ae(t.error?.code,t.error?.message??"links.getStats failed");return t.data}};var Y=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 Q=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 X=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 J=class J{constructor(e){this.autoTrackCleanup=null;this.http=new z(e),this.storage=new G,this.consent=new N(e.defaultConsent===!0,this.http),this.analytics=new D(e,this.http,this.storage,this.consent),this.flags=new L(e,this.http,this.storage),this.surveys=new U(e,this.http,this.storage),this.messages=new x(e,this.http,this.storage),this.chatbot=new O(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 Y(this.http),this.support=new Q(this.http),this.kb=new X(this.http),e.autoTrack!==!1&&(this.autoTrackCleanup=Se(this.analytics,e))}static init(e){return me(e.apiKey),new J(e)}static sanitizeHtml(e){return oe(e)}static safeUrl(e){return b(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()}};J.version=P;var ue=J,Gt=ue;export{D as Analytics,$ as Audiences,K as Auth,u as AuthError,O as Chatbot,N as Consent,F as EmailAlreadyTakenError,L as FeatureFlags,x as InAppMessaging,h as LinkError,W as Links,ue as SendoraCloud,U as Surveys,p as ValidationError,V as WebPush,Gt as default,Xe as extractShortcodeFromUrl,b as safeUrl,oe as sanitizeHtml};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sendoracloud/sdk-web",
|
|
3
|
-
"version": "3.2.
|
|
3
|
+
"version": "3.2.1",
|
|
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",
|