@sendoracloud/sdk-web 2.20.0 → 3.0.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/README.md CHANGED
@@ -10,6 +10,21 @@ Full docs: [sendoracloud.com/sdks](https://sendoracloud.com/sdks)
10
10
  npm install @sendoracloud/sdk-web
11
11
  ```
12
12
 
13
+ ## Upgrading from 2.x
14
+
15
+ 3.0.0 drops the `orgId` init option — the backend now resolves the org from
16
+ your API key server-side. Migration: delete the `orgId` line from your
17
+ `SendoraCloud.init({...})` call. No other API changes.
18
+
19
+ ```diff
20
+ SendoraCloud.init({
21
+ apiKey: "pk_live_...",
22
+ - orgId: "<ORG_ID>",
23
+ });
24
+ ```
25
+
26
+ The `webPush.setOrgId(...)` helper is also removed for the same reason.
27
+
13
28
  ## Quick start
14
29
 
15
30
  ```ts
package/dist/index.cjs CHANGED
@@ -1 +1 @@
1
- "use strict";var ee=Object.defineProperty;var ve=Object.getOwnPropertyDescriptor;var ke=Object.getOwnPropertyNames;var Ae=Object.prototype.hasOwnProperty;var Ie=(r,e)=>{for(var t in e)ee(r,t,{get:e[t],enumerable:!0})},Se=(r,e,t,s)=>{if(e&&typeof e=="object"||typeof e=="function")for(let n of ke(e))!Ae.call(r,n)&&n!==t&&ee(r,n,{get:()=>e[n],enumerable:!(s=ve(e,n))||s.enumerable});return r};var be=r=>Se(ee({},"__esModule",{value:!0}),r);var Je={};Ie(Je,{Analytics:()=>R,Audiences:()=>N,Auth:()=>U,AuthError:()=>c,Chatbot:()=>L,Consent:()=>D,EmailAlreadyTakenError:()=>x,FeatureFlags:()=>C,InAppMessaging:()=>_,LinkError:()=>h,Links:()=>M,SendoraCloud:()=>Y,Surveys:()=>P,ValidationError:()=>p,WebPush:()=>O,default:()=>Xe,extractShortcodeFromUrl:()=>ye,safeUrl:()=>k,sanitizeHtml:()=>B});module.exports=be(Je);var ne=/^[a-zA-Z][a-zA-Z0-9._:-]{0,127}$/;var Te=new Set(["__proto__","constructor","prototype"]),p=class extends Error{constructor(e){super(`[SendoraCloud] ${e}`),this.name="SendoraValidationError"}};function ie(r){if(typeof r!="string"||!ne.test(r))throw new p(`Invalid event name: must match ${ne}, got "${String(r)}".`)}function oe(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=ae(r);if(e>5)throw new p(`Properties nested too deeply (${e} levels, max 5).`);ce(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 ae(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=ae(r[s],e+1);n>t&&(t=n)}return t}function ce(r){if(!(r==null||typeof r!="object"))for(let e of Object.keys(r)){if(Te.has(e))throw new p(`Forbidden property key "${e}" (prototype pollution guard).`);ce(r[e])}}var Ee=/^pk_[A-Za-z0-9_-]{16,}$/;function ue(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(!Ee.test(r))throw new p("Invalid publishable key format. Expected pk_... (17+ chars).")}function le(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",Ce=1e4,V=class{constructor(e){let t=(e.apiUrl??Re).replace(/\/$/,"");le(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(),Ce):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 d;try{d=await u.json()}catch{d={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=d?.error?.code??"HTTP_ERROR",y=d?.error?.message??`HTTP ${u.status}`,w=d?.error?.details?.fieldErrors,v=w?` (${Object.entries(w).map(([l,S])=>`${l}: ${(S??[]).join(", ")}`).join("; ")})`:"";console.warn(`[SendoraCloud] ${e} ${t} \u2192 HTTP ${u.status} \u2014 ${f}: ${y}${v}`)}return d}catch(u){a&&clearTimeout(a);let d=u instanceof Error&&u.name==="AbortError"?"timeout":"error";return console.warn(`[SendoraCloud] ${e} ${t} network ${d}`),{success:!1,error:{code:"NETWORK_ERROR",message:d}}}}};var q="sendora_",W=class{constructor(){this.memoryStore=new Map;this.useLocalStorage=this.checkLocalStorage()}get(e){let t=q+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=q+e;if(this.useLocalStorage)try{localStorage.setItem(s,t);return}catch{}this.memoryStore.set(s,t)}remove(e){let t=q+e;if(this.useLocalStorage)try{localStorage.removeItem(t)}catch{}this.memoryStore.delete(t)}clearPrefix(e){let t=q+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 E(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 de="2.9.0",he=500,Pe=10,_e=6e4,R=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=E("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){ie(e),t&&oe(t),this.queue.length>=he&&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=E("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>Pe)return;let n=Math.min(_e,1e3*2**this.consecutiveFailures);this.nextFlushAfter=Date.now()+n;let i=Math.max(0,he-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:de}}:{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:de}}}};var C=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)te(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&&te(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)te(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 te(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 Le=32*1024,P=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||!De(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>Le)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 De(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(xe)}function xe(r){if(r==null||typeof r!="object")return!1;let e=r;return typeof e.id=="string"&&typeof e.text=="string"}function k(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"]),Oe={a:new Set(["href","target","rel"])};function B(r){if(typeof DOMParser>"u")return"";let t=new DOMParser().parseFromString(`<div>${r}</div>`,"text/html").body.firstElementChild;return t?(pe(t),t.innerHTML):""}function pe(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=Oe[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=k(i.value);o?t.setAttribute("href",o):t.removeAttribute(i.name)}}s==="a"&&(t.setAttribute("rel","noopener noreferrer"),t.setAttribute("target","_blank")),pe(t)}}var _=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(Ne);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 Ne(r){return{...r,content:{...r.content,ctaUrl:k(r.content.ctaUrl),imageUrl:k(r.content.imageUrl)}}}var Me=4e3,L=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??E("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>Me)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=E("chat_"),this.storage.set("chat_session_id",this.sessionId)}getSessionId(){return this.sessionId}};var D=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 b="auth_access_token",T="auth_access_expires",g="auth_refresh_token",F="auth_user",He=3e4;function m(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")}var x=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}},U=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.proactiveRefreshTimer=null;this.proactiveRefreshCleanup=null;this.http=t,this.storage=s,this.analytics=n,this.flags=i,this.chatbot=o;let a=this.storage.get(F),u=this.storage.get(b),d=this.storage.get(g),I=this.storage.get(T);if(a&&(u||d))try{let f=JSON.parse(a);if(typeof f.id!="string"||f.id.length===0)throw new Error("invalid cache");this.user=f,u?(this.accessToken=u,this.accessExpiresAt=I?Number(I):0):(this.accessToken=null,this.accessExpiresAt=0),this.analytics.identify(this.user.id)}catch{this.storage.remove(F),this.storage.remove(b),this.storage.remove(T),this.storage.remove(g)}}getCurrentUser(){return this.user}async getAccessToken(){return this.accessToken?this.accessExpiresAt&&Date.now()<this.accessExpiresAt-He?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||!m(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&&m(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 x(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||!m(i.data)){let o=i.error?.code??"";throw o==="CONFLICT"?new x(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()=>{this.user!==null&&(await this.analytics.flush(),this.wipeLocalIdentity());let n=await this.http.post("/auth-service/login",{email:e,password:t});if(!n.success||!n.data)throw new c(n.error?.code??"AUTH_ERROR",n.error?.message??"Login failed");let i=n.data;if(i.mfaRequired===!0&&typeof i.mfaChallengeToken=="string"){let o=i.user;return{mfaRequired:!0,mfaChallengeToken:i.mfaChallengeToken,userId:o?.id??""}}if(!m(n.data))throw new c(n.error?.code??"AUTH_ERROR",n.error?.message??"Login failed");return this.persist(n.data),n.data.user})}loginSocial(e){return this.serialize(async()=>{this.user!==null&&(await this.analytics.flush(),this.wipeLocalIdentity());let s=await this.http.post("/auth-service/login/social",e);if(!s.success||!m(s.data))throw new c(s.error?.code??"SOCIAL_LOGIN_FAILED",s.error?.message??"Social login failed");return this.persist(s.data),s.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=await this.http.post("/auth-service/mfa/challenge",{challengeToken:e,code:t});if(!s.success||!m(s.data))throw new c(s.error?.code??"MFA_CHALLENGE_FAILED",s.error?.message??"Invalid MFA code");return this.persist(s.data),s.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()=>{this.user!==null&&(await this.analytics.flush(),this.wipeLocalIdentity());let n=await this.http.post("/auth-service/email-otp/verify",{email:e,code:t});if(!n.success||!m(n.data))throw new c(n.error?.code??"EMAIL_OTP_INVALID",n.error?.message??"Wrong or expired code");return this.persist(n.data),n.data.user})}async requestPasswordReset(e){let t=await this.http.post("/auth-service/password/forgot",{email:e});if(!t.success)throw new 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()=>{this.user!==null&&(await this.analytics.flush(),this.wipeLocalIdentity());let s=await this.http.post("/auth-service/magic-link/verify",{token:e});if(!s.success||!m(s.data))throw new c(s.error?.code??"MAGIC_LINK_INVALID",s.error?.message??"Invalid or expired magic link");return this.persist(s.data),s.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=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 c("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 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");this.user!==null&&(await this.analytics.flush(),this.wipeLocalIdentity());let s=await this.http.post("/auth-service/passkeys/authenticate/start",{userId:e??null});if(!s.success||!s.data)throw new c(s.error?.code??"PASSKEY_START_FAILED",s.error?.message??"Passkey start failed");let n=$e(s.data),i;try{let u=await navigator.credentials.get({publicKey:n});if(!u)throw new Error("get returned null");i=u}catch(u){throw new c("PASSKEY_USER_CANCELLED",u instanceof Error?u.message:"User cancelled passkey signin")}let o=We(i),a=await this.http.post("/auth-service/passkeys/authenticate/finish",{response:o,userId:e??null});if(!a.success||!m(a.data))throw new c(a.error?.code??"PASSKEY_AUTH_FAILED",a.error?.message??"Passkey signin failed");return this.persist(a.data),a.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=await this.http.post("/auth-service/sso/oidc/start",{returnTo:e});if(!t.success||!t.data)throw new c(t.error?.code??"SSO_START_FAILED",t.error?.message??"SSO start failed");return t.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=await this.http.post("/auth-service/sso/saml/start",{returnTo:e});if(!t.success||!t.data)throw new c(t.error?.code??"SAML_START_FAILED",t.error?.message??"SAML start failed");return t.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;window.history.replaceState(null,"",window.location.pathname+window.location.search),this.user!==null&&(await this.analytics.flush(),this.wipeLocalIdentity());let i=await this.http.post("/auth-service/token/refresh",{refreshToken:n});if(!i.success||!i.data)throw new c(i.error?.code??"SSO_EXCHANGE_FAILED",i.error?.message??"Could not exchange SSO token");let o=i.data;this.accessToken=o.accessToken,this.accessExpiresAt=Date.now()+o.expiresIn*1e3,this.storage.set(b,o.accessToken),this.storage.set(T,String(this.accessExpiresAt)),this.storage.set(g,o.refreshToken);let a=qe(o.accessToken),u={id:a?.sub??"",email:a?.email??null,emailVerified:a?.email_verified??!0,name:a?.name??null,isAnonymous:!1};return this.user=u,this.storage.set(F,JSON.stringify(u)),this.analytics.identify(u.id),this.flags.setContext({userId:u.id}),u})}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(b),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(),null;if(!i.data||typeof i.data!="object"||typeof i.data.accessToken!="string"||typeof i.data.refreshToken!="string"||typeof i.data.expiresIn!="number")return null;let o=i.data;return this.accessToken=o.accessToken,this.accessExpiresAt=Date.now()+o.expiresIn*1e3,this.storage.set(b,o.accessToken),this.storage.set(T,String(this.accessExpiresAt)),this.storage.set(g,o.refreshToken),o.accessToken}catch{return 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}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 d=Math.max(a,5*6e4)*(1-e)+(Math.random()*s*2-s);a<=d&&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(F,JSON.stringify(e.user)),this.storage.set(b,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()}wipeLocalIdentity(){this.user=null,this.accessToken=null,this.accessExpiresAt=0,this.storage.remove(F),this.storage.remove(b),this.storage.remove(T),this.storage.remove(g),this.analytics.reset(),this.analytics.dropQueue(),this.flags.reset(),this.chatbot.newSession(),this.stopProactiveRefreshCron()}};function Fe(r){return r?r==="INVALID_REFRESH_TOKEN"||r==="UNAUTHORIZED"||r==="HTTP_401"||r==="RATE_LIMIT_EXCEEDED"||r==="RATE_LIMIT":!1}function K(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:K(r.challenge),rp:r.rp,user:{id:K(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:K(e.id),type:"public-key",transports:e.transports})),authenticatorSelection:r.authenticatorSelection}}function $e(r){return{challenge:K(r.challenge),timeout:r.timeout,rpId:r.rpId,allowCredentials:r.allowCredentials?.map(e=>({id:K(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}:r===!0||r===void 0?{pageViews:!0,sessionStart:!0,appOpen:!0,appBackground:!0}:{pageViews:r.pageViews??!0,sessionStart:r.sessionStart??!0,appOpen:r.appOpen??!0,appBackground:r.appBackground??!0}}function me(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 ge(i)??""}function u(l){fe(i,l)}function d(){let l=ge(o);return l&&Number(l)||0}function I(l){fe(o,String(l))}function f(){return`sess_${Date.now().toString(36)}_${Math.random().toString(36).slice(2,10)}`}function y(l){let S=Date.now(),H=d();if(!a()||H&&S-H>s){let $=f();u($),l&&t.sessionStart&&r.track("session.start",{sessionId:$})}I(S)}if(y(t.sessionStart),t.appOpen&&r.track("app.opened",{sessionId:a()||void 0,url:window.location.origin+window.location.pathname,referrer:document.referrer||void 0}),t.pageViews){r.page();let l=()=>{y(!0),r.page()},S=()=>l();window.addEventListener("popstate",S),n.push(()=>window.removeEventListener("popstate",S));let H=window;if(!H.__sendoraHistoryPatched){H.__sendoraHistoryPatched=!0;let $=history.pushState,we=history.replaceState;history.pushState=function(...J){let Z=$.apply(this,J);return window.dispatchEvent(new Event("sendora:locationchange")),Z},history.replaceState=function(...J){let Z=we.apply(this,J);return window.dispatchEvent(new Event("sendora:locationchange")),Z}}let X=()=>l();window.addEventListener("sendora:locationchange",X),n.push(()=>window.removeEventListener("sendora:locationchange",X))}if(t.appBackground){let l=()=>{y(!0),document.visibilityState==="hidden"?r.track("app.backgrounded",{sessionId:a()||void 0}):document.visibilityState==="visible"&&r.track("app.foregrounded",{sessionId:a()||void 0})};document.addEventListener("visibilitychange",l),n.push(()=>document.removeEventListener("visibilitychange",l))}let w=0,v=()=>{let l=Date.now();l-w<5e3||(w=l,y(!0))};return window.addEventListener("click",v,{passive:!0}),window.addEventListener("keydown",v,{passive:!0}),window.addEventListener("scroll",v,{passive:!0}),n.push(()=>{window.removeEventListener("click",v),window.removeEventListener("keydown",v),window.removeEventListener("scroll",v)}),()=>{for(let l of n)try{l()}catch{}}}function ge(r){try{return window.sessionStorage?.getItem(r)??null}catch{return null}}function fe(r,e){try{window.sessionStorage?.setItem(r,e)}catch{}}var O=class r{constructor(e,t){this.orgId=null;this.http=e,this.orgId=t??null}setOrgId(e){this.orgId=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.");if(!this.orgId)throw new Error("[sendoracloud] WebPush.subscribe needs orgId \u2014 pass via SendoraCloud.init({ orgId }) or call webPush.setOrgId().");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(`/orgs/${this.orgId}/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:ze(i)}),u=a.toJSON(),d=u.endpoint,I=u.keys?.p256dh??"",f=u.keys?.auth??"",y=await je(d),w=await this.http.post("/push/tokens",{orgId:this.orgId,platform:"web",token:y,webEndpoint:d,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 ze(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 je(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 N=class{constructor(e,t){this.http=e,this.orgId=t}setOrgId(e){this.orgId=e}base(){if(!this.orgId)throw new Error("Audiences API requires orgId \u2014 pass it via SendoraCloud.init({ orgId }) or call audiences.setOrgId() before use.");return`/orgs/${this.orgId}/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 se(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 re=/^[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 re.test(s)?s:null}catch{return null}}var Ge=5*6e4,Ye=50,M=class{constructor(e){this.http=e;this.prewarmCache=new Map}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>Ge&&this.prewarmCache.delete(t);for(;this.prewarmCache.size>Ye;){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))return;let n=this.doCreate(e).catch(i=>{throw this.prewarmCache.delete(s),i});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 se(s.error?.code,s.error?.message??"links.create failed");return s.data}async revoke(e){if(!re.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 se(t.error?.code,t.error?.message??"links.revoke failed");return{shortcode:t.data.shortcode,revoked:!0}}async getStats(e){if(!re.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 se(t.error?.code,t.error?.message??"links.getStats failed");return t.data}};var z=class{constructor(e){this.http=e}async reportInstall(e={}){let t=await this.http.post("/attribution/install",e);if(!t.success||!t.data)throw new Error(t.error?.message??"attribution.reportInstall failed");return t.data}async checkDeferred(e={}){let t=await this.http.post("/attribution/deferred",e);if(!t.success)throw new Error(t.error?.message??"attribution.checkDeferred failed");return t.data??null}};var j=class{constructor(e){this.http=e}async createTicket(e){let t=await this.http.post("/support/tickets",e);if(!t.success||!t.data)throw new Error(t.error?.message??"support.createTicket failed");return t.data}async submitCsat(e){let t=await this.http.post("/support/csat",e);if(!t.success||!t.data)throw new Error(t.error?.message??"support.submitCsat failed");return t.data}};var G=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 Qe="2.19.0",Q=class Q{constructor(e){this.autoTrackCleanup=null;this.http=new V(e),this.storage=new W,this.consent=new D(e.defaultConsent===!0,this.http),this.analytics=new R(e,this.http,this.storage,this.consent),this.flags=new C(e,this.http,this.storage),this.surveys=new P(e,this.http,this.storage),this.messages=new _(e,this.http,this.storage),this.chatbot=new L(e,this.http,this.storage),this.auth=new U(e,this.http,this.storage,this.analytics,this.flags,this.chatbot),this.webPush=new O(this.http,e.orgId),this.audiences=new N(this.http,e.orgId),this.links=new M(this.http),this.attribution=new z(this.http),this.support=new j(this.http),this.kb=new G(this.http),e.autoTrack!==!1&&(this.autoTrackCleanup=me(this.analytics,e))}static init(e){return ue(e.apiKey),new Q(e)}static sanitizeHtml(e){return B(e)}static safeUrl(e){return k(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()}};Q.version=Qe;var Y=Q,Xe=Y;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 ee=Object.defineProperty;var ve=Object.getOwnPropertyDescriptor;var ke=Object.getOwnPropertyNames;var Ae=Object.prototype.hasOwnProperty;var Ie=(s,e)=>{for(var t in e)ee(s,t,{get:e[t],enumerable:!0})},Se=(s,e,t,r)=>{if(e&&typeof e=="object"||typeof e=="function")for(let n of ke(e))!Ae.call(s,n)&&n!==t&&ee(s,n,{get:()=>e[n],enumerable:!(r=ve(e,n))||r.enumerable});return s};var be=s=>Se(ee({},"__esModule",{value:!0}),s);var Ze={};Ie(Ze,{Analytics:()=>R,Audiences:()=>N,Auth:()=>U,AuthError:()=>c,Chatbot:()=>L,Consent:()=>D,EmailAlreadyTakenError:()=>x,FeatureFlags:()=>C,InAppMessaging:()=>P,LinkError:()=>h,Links:()=>M,SendoraCloud:()=>Y,Surveys:()=>_,ValidationError:()=>p,WebPush:()=>O,default:()=>Je,extractShortcodeFromUrl:()=>ye,safeUrl:()=>k,sanitizeHtml:()=>B});module.exports=be(Ze);var ne=/^[a-zA-Z][a-zA-Z0-9._:-]{0,127}$/;var Te=new Set(["__proto__","constructor","prototype"]),p=class extends Error{constructor(e){super(`[SendoraCloud] ${e}`),this.name="SendoraValidationError"}};function ie(s){if(typeof s!="string"||!ne.test(s))throw new p(`Invalid event name: must match ${ne}, got "${String(s)}".`)}function oe(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=ae(s);if(e>5)throw new p(`Properties nested too deeply (${e} levels, max 5).`);ce(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 ae(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=ae(s[r],e+1);n>t&&(t=n)}return t}function ce(s){if(!(s==null||typeof s!="object"))for(let e of Object.keys(s)){if(Te.has(e))throw new p(`Forbidden property key "${e}" (prototype pollution guard).`);ce(s[e])}}var Ee=/^pk_[A-Za-z0-9_-]{16,}$/;function ue(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(!Ee.test(s))throw new p("Invalid publishable key format. Expected pk_... (17+ chars).")}function le(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 Re="https://api.sendoracloud.com",Ce=1e4,V=class{constructor(e){let t=(e.apiUrl??Re).replace(/\/$/,"");le(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(),Ce):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",y=l?.error?.message??`HTTP ${u.status}`,w=l?.error?.details?.fieldErrors,v=w?` (${Object.entries(w).map(([d,S])=>`${d}: ${(S??[]).join(", ")}`).join("; ")})`:"";console.warn(`[SendoraCloud] ${e} ${t} \u2192 HTTP ${u.status} \u2014 ${f}: ${y}${v}`)}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 q="sendora_",W=class{constructor(){this.memoryStore=new Map;this.useLocalStorage=this.checkLocalStorage()}get(e){let t=q+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=q+e;if(this.useLocalStorage)try{localStorage.setItem(r,t);return}catch{}this.memoryStore.set(r,t)}remove(e){let t=q+e;if(this.useLocalStorage)try{localStorage.removeItem(t)}catch{}this.memoryStore.delete(t)}clearPrefix(e){let t=q+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 E(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 de="2.9.0",he=500,_e=10,Pe=6e4,R=class{constructor(e,t,r,n){this.queue=[];this.timer=null;this.userId=null;this.identityToken=null;this.consecutiveFailures=0;this.nextFlushAfter=0;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=E("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){ie(e),t&&oe(t),this.queue.length>=he&&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=E("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>_e)return;let n=Math.min(Pe,1e3*2**this.consecutiveFailures);this.nextFlushAfter=Date.now()+n;let i=Math.max(0,he-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:de}}:{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:de}}}};var C=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)te(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&&te(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)te(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 te(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,_=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||!De(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 De(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 k(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 Ue=new Set(["b","i","u","em","strong","a","p","br","span","ul","ol","li","h1","h2","h3","h4"]),Oe={a:new Set(["href","target","rel"])};function B(s){if(typeof DOMParser>"u")return"";let t=new DOMParser().parseFromString(`<div>${s}</div>`,"text/html").body.firstElementChild;return t?(pe(t),t.innerHTML):""}function pe(s){let e=Array.from(s.children);for(let t of e){let r=t.tagName.toLowerCase();if(!Ue.has(r)){let i=document.createTextNode(t.textContent??"");t.replaceWith(i);continue}let n=Oe[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=k(i.value);o?t.setAttribute("href",o):t.removeAttribute(i.name)}}r==="a"&&(t.setAttribute("rel","noopener noreferrer"),t.setAttribute("target","_blank")),pe(t)}}var P=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(Ne);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 Ne(s){return{...s,content:{...s.content,ctaUrl:k(s.content.ctaUrl),imageUrl:k(s.content.imageUrl)}}}var Me=4e3,L=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??E("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>Me)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=E("chat_"),this.storage.set("chat_session_id",this.sessionId)}getSessionId(){return this.sessionId}};var D=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 b="auth_access_token",T="auth_access_expires",g="auth_refresh_token",F="auth_user",He=3e4,Fe=/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;function m(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 x=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}},U=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.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(F),u=this.storage.get(b),l=this.storage.get(g),I=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=I?Number(I):0):(this.accessToken=null,this.accessExpiresAt=0),this.analytics.identify(this.user.id)}catch{this.storage.remove(F),this.storage.remove(b),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||!Fe.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-He?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||!m(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&&m(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 x(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||!m(i.data)){let o=i.error?.code??"";throw o==="CONFLICT"?new x(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(!m(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||!m(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||!m(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||!m(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||!m(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 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 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,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=Ve(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=Be(o),u=await this.http.post("/auth-service/passkeys/authenticate/finish",{response:a,userId:e??null,...r?{prevAnonRefreshToken:r}:{}});if(!u.success||!m(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(),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(b,a.accessToken),this.storage.set(T,String(this.accessExpiresAt)),this.storage.set(g,a.refreshToken);let u=We(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(F,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,r=async()=>{let n=this.storage.get(g);if(n&&n!==e){let i=this.storage.get(b),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 Ke(i.error?.code)&&this.wipeLocalIdentity(),null;if(!i.data||typeof i.data!="object"||typeof i.data.accessToken!="string"||typeof i.data.refreshToken!="string"||typeof i.data.expiresIn!="number")return null;let o=i.data;return this.accessToken=o.accessToken,this.accessExpiresAt=Date.now()+o.expiresIn*1e3,this.storage.set(b,o.accessToken),this.storage.set(T,String(this.accessExpiresAt)),this.storage.set(g,o.refreshToken),o.accessToken}catch{return 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}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(F,JSON.stringify(e.user)),this.storage.set(b,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(),e.retiredAnonUserId&&this.fireDeviceTakeover(e.retiredAnonUserId,e.user.id)}wipeLocalIdentity(){this.user=null,this.accessToken=null,this.accessExpiresAt=0,this.storage.remove(F),this.storage.remove(b),this.storage.remove(T),this.storage.remove(g),this.analytics.reset(),this.analytics.dropQueue(),this.flags.reset(),this.chatbot.newSession(),this.stopProactiveRefreshCron()}};function Ke(s){return s?s==="INVALID_REFRESH_TOKEN"||s==="UNAUTHORIZED"||s==="HTTP_401"||s==="RATE_LIMIT_EXCEEDED"||s==="RATE_LIMIT":!1}function K(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:K(s.challenge),rp:s.rp,user:{id:K(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:K(e.id),type:"public-key",transports:e.transports})),authenticatorSelection:s.authenticatorSelection}}function Ve(s){return{challenge:K(s.challenge),timeout:s.timeout,rpId:s.rpId,allowCredentials:s.allowCredentials?.map(e=>({id:K(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 We(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 Be(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 ze(s){return s===!1?{pageViews:!1,sessionStart:!1,appOpen:!1,appBackground:!1}:s===!0||s===void 0?{pageViews:!0,sessionStart:!0,appOpen:!0,appBackground:!0}:{pageViews:s.pageViews??!0,sessionStart:s.sessionStart??!0,appOpen:s.appOpen??!0,appBackground:s.appBackground??!0}}function me(s,e){if(typeof window>"u")return()=>{};let t=ze(e.autoTrack),r=e.sessionIdleMs??18e5,n=[],i="sendora_session_id",o="sendora_session_last_activity";function a(){return ge(i)??""}function u(d){fe(i,d)}function l(){let d=ge(o);return d&&Number(d)||0}function I(d){fe(o,String(d))}function f(){return`sess_${Date.now().toString(36)}_${Math.random().toString(36).slice(2,10)}`}function y(d){let S=Date.now(),H=l();if(!a()||H&&S-H>r){let $=f();u($),d&&t.sessionStart&&s.track("session.start",{sessionId:$})}I(S)}if(y(t.sessionStart),t.appOpen&&s.track("app.opened",{sessionId:a()||void 0,url:window.location.origin+window.location.pathname,referrer:document.referrer||void 0}),t.pageViews){s.page();let d=()=>{y(!0),s.page()},S=()=>d();window.addEventListener("popstate",S),n.push(()=>window.removeEventListener("popstate",S));let H=window;if(!H.__sendoraHistoryPatched){H.__sendoraHistoryPatched=!0;let $=history.pushState,we=history.replaceState;history.pushState=function(...J){let Z=$.apply(this,J);return window.dispatchEvent(new Event("sendora:locationchange")),Z},history.replaceState=function(...J){let Z=we.apply(this,J);return window.dispatchEvent(new Event("sendora:locationchange")),Z}}let X=()=>d();window.addEventListener("sendora:locationchange",X),n.push(()=>window.removeEventListener("sendora:locationchange",X))}if(t.appBackground){let d=()=>{y(!0),document.visibilityState==="hidden"?s.track("app.backgrounded",{sessionId:a()||void 0}):document.visibilityState==="visible"&&s.track("app.foregrounded",{sessionId:a()||void 0})};document.addEventListener("visibilitychange",d),n.push(()=>document.removeEventListener("visibilitychange",d))}let w=0,v=()=>{let d=Date.now();d-w<5e3||(w=d,y(!0))};return window.addEventListener("click",v,{passive:!0}),window.addEventListener("keydown",v,{passive:!0}),window.addEventListener("scroll",v,{passive:!0}),n.push(()=>{window.removeEventListener("click",v),window.removeEventListener("keydown",v),window.removeEventListener("scroll",v)}),()=>{for(let d of n)try{d()}catch{}}}function ge(s){try{return window.sessionStorage?.getItem(s)??null}catch{return null}}function fe(s,e){try{window.sessionStorage?.setItem(s,e)}catch{}}var O=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:je(i)}),u=a.toJSON(),l=u.endpoint,I=u.keys?.p256dh??"",f=u.keys?.auth??"",y=await Ge(l),w=await this.http.post("/push/tokens",{platform:"web",token:y,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 je(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 Ge(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 N=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 r=await this.http.patch(`${this.base()}/${e}`,t);return r.success?r.data??null:null}async delete(e){return(await this.http.delete(`${this.base()}/${e}`)).success}};var h=class extends Error{constructor(e,t,r){super(t),this.name="LinkError",this.code=e,this.statusCode=r}};function re(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 se=/^[a-z0-9-]{3,20}$/;function ye(s){try{let t=new URL(s).pathname.split("/").filter(Boolean);if(t.length===0)return null;let r=t[t.length-1];return se.test(r)?r:null}catch{return null}}var Ye=5*6e4,Qe=50,M=class{constructor(e){this.http=e;this.prewarmCache=new Map}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>Ye&&this.prewarmCache.delete(t);for(;this.prewarmCache.size>Qe;){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))return;let n=this.doCreate(e).catch(i=>{throw this.prewarmCache.delete(r),i});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 re(r.error?.code,r.error?.message??"links.create failed");return r.data}async revoke(e){if(!se.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 re(t.error?.code,t.error?.message??"links.revoke failed");return{shortcode:t.data.shortcode,revoked:!0}}async getStats(e){if(!se.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 re(t.error?.code,t.error?.message??"links.getStats failed");return t.data}};var z=class{constructor(e){this.http=e}async reportInstall(e={}){let t=await this.http.post("/attribution/install",e);if(!t.success||!t.data)throw new Error(t.error?.message??"attribution.reportInstall failed");return t.data}async checkDeferred(e={}){let t=await this.http.post("/attribution/deferred",e);if(!t.success)throw new Error(t.error?.message??"attribution.checkDeferred failed");return t.data??null}};var j=class{constructor(e){this.http=e}async createTicket(e){let t=await this.http.post("/support/tickets",e);if(!t.success||!t.data)throw new Error(t.error?.message??"support.createTicket failed");return t.data}async submitCsat(e){let t=await this.http.post("/support/csat",e);if(!t.success||!t.data)throw new Error(t.error?.message??"support.submitCsat failed");return t.data}};var G=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 Xe="3.0.1",Q=class Q{constructor(e){this.autoTrackCleanup=null;this.http=new V(e),this.storage=new W,this.consent=new D(e.defaultConsent===!0,this.http),this.analytics=new R(e,this.http,this.storage,this.consent),this.flags=new C(e,this.http,this.storage),this.surveys=new _(e,this.http,this.storage),this.messages=new P(e,this.http,this.storage),this.chatbot=new L(e,this.http,this.storage),this.auth=new U(e,this.http,this.storage,this.analytics,this.flags,this.chatbot),this.webPush=new O(this.http),this.audiences=new N(this.http),this.links=new M(this.http),this.attribution=new z(this.http),this.support=new j(this.http),this.kb=new G(this.http),e.autoTrack!==!1&&(this.autoTrackCleanup=me(this.analytics,e))}static init(e){return ue(e.apiKey),new Q(e)}static sanitizeHtml(e){return B(e)}static safeUrl(e){return k(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()}};Q.version=Xe;var Y=Q,Je=Y;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
@@ -2,10 +2,7 @@
2
2
  interface SendoraConfig {
3
3
  /** Publishable (pk_...) key. Secret (sk_...) keys are refused — they must never ship in browser bundles. */
4
4
  apiKey: string;
5
- /** Org UUID. Required for `webPush.subscribe()`. Backend derives from
6
- * apiKey for events, but Web Push needs it explicitly to fetch the
7
- * per-org VAPID public key. */
8
- orgId?: string;
5
+ /** Optional project scoping. Org is resolved server-side from the apiKey. */
9
6
  projectId?: string;
10
7
  /** Override API base URL. Must be https:// unless it's http://localhost. */
11
8
  apiUrl?: string;
@@ -382,6 +379,17 @@ interface AuthTokens {
382
379
  expiresIn: number;
383
380
  tokenType: string;
384
381
  }
382
+ /**
383
+ * Detail handed to onDeviceTakeover subscribers. Fires once per
384
+ * identified-signin call where the backend retired an anonymous
385
+ * user_id (anon → identified flip on the same device, s58.111+).
386
+ */
387
+ interface DeviceTakeoverEvent {
388
+ retiredAnonUserId: string;
389
+ identifiedUserId: string;
390
+ at: Date;
391
+ }
392
+ type DeviceTakeoverListener = (evt: DeviceTakeoverEvent) => void;
385
393
  declare class EmailAlreadyTakenError extends Error {
386
394
  constructor(message?: string);
387
395
  }
@@ -402,9 +410,50 @@ declare class Auth {
402
410
  private refreshInflight;
403
411
  private proactiveRefreshTimer;
404
412
  private proactiveRefreshCleanup;
413
+ private takeoverListeners;
414
+ private lastTakeover;
405
415
  constructor(_config: SendoraConfig, http: HttpClient, storage: Storage, analytics: Analytics, flags: FeatureFlags, chatbot: Chatbot);
406
416
  /** Returns the cached current user or null if signed out. */
407
417
  getCurrentUser(): AuthUser | null;
418
+ /**
419
+ * Register a callback fired when the backend retires an anon
420
+ * `user_id` during a signin on this tab. Use it to delete the
421
+ * matching row from any local mirror — Sendora's own platform
422
+ * records are already cleaned up server-side. Returns an
423
+ * unsubscribe function.
424
+ *
425
+ * Fires on every identified-signin path: signIn, loginSocial,
426
+ * verifyMagicLink, verifyEmailOtp, challengeMfa, signInWithPasskey,
427
+ * consumeSsoTokenFromUrl. Local-only — survives webhook receiver
428
+ * downtime. For server-pipeline cleanup also subscribe to the
429
+ * `auth.device_takeover` webhook.
430
+ */
431
+ onDeviceTakeover(listener: DeviceTakeoverListener): () => void;
432
+ /**
433
+ * Returns the most recent takeover the SDK observed in this tab,
434
+ * or null if none. Lets late subscribers pick up the takeover
435
+ * their handler missed.
436
+ */
437
+ getLastDeviceTakeover(): DeviceTakeoverEvent | null;
438
+ /**
439
+ * Internal — called from every identified-signin path with the
440
+ * `retiredAnonUserId` parsed off the backend response (or off
441
+ * the SSO redirect fragment). Snapshot + dispatch synchronously
442
+ * since handlers run on the same event loop turn as persist().
443
+ *
444
+ * Validates UUID shape before dispatch. The value comes from a URL
445
+ * fragment / response body that — under XSS — could be controlled.
446
+ * A non-UUID value handed to a listener that interpolates it into
447
+ * a DELETE call (the documented pattern) becomes a path-injection
448
+ * sink in the host app.
449
+ */
450
+ private fireDeviceTakeover;
451
+ /**
452
+ * Returns the stored anon refresh token iff the local subject is
453
+ * currently anonymous. Used by every identified-session-mint path
454
+ * so the backend can run device-takeover (s58.111+).
455
+ */
456
+ private takeoverHint;
408
457
  /**
409
458
  * Returns a non-expired access token or null. If the cached token
410
459
  * is expired but a refresh token exists, transparently refreshes
@@ -704,7 +753,8 @@ declare class Auth {
704
753
  * One-call wiring:
705
754
  * 1. Asks browser for Notification permission.
706
755
  * 2. Registers a service worker (caller-supplied path).
707
- * 3. Fetches the org's VAPID public key from `/orgs/:orgId/push/vapid`.
756
+ * 3. Fetches the org's VAPID public key from `/push/vapid` (org
757
+ * auto-resolved from the API key).
708
758
  * 4. Calls `pushManager.subscribe({ userVisibleOnly: true,
709
759
  * applicationServerKey: <urlBase64ToUint8> })`.
710
760
  * 5. POSTs the resulting subscription parts (endpoint + p256dh + auth)
@@ -720,10 +770,7 @@ declare class Auth {
720
770
  */
721
771
  declare class WebPush {
722
772
  private http;
723
- private orgId;
724
- constructor(http: HttpClient, orgId?: string);
725
- /** Set / override the orgId. Helper resolves it from config when omitted. */
726
- setOrgId(orgId: string): void;
773
+ constructor(http: HttpClient);
727
774
  /**
728
775
  * Cheap capability check. Returns false on Safari < 16, in iframes
729
776
  * without Permissions-Policy, in private browsing on some browsers,
@@ -801,7 +848,7 @@ interface UpdateAudienceInput {
801
848
  rules?: AudienceRuleGroup;
802
849
  }
803
850
  /**
804
- * Audiences API wrapper. Org-scoped; pass `orgId` once at construction time.
851
+ * Audiences API wrapper. Org is resolved server-side from the API key.
805
852
  *
806
853
  * Requires `sk_*` (secret) key — public keys can't read or mutate audiences.
807
854
  *
@@ -818,10 +865,7 @@ interface UpdateAudienceInput {
818
865
  */
819
866
  declare class Audiences {
820
867
  private http;
821
- private orgId?;
822
- constructor(http: HttpClient, orgId?: string);
823
- /** Override the orgId after construction (e.g. multi-org dashboards). */
824
- setOrgId(orgId: string): void;
868
+ constructor(http: HttpClient);
825
869
  private base;
826
870
  list(): Promise<Audience[]>;
827
871
  get(audienceId: string): Promise<Audience | null>;
@@ -1165,7 +1209,7 @@ declare class SendoraCloud {
1165
1209
  /** Return the URL if it's http(s); undefined otherwise. */
1166
1210
  static safeUrl(u?: string): string | undefined;
1167
1211
  /** SDK version. */
1168
- static readonly version = "2.19.0";
1212
+ static readonly version = "3.0.1";
1169
1213
  track(eventType: string, properties?: Record<string, unknown>): void;
1170
1214
  page(properties?: Record<string, unknown>): void;
1171
1215
  identify(userId: string, traits?: UserTraits, opts?: IdentifyOptions): void;
package/dist/index.d.ts CHANGED
@@ -2,10 +2,7 @@
2
2
  interface SendoraConfig {
3
3
  /** Publishable (pk_...) key. Secret (sk_...) keys are refused — they must never ship in browser bundles. */
4
4
  apiKey: string;
5
- /** Org UUID. Required for `webPush.subscribe()`. Backend derives from
6
- * apiKey for events, but Web Push needs it explicitly to fetch the
7
- * per-org VAPID public key. */
8
- orgId?: string;
5
+ /** Optional project scoping. Org is resolved server-side from the apiKey. */
9
6
  projectId?: string;
10
7
  /** Override API base URL. Must be https:// unless it's http://localhost. */
11
8
  apiUrl?: string;
@@ -382,6 +379,17 @@ interface AuthTokens {
382
379
  expiresIn: number;
383
380
  tokenType: string;
384
381
  }
382
+ /**
383
+ * Detail handed to onDeviceTakeover subscribers. Fires once per
384
+ * identified-signin call where the backend retired an anonymous
385
+ * user_id (anon → identified flip on the same device, s58.111+).
386
+ */
387
+ interface DeviceTakeoverEvent {
388
+ retiredAnonUserId: string;
389
+ identifiedUserId: string;
390
+ at: Date;
391
+ }
392
+ type DeviceTakeoverListener = (evt: DeviceTakeoverEvent) => void;
385
393
  declare class EmailAlreadyTakenError extends Error {
386
394
  constructor(message?: string);
387
395
  }
@@ -402,9 +410,50 @@ declare class Auth {
402
410
  private refreshInflight;
403
411
  private proactiveRefreshTimer;
404
412
  private proactiveRefreshCleanup;
413
+ private takeoverListeners;
414
+ private lastTakeover;
405
415
  constructor(_config: SendoraConfig, http: HttpClient, storage: Storage, analytics: Analytics, flags: FeatureFlags, chatbot: Chatbot);
406
416
  /** Returns the cached current user or null if signed out. */
407
417
  getCurrentUser(): AuthUser | null;
418
+ /**
419
+ * Register a callback fired when the backend retires an anon
420
+ * `user_id` during a signin on this tab. Use it to delete the
421
+ * matching row from any local mirror — Sendora's own platform
422
+ * records are already cleaned up server-side. Returns an
423
+ * unsubscribe function.
424
+ *
425
+ * Fires on every identified-signin path: signIn, loginSocial,
426
+ * verifyMagicLink, verifyEmailOtp, challengeMfa, signInWithPasskey,
427
+ * consumeSsoTokenFromUrl. Local-only — survives webhook receiver
428
+ * downtime. For server-pipeline cleanup also subscribe to the
429
+ * `auth.device_takeover` webhook.
430
+ */
431
+ onDeviceTakeover(listener: DeviceTakeoverListener): () => void;
432
+ /**
433
+ * Returns the most recent takeover the SDK observed in this tab,
434
+ * or null if none. Lets late subscribers pick up the takeover
435
+ * their handler missed.
436
+ */
437
+ getLastDeviceTakeover(): DeviceTakeoverEvent | null;
438
+ /**
439
+ * Internal — called from every identified-signin path with the
440
+ * `retiredAnonUserId` parsed off the backend response (or off
441
+ * the SSO redirect fragment). Snapshot + dispatch synchronously
442
+ * since handlers run on the same event loop turn as persist().
443
+ *
444
+ * Validates UUID shape before dispatch. The value comes from a URL
445
+ * fragment / response body that — under XSS — could be controlled.
446
+ * A non-UUID value handed to a listener that interpolates it into
447
+ * a DELETE call (the documented pattern) becomes a path-injection
448
+ * sink in the host app.
449
+ */
450
+ private fireDeviceTakeover;
451
+ /**
452
+ * Returns the stored anon refresh token iff the local subject is
453
+ * currently anonymous. Used by every identified-session-mint path
454
+ * so the backend can run device-takeover (s58.111+).
455
+ */
456
+ private takeoverHint;
408
457
  /**
409
458
  * Returns a non-expired access token or null. If the cached token
410
459
  * is expired but a refresh token exists, transparently refreshes
@@ -704,7 +753,8 @@ declare class Auth {
704
753
  * One-call wiring:
705
754
  * 1. Asks browser for Notification permission.
706
755
  * 2. Registers a service worker (caller-supplied path).
707
- * 3. Fetches the org's VAPID public key from `/orgs/:orgId/push/vapid`.
756
+ * 3. Fetches the org's VAPID public key from `/push/vapid` (org
757
+ * auto-resolved from the API key).
708
758
  * 4. Calls `pushManager.subscribe({ userVisibleOnly: true,
709
759
  * applicationServerKey: <urlBase64ToUint8> })`.
710
760
  * 5. POSTs the resulting subscription parts (endpoint + p256dh + auth)
@@ -720,10 +770,7 @@ declare class Auth {
720
770
  */
721
771
  declare class WebPush {
722
772
  private http;
723
- private orgId;
724
- constructor(http: HttpClient, orgId?: string);
725
- /** Set / override the orgId. Helper resolves it from config when omitted. */
726
- setOrgId(orgId: string): void;
773
+ constructor(http: HttpClient);
727
774
  /**
728
775
  * Cheap capability check. Returns false on Safari < 16, in iframes
729
776
  * without Permissions-Policy, in private browsing on some browsers,
@@ -801,7 +848,7 @@ interface UpdateAudienceInput {
801
848
  rules?: AudienceRuleGroup;
802
849
  }
803
850
  /**
804
- * Audiences API wrapper. Org-scoped; pass `orgId` once at construction time.
851
+ * Audiences API wrapper. Org is resolved server-side from the API key.
805
852
  *
806
853
  * Requires `sk_*` (secret) key — public keys can't read or mutate audiences.
807
854
  *
@@ -818,10 +865,7 @@ interface UpdateAudienceInput {
818
865
  */
819
866
  declare class Audiences {
820
867
  private http;
821
- private orgId?;
822
- constructor(http: HttpClient, orgId?: string);
823
- /** Override the orgId after construction (e.g. multi-org dashboards). */
824
- setOrgId(orgId: string): void;
868
+ constructor(http: HttpClient);
825
869
  private base;
826
870
  list(): Promise<Audience[]>;
827
871
  get(audienceId: string): Promise<Audience | null>;
@@ -1165,7 +1209,7 @@ declare class SendoraCloud {
1165
1209
  /** Return the URL if it's http(s); undefined otherwise. */
1166
1210
  static safeUrl(u?: string): string | undefined;
1167
1211
  /** SDK version. */
1168
- static readonly version = "2.19.0";
1212
+ static readonly version = "3.0.1";
1169
1213
  track(eventType: string, properties?: Record<string, unknown>): void;
1170
1214
  page(properties?: Record<string, unknown>): void;
1171
1215
  identify(userId: string, traits?: UserTraits, opts?: IdentifyOptions): void;
package/dist/index.js CHANGED
@@ -1 +1 @@
1
- var re=/^[a-zA-Z][a-zA-Z0-9._:-]{0,127}$/;var ye=new Set(["__proto__","constructor","prototype"]),p=class extends Error{constructor(e){super(`[SendoraCloud] ${e}`),this.name="SendoraValidationError"}};function ne(r){if(typeof r!="string"||!re.test(r))throw new p(`Invalid event name: must match ${re}, got "${String(r)}".`)}function ie(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=oe(r);if(e>5)throw new p(`Properties nested too deeply (${e} levels, max 5).`);ae(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 oe(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=oe(r[s],e+1);n>t&&(t=n)}return t}function ae(r){if(!(r==null||typeof r!="object"))for(let e of Object.keys(r)){if(ye.has(e))throw new p(`Forbidden property key "${e}" (prototype pollution guard).`);ae(r[e])}}var we=/^pk_[A-Za-z0-9_-]{16,}$/;function ce(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(!we.test(r))throw new p("Invalid publishable key format. Expected pk_... (17+ chars).")}function ue(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 ve="https://api.sendoracloud.com",ke=1e4,V=class{constructor(e){let t=(e.apiUrl??ve).replace(/\/$/,"");ue(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(),ke):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 d;try{d=await c.json()}catch{d={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=d?.error?.code??"HTTP_ERROR",y=d?.error?.message??`HTTP ${c.status}`,w=d?.error?.details?.fieldErrors,v=w?` (${Object.entries(w).map(([l,I])=>`${l}: ${(I??[]).join(", ")}`).join("; ")})`:"";console.warn(`[SendoraCloud] ${e} ${t} \u2192 HTTP ${c.status} \u2014 ${f}: ${y}${v}`)}return d}catch(c){a&&clearTimeout(a);let d=c instanceof Error&&c.name==="AbortError"?"timeout":"error";return console.warn(`[SendoraCloud] ${e} ${t} network ${d}`),{success:!1,error:{code:"NETWORK_ERROR",message:d}}}}};var q="sendora_",W=class{constructor(){this.memoryStore=new Map;this.useLocalStorage=this.checkLocalStorage()}get(e){let t=q+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=q+e;if(this.useLocalStorage)try{localStorage.setItem(s,t);return}catch{}this.memoryStore.set(s,t)}remove(e){let t=q+e;if(this.useLocalStorage)try{localStorage.removeItem(t)}catch{}this.memoryStore.delete(t)}clearPrefix(e){let t=q+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 E(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 le="2.9.0",de=500,Ae=10,Ie=6e4,C=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=E("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){ne(e),t&&ie(t),this.queue.length>=de&&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=E("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>Ae)return;let n=Math.min(Ie,1e3*2**this.consecutiveFailures);this.nextFlushAfter=Date.now()+n;let i=Math.max(0,de-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:le}}:{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:le}}}};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)J(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&&J(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)J(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 J(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 Se=32*1024,_=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||!be(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>Se)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 be(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(Te)}function Te(r){if(r==null||typeof r!="object")return!1;let e=r;return typeof e.id=="string"&&typeof e.text=="string"}function S(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 Ee=new Set(["b","i","u","em","strong","a","p","br","span","ul","ol","li","h1","h2","h3","h4"]),Re={a:new Set(["href","target","rel"])};function Z(r){if(typeof DOMParser>"u")return"";let t=new DOMParser().parseFromString(`<div>${r}</div>`,"text/html").body.firstElementChild;return t?(he(t),t.innerHTML):""}function he(r){let e=Array.from(r.children);for(let t of e){let s=t.tagName.toLowerCase();if(!Ee.has(s)){let i=document.createTextNode(t.textContent??"");t.replaceWith(i);continue}let n=Re[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=S(i.value);o?t.setAttribute("href",o):t.removeAttribute(i.name)}}s==="a"&&(t.setAttribute("rel","noopener noreferrer"),t.setAttribute("target","_blank")),he(t)}}var L=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(Ce);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 Ce(r){return{...r,content:{...r.content,ctaUrl:S(r.content.ctaUrl),imageUrl:S(r.content.imageUrl)}}}var Pe=4e3,D=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??E("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>Pe)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=E("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 b="auth_access_token",T="auth_access_expires",g="auth_refresh_token",U="auth_user",_e=3e4;function m(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")}var N=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}},M=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.proactiveRefreshTimer=null;this.proactiveRefreshCleanup=null;this.http=t,this.storage=s,this.analytics=n,this.flags=i,this.chatbot=o;let a=this.storage.get(U),c=this.storage.get(b),d=this.storage.get(g),A=this.storage.get(T);if(a&&(c||d))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=A?Number(A):0):(this.accessToken=null,this.accessExpiresAt=0),this.analytics.identify(this.user.id)}catch{this.storage.remove(U),this.storage.remove(b),this.storage.remove(T),this.storage.remove(g)}}getCurrentUser(){return this.user}async getAccessToken(){return this.accessToken?this.accessExpiresAt&&Date.now()<this.accessExpiresAt-_e?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||!m(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&&m(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 N(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||!m(i.data)){let o=i.error?.code??"";throw o==="CONFLICT"?new N(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()=>{this.user!==null&&(await this.analytics.flush(),this.wipeLocalIdentity());let n=await this.http.post("/auth-service/login",{email:e,password:t});if(!n.success||!n.data)throw new u(n.error?.code??"AUTH_ERROR",n.error?.message??"Login failed");let i=n.data;if(i.mfaRequired===!0&&typeof i.mfaChallengeToken=="string"){let o=i.user;return{mfaRequired:!0,mfaChallengeToken:i.mfaChallengeToken,userId:o?.id??""}}if(!m(n.data))throw new u(n.error?.code??"AUTH_ERROR",n.error?.message??"Login failed");return this.persist(n.data),n.data.user})}loginSocial(e){return this.serialize(async()=>{this.user!==null&&(await this.analytics.flush(),this.wipeLocalIdentity());let s=await this.http.post("/auth-service/login/social",e);if(!s.success||!m(s.data))throw new u(s.error?.code??"SOCIAL_LOGIN_FAILED",s.error?.message??"Social login failed");return this.persist(s.data),s.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=await this.http.post("/auth-service/mfa/challenge",{challengeToken:e,code:t});if(!s.success||!m(s.data))throw new u(s.error?.code??"MFA_CHALLENGE_FAILED",s.error?.message??"Invalid MFA code");return this.persist(s.data),s.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()=>{this.user!==null&&(await this.analytics.flush(),this.wipeLocalIdentity());let n=await this.http.post("/auth-service/email-otp/verify",{email:e,code:t});if(!n.success||!m(n.data))throw new u(n.error?.code??"EMAIL_OTP_INVALID",n.error?.message??"Wrong or expired code");return this.persist(n.data),n.data.user})}async requestPasswordReset(e){let t=await this.http.post("/auth-service/password/forgot",{email:e});if(!t.success)throw new u(t.error?.code??"PASSWORD_RESET_REQUEST_FAILED",t.error?.message??"Failed to send reset email")}async resetPassword(e,t){let 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()=>{this.user!==null&&(await this.analytics.flush(),this.wipeLocalIdentity());let s=await this.http.post("/auth-service/magic-link/verify",{token:e});if(!s.success||!m(s.data))throw new u(s.error?.code??"MAGIC_LINK_INVALID",s.error?.message??"Invalid or expired magic link");return this.persist(s.data),s.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=De(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=Ue(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");this.user!==null&&(await this.analytics.flush(),this.wipeLocalIdentity());let s=await this.http.post("/auth-service/passkeys/authenticate/start",{userId:e??null});if(!s.success||!s.data)throw new u(s.error?.code??"PASSKEY_START_FAILED",s.error?.message??"Passkey start failed");let n=xe(s.data),i;try{let c=await navigator.credentials.get({publicKey:n});if(!c)throw new Error("get returned null");i=c}catch(c){throw new u("PASSKEY_USER_CANCELLED",c instanceof Error?c.message:"User cancelled passkey signin")}let o=Ne(i),a=await this.http.post("/auth-service/passkeys/authenticate/finish",{response:o,userId:e??null});if(!a.success||!m(a.data))throw new u(a.error?.code??"PASSKEY_AUTH_FAILED",a.error?.message??"Passkey signin failed");return this.persist(a.data),a.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=await this.http.post("/auth-service/sso/oidc/start",{returnTo:e});if(!t.success||!t.data)throw new u(t.error?.code??"SSO_START_FAILED",t.error?.message??"SSO start failed");return t.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=await this.http.post("/auth-service/sso/saml/start",{returnTo:e});if(!t.success||!t.data)throw new u(t.error?.code??"SAML_START_FAILED",t.error?.message??"SAML start failed");return t.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;window.history.replaceState(null,"",window.location.pathname+window.location.search),this.user!==null&&(await this.analytics.flush(),this.wipeLocalIdentity());let i=await this.http.post("/auth-service/token/refresh",{refreshToken:n});if(!i.success||!i.data)throw new u(i.error?.code??"SSO_EXCHANGE_FAILED",i.error?.message??"Could not exchange SSO token");let o=i.data;this.accessToken=o.accessToken,this.accessExpiresAt=Date.now()+o.expiresIn*1e3,this.storage.set(b,o.accessToken),this.storage.set(T,String(this.accessExpiresAt)),this.storage.set(g,o.refreshToken);let a=Oe(o.accessToken),c={id:a?.sub??"",email:a?.email??null,emailVerified:a?.email_verified??!0,name:a?.name??null,isAnonymous:!1};return this.user=c,this.storage.set(U,JSON.stringify(c)),this.analytics.identify(c.id),this.flags.setContext({userId:c.id}),c})}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(b),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 Le(i.error?.code)&&this.wipeLocalIdentity(),null;if(!i.data||typeof i.data!="object"||typeof i.data.accessToken!="string"||typeof i.data.refreshToken!="string"||typeof i.data.expiresIn!="number")return null;let o=i.data;return this.accessToken=o.accessToken,this.accessExpiresAt=Date.now()+o.expiresIn*1e3,this.storage.set(b,o.accessToken),this.storage.set(T,String(this.accessExpiresAt)),this.storage.set(g,o.refreshToken),o.accessToken}catch{return 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}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 d=Math.max(a,5*6e4)*(1-e)+(Math.random()*s*2-s);a<=d&&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(U,JSON.stringify(e.user)),this.storage.set(b,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()}wipeLocalIdentity(){this.user=null,this.accessToken=null,this.accessExpiresAt=0,this.storage.remove(U),this.storage.remove(b),this.storage.remove(T),this.storage.remove(g),this.analytics.reset(),this.analytics.dropQueue(),this.flags.reset(),this.chatbot.newSession(),this.stopProactiveRefreshCron()}};function Le(r){return r?r==="INVALID_REFRESH_TOKEN"||r==="UNAUTHORIZED"||r==="HTTP_401"||r==="RATE_LIMIT_EXCEEDED"||r==="RATE_LIMIT":!1}function O(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 k(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 De(r){return{challenge:O(r.challenge),rp:r.rp,user:{id:O(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:O(e.id),type:"public-key",transports:e.transports})),authenticatorSelection:r.authenticatorSelection}}function xe(r){return{challenge:O(r.challenge),timeout:r.timeout,rpId:r.rpId,allowCredentials:r.allowCredentials?.map(e=>({id:O(e.id),type:"public-key",transports:e.transports})),userVerification:r.userVerification}}function Ue(r){let e=r.response;return{id:r.id,rawId:k(r.rawId),type:r.type,response:{clientDataJSON:k(e.clientDataJSON),attestationObject:k(e.attestationObject),transports:typeof e.getTransports=="function"?e.getTransports():void 0},clientExtensionResults:r.getClientExtensionResults()}}function Oe(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 Ne(r){let e=r.response;return{id:r.id,rawId:k(r.rawId),type:r.type,response:{clientDataJSON:k(e.clientDataJSON),authenticatorData:k(e.authenticatorData),signature:k(e.signature),userHandle:e.userHandle?k(e.userHandle):null},clientExtensionResults:r.getClientExtensionResults()}}function Me(r){return r===!1?{pageViews:!1,sessionStart:!1,appOpen:!1,appBackground:!1}:r===!0||r===void 0?{pageViews:!0,sessionStart:!0,appOpen:!0,appBackground:!0}:{pageViews:r.pageViews??!0,sessionStart:r.sessionStart??!0,appOpen:r.appOpen??!0,appBackground:r.appBackground??!0}}function fe(r,e){if(typeof window>"u")return()=>{};let t=Me(e.autoTrack),s=e.sessionIdleMs??18e5,n=[],i="sendora_session_id",o="sendora_session_last_activity";function a(){return pe(i)??""}function c(l){ge(i,l)}function d(){let l=pe(o);return l&&Number(l)||0}function A(l){ge(o,String(l))}function f(){return`sess_${Date.now().toString(36)}_${Math.random().toString(36).slice(2,10)}`}function y(l){let I=Date.now(),R=d();if(!a()||R&&I-R>s){let $=f();c($),l&&t.sessionStart&&r.track("session.start",{sessionId:$})}A(I)}if(y(t.sessionStart),t.appOpen&&r.track("app.opened",{sessionId:a()||void 0,url:window.location.origin+window.location.pathname,referrer:document.referrer||void 0}),t.pageViews){r.page();let l=()=>{y(!0),r.page()},I=()=>l();window.addEventListener("popstate",I),n.push(()=>window.removeEventListener("popstate",I));let R=window;if(!R.__sendoraHistoryPatched){R.__sendoraHistoryPatched=!0;let $=history.pushState,me=history.replaceState;history.pushState=function(...Q){let X=$.apply(this,Q);return window.dispatchEvent(new Event("sendora:locationchange")),X},history.replaceState=function(...Q){let X=me.apply(this,Q);return window.dispatchEvent(new Event("sendora:locationchange")),X}}let Y=()=>l();window.addEventListener("sendora:locationchange",Y),n.push(()=>window.removeEventListener("sendora:locationchange",Y))}if(t.appBackground){let l=()=>{y(!0),document.visibilityState==="hidden"?r.track("app.backgrounded",{sessionId:a()||void 0}):document.visibilityState==="visible"&&r.track("app.foregrounded",{sessionId:a()||void 0})};document.addEventListener("visibilitychange",l),n.push(()=>document.removeEventListener("visibilitychange",l))}let w=0,v=()=>{let l=Date.now();l-w<5e3||(w=l,y(!0))};return window.addEventListener("click",v,{passive:!0}),window.addEventListener("keydown",v,{passive:!0}),window.addEventListener("scroll",v,{passive:!0}),n.push(()=>{window.removeEventListener("click",v),window.removeEventListener("keydown",v),window.removeEventListener("scroll",v)}),()=>{for(let l of n)try{l()}catch{}}}function pe(r){try{return window.sessionStorage?.getItem(r)??null}catch{return null}}function ge(r,e){try{window.sessionStorage?.setItem(r,e)}catch{}}var H=class r{constructor(e,t){this.orgId=null;this.http=e,this.orgId=t??null}setOrgId(e){this.orgId=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.");if(!this.orgId)throw new Error("[sendoracloud] WebPush.subscribe needs orgId \u2014 pass via SendoraCloud.init({ orgId }) or call webPush.setOrgId().");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(`/orgs/${this.orgId}/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:He(i)}),c=a.toJSON(),d=c.endpoint,A=c.keys?.p256dh??"",f=c.keys?.auth??"",y=await Fe(d),w=await this.http.post("/push/tokens",{orgId:this.orgId,platform:"web",token:y,webEndpoint:d,webP256dh:A,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 He(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 Fe(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 F=class{constructor(e,t){this.http=e,this.orgId=t}setOrgId(e){this.orgId=e}base(){if(!this.orgId)throw new Error("Audiences API requires orgId \u2014 pass it via SendoraCloud.init({ orgId }) or call audiences.setOrgId() before use.");return`/orgs/${this.orgId}/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 ee(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 te=/^[a-z0-9-]{3,20}$/;function Ke(r){try{let t=new URL(r).pathname.split("/").filter(Boolean);if(t.length===0)return null;let s=t[t.length-1];return te.test(s)?s:null}catch{return null}}var $e=5*6e4,Ve=50,K=class{constructor(e){this.http=e;this.prewarmCache=new Map}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>$e&&this.prewarmCache.delete(t);for(;this.prewarmCache.size>Ve;){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))return;let n=this.doCreate(e).catch(i=>{throw this.prewarmCache.delete(s),i});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 ee(s.error?.code,s.error?.message??"links.create failed");return s.data}async revoke(e){if(!te.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 ee(t.error?.code,t.error?.message??"links.revoke failed");return{shortcode:t.data.shortcode,revoked:!0}}async getStats(e){if(!te.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 ee(t.error?.code,t.error?.message??"links.getStats failed");return t.data}};var B=class{constructor(e){this.http=e}async reportInstall(e={}){let t=await this.http.post("/attribution/install",e);if(!t.success||!t.data)throw new Error(t.error?.message??"attribution.reportInstall failed");return t.data}async checkDeferred(e={}){let t=await this.http.post("/attribution/deferred",e);if(!t.success)throw new Error(t.error?.message??"attribution.checkDeferred failed");return t.data??null}};var z=class{constructor(e){this.http=e}async createTicket(e){let t=await this.http.post("/support/tickets",e);if(!t.success||!t.data)throw new Error(t.error?.message??"support.createTicket failed");return t.data}async submitCsat(e){let t=await this.http.post("/support/csat",e);if(!t.success||!t.data)throw new Error(t.error?.message??"support.submitCsat failed");return t.data}};var 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 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 qe="2.19.0",G=class G{constructor(e){this.autoTrackCleanup=null;this.http=new V(e),this.storage=new W,this.consent=new x(e.defaultConsent===!0,this.http),this.analytics=new C(e,this.http,this.storage,this.consent),this.flags=new P(e,this.http,this.storage),this.surveys=new _(e,this.http,this.storage),this.messages=new L(e,this.http,this.storage),this.chatbot=new D(e,this.http,this.storage),this.auth=new M(e,this.http,this.storage,this.analytics,this.flags,this.chatbot),this.webPush=new H(this.http,e.orgId),this.audiences=new F(this.http,e.orgId),this.links=new K(this.http),this.attribution=new B(this.http),this.support=new z(this.http),this.kb=new j(this.http),e.autoTrack!==!1&&(this.autoTrackCleanup=fe(this.analytics,e))}static init(e){return ce(e.apiKey),new G(e)}static sanitizeHtml(e){return Z(e)}static safeUrl(e){return S(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()}};G.version=qe;var se=G,Dt=se;export{C as Analytics,F as Audiences,M as Auth,u as AuthError,D as Chatbot,x as Consent,N as EmailAlreadyTakenError,P as FeatureFlags,L as InAppMessaging,h as LinkError,K as Links,se as SendoraCloud,_ as Surveys,p as ValidationError,H as WebPush,Dt as default,Ke as extractShortcodeFromUrl,S as safeUrl,Z as sanitizeHtml};
1
+ var se=/^[a-zA-Z][a-zA-Z0-9._:-]{0,127}$/;var ye=new Set(["__proto__","constructor","prototype"]),p=class extends Error{constructor(e){super(`[SendoraCloud] ${e}`),this.name="SendoraValidationError"}};function ne(s){if(typeof s!="string"||!se.test(s))throw new p(`Invalid event name: must match ${se}, got "${String(s)}".`)}function ie(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=oe(s);if(e>5)throw new p(`Properties nested too deeply (${e} levels, max 5).`);ae(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 oe(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=oe(s[r],e+1);n>t&&(t=n)}return t}function ae(s){if(!(s==null||typeof s!="object"))for(let e of Object.keys(s)){if(ye.has(e))throw new p(`Forbidden property key "${e}" (prototype pollution guard).`);ae(s[e])}}var we=/^pk_[A-Za-z0-9_-]{16,}$/;function ce(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(!we.test(s))throw new p("Invalid publishable key format. Expected pk_... (17+ chars).")}function ue(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 ve="https://api.sendoracloud.com",ke=1e4,V=class{constructor(e){let t=(e.apiUrl??ve).replace(/\/$/,"");ue(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(),ke):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",y=l?.error?.message??`HTTP ${c.status}`,w=l?.error?.details?.fieldErrors,v=w?` (${Object.entries(w).map(([d,I])=>`${d}: ${(I??[]).join(", ")}`).join("; ")})`:"";console.warn(`[SendoraCloud] ${e} ${t} \u2192 HTTP ${c.status} \u2014 ${f}: ${y}${v}`)}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 q="sendora_",W=class{constructor(){this.memoryStore=new Map;this.useLocalStorage=this.checkLocalStorage()}get(e){let t=q+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=q+e;if(this.useLocalStorage)try{localStorage.setItem(r,t);return}catch{}this.memoryStore.set(r,t)}remove(e){let t=q+e;if(this.useLocalStorage)try{localStorage.removeItem(t)}catch{}this.memoryStore.delete(t)}clearPrefix(e){let t=q+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 E(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 le="2.9.0",de=500,Ae=10,Ie=6e4,C=class{constructor(e,t,r,n){this.queue=[];this.timer=null;this.userId=null;this.identityToken=null;this.consecutiveFailures=0;this.nextFlushAfter=0;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=E("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){ne(e),t&&ie(t),this.queue.length>=de&&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=E("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>Ae)return;let n=Math.min(Ie,1e3*2**this.consecutiveFailures);this.nextFlushAfter=Date.now()+n;let i=Math.max(0,de-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:le}}:{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:le}}}};var _=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)J(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&&J(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)J(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 J(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 Se=32*1024,P=class{constructor(e,t,r){this.activeSurvey=null;this.http=t,this.storage=r}onDisplay(e){this.onSurveyDisplay=e}async showSurvey(e){if(typeof e!="string"||!/^[A-Za-z0-9_-]{6,64}$/.test(e))return null;let t=`survey_shown_${e}`;if(this.storage.get(t))return null;let r=await this.http.get(`/surveys/${encodeURIComponent(e)}/config`);return!r.success||!r.data||!be(r.data)?null:(this.activeSurvey=r.data,this.storage.set(t,"1"),this.onSurveyDisplay&&this.onSurveyDisplay(r.data),r.data)}async submitResponse(e,t,r=!0){let n=0;try{n=JSON.stringify(t).length}catch{return!1}if(n>Se)return!1;let i=await this.http.post("/surveys/responses",{surveyId:e,answers:t,completed:r});return i.success&&(this.activeSurvey=null),i.success}dismiss(){this.activeSurvey=null}getActiveSurvey(){return this.activeSurvey}};function be(s){if(s==null||typeof s!="object")return!1;let e=s;return typeof e.id!="string"||typeof e.name!="string"||!Array.isArray(e.questions)?!1:e.questions.every(Te)}function Te(s){if(s==null||typeof s!="object")return!1;let e=s;return typeof e.id=="string"&&typeof e.text=="string"}function S(s){if(!(!s||typeof s!="string"))try{let e=typeof window<"u"?window.location.href:"https://local/",t=new URL(s,e);return t.protocol==="http:"||t.protocol==="https:"?t.href:void 0}catch{return}}var Ee=new Set(["b","i","u","em","strong","a","p","br","span","ul","ol","li","h1","h2","h3","h4"]),Re={a:new Set(["href","target","rel"])};function Z(s){if(typeof DOMParser>"u")return"";let t=new DOMParser().parseFromString(`<div>${s}</div>`,"text/html").body.firstElementChild;return t?(he(t),t.innerHTML):""}function he(s){let e=Array.from(s.children);for(let t of e){let r=t.tagName.toLowerCase();if(!Ee.has(r)){let i=document.createTextNode(t.textContent??"");t.replaceWith(i);continue}let n=Re[r]??new Set;for(let i of Array.from(t.attributes)){if(!n.has(i.name.toLowerCase())){t.removeAttribute(i.name);continue}if(i.name.toLowerCase()==="href"){let o=S(i.value);o?t.setAttribute("href",o):t.removeAttribute(i.name)}}r==="a"&&(t.setAttribute("rel","noopener noreferrer"),t.setAttribute("target","_blank")),he(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(Ce);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 Ce(s){return{...s,content:{...s.content,ctaUrl:S(s.content.ctaUrl),imageUrl:S(s.content.imageUrl)}}}var _e=4e3,D=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??E("chat_"),n||this.storage.set("chat_session_id",this.sessionId)}init(e){if(typeof e!="string"||!/^[A-Za-z0-9_-]{6,64}$/.test(e))throw new Error("[SendoraCloud] Invalid chatbotId.");this.chatbotId=e}onNewMessage(e){this.onMessage=e}async sendMessage(e,t){if(!this.chatbotId||typeof e!="string"||e.length===0||e.length>_e)return null;let r={role:"user",content:e,timestamp:new Date().toISOString()};this.messages.push(r);let n=await this.http.post("/chatbot/message",{chatbotId:this.chatbotId,sessionId:this.sessionId,message:e,userId:t});if(n.success&&n.data&&typeof n.data.reply=="string"){let i={role:"assistant",content:n.data.reply,timestamp:new Date().toISOString()};return this.messages.push(i),this.onMessage&&this.onMessage(i),i}return null}getMessages(){return[...this.messages]}newSession(){this.messages=[],this.sessionId=E("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 b="auth_access_token",T="auth_access_expires",g="auth_refresh_token",U="auth_user",Pe=3e4,Le=/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;function m(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 N=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}},M=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.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),c=this.storage.get(b),l=this.storage.get(g),A=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=A?Number(A):0):(this.accessToken=null,this.accessExpiresAt=0),this.analytics.identify(this.user.id)}catch{this.storage.remove(U),this.storage.remove(b),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||!Le.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-Pe?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||!m(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&&m(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 N(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||!m(i.data)){let o=i.error?.code??"";throw o==="CONFLICT"?new N(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(!m(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||!m(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||!m(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||!m(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||!m(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 r=xe(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=Oe(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,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=Ue(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=Me(o),c=await this.http.post("/auth-service/passkeys/authenticate/finish",{response:a,userId:e??null,...r?{prevAnonRefreshToken:r}:{}});if(!c.success||!m(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(),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(b,a.accessToken),this.storage.set(T,String(this.accessExpiresAt)),this.storage.set(g,a.refreshToken);let c=Ne(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(U,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,r=async()=>{let n=this.storage.get(g);if(n&&n!==e){let i=this.storage.get(b),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 De(i.error?.code)&&this.wipeLocalIdentity(),null;if(!i.data||typeof i.data!="object"||typeof i.data.accessToken!="string"||typeof i.data.refreshToken!="string"||typeof i.data.expiresIn!="number")return null;let o=i.data;return this.accessToken=o.accessToken,this.accessExpiresAt=Date.now()+o.expiresIn*1e3,this.storage.set(b,o.accessToken),this.storage.set(T,String(this.accessExpiresAt)),this.storage.set(g,o.refreshToken),o.accessToken}catch{return 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}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(U,JSON.stringify(e.user)),this.storage.set(b,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(),e.retiredAnonUserId&&this.fireDeviceTakeover(e.retiredAnonUserId,e.user.id)}wipeLocalIdentity(){this.user=null,this.accessToken=null,this.accessExpiresAt=0,this.storage.remove(U),this.storage.remove(b),this.storage.remove(T),this.storage.remove(g),this.analytics.reset(),this.analytics.dropQueue(),this.flags.reset(),this.chatbot.newSession(),this.stopProactiveRefreshCron()}};function De(s){return s?s==="INVALID_REFRESH_TOKEN"||s==="UNAUTHORIZED"||s==="HTTP_401"||s==="RATE_LIMIT_EXCEEDED"||s==="RATE_LIMIT":!1}function O(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 k(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 xe(s){return{challenge:O(s.challenge),rp:s.rp,user:{id:O(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:O(e.id),type:"public-key",transports:e.transports})),authenticatorSelection:s.authenticatorSelection}}function Ue(s){return{challenge:O(s.challenge),timeout:s.timeout,rpId:s.rpId,allowCredentials:s.allowCredentials?.map(e=>({id:O(e.id),type:"public-key",transports:e.transports})),userVerification:s.userVerification}}function Oe(s){let e=s.response;return{id:s.id,rawId:k(s.rawId),type:s.type,response:{clientDataJSON:k(e.clientDataJSON),attestationObject:k(e.attestationObject),transports:typeof e.getTransports=="function"?e.getTransports():void 0},clientExtensionResults:s.getClientExtensionResults()}}function Ne(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 Me(s){let e=s.response;return{id:s.id,rawId:k(s.rawId),type:s.type,response:{clientDataJSON:k(e.clientDataJSON),authenticatorData:k(e.authenticatorData),signature:k(e.signature),userHandle:e.userHandle?k(e.userHandle):null},clientExtensionResults:s.getClientExtensionResults()}}function He(s){return s===!1?{pageViews:!1,sessionStart:!1,appOpen:!1,appBackground:!1}:s===!0||s===void 0?{pageViews:!0,sessionStart:!0,appOpen:!0,appBackground:!0}:{pageViews:s.pageViews??!0,sessionStart:s.sessionStart??!0,appOpen:s.appOpen??!0,appBackground:s.appBackground??!0}}function fe(s,e){if(typeof window>"u")return()=>{};let t=He(e.autoTrack),r=e.sessionIdleMs??18e5,n=[],i="sendora_session_id",o="sendora_session_last_activity";function a(){return pe(i)??""}function c(d){ge(i,d)}function l(){let d=pe(o);return d&&Number(d)||0}function A(d){ge(o,String(d))}function f(){return`sess_${Date.now().toString(36)}_${Math.random().toString(36).slice(2,10)}`}function y(d){let I=Date.now(),R=l();if(!a()||R&&I-R>r){let $=f();c($),d&&t.sessionStart&&s.track("session.start",{sessionId:$})}A(I)}if(y(t.sessionStart),t.appOpen&&s.track("app.opened",{sessionId:a()||void 0,url:window.location.origin+window.location.pathname,referrer:document.referrer||void 0}),t.pageViews){s.page();let d=()=>{y(!0),s.page()},I=()=>d();window.addEventListener("popstate",I),n.push(()=>window.removeEventListener("popstate",I));let R=window;if(!R.__sendoraHistoryPatched){R.__sendoraHistoryPatched=!0;let $=history.pushState,me=history.replaceState;history.pushState=function(...Q){let X=$.apply(this,Q);return window.dispatchEvent(new Event("sendora:locationchange")),X},history.replaceState=function(...Q){let X=me.apply(this,Q);return window.dispatchEvent(new Event("sendora:locationchange")),X}}let Y=()=>d();window.addEventListener("sendora:locationchange",Y),n.push(()=>window.removeEventListener("sendora:locationchange",Y))}if(t.appBackground){let d=()=>{y(!0),document.visibilityState==="hidden"?s.track("app.backgrounded",{sessionId:a()||void 0}):document.visibilityState==="visible"&&s.track("app.foregrounded",{sessionId:a()||void 0})};document.addEventListener("visibilitychange",d),n.push(()=>document.removeEventListener("visibilitychange",d))}let w=0,v=()=>{let d=Date.now();d-w<5e3||(w=d,y(!0))};return window.addEventListener("click",v,{passive:!0}),window.addEventListener("keydown",v,{passive:!0}),window.addEventListener("scroll",v,{passive:!0}),n.push(()=>{window.removeEventListener("click",v),window.removeEventListener("keydown",v),window.removeEventListener("scroll",v)}),()=>{for(let d of n)try{d()}catch{}}}function pe(s){try{return window.sessionStorage?.getItem(s)??null}catch{return null}}function ge(s,e){try{window.sessionStorage?.setItem(s,e)}catch{}}var H=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:Fe(i)}),c=a.toJSON(),l=c.endpoint,A=c.keys?.p256dh??"",f=c.keys?.auth??"",y=await Ke(l),w=await this.http.post("/push/tokens",{platform:"web",token:y,webEndpoint:l,webP256dh:A,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 Fe(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 Ke(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 F=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 r=await this.http.patch(`${this.base()}/${e}`,t);return r.success?r.data??null:null}async delete(e){return(await this.http.delete(`${this.base()}/${e}`)).success}};var h=class extends Error{constructor(e,t,r){super(t),this.name="LinkError",this.code=e,this.statusCode=r}};function ee(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 te=/^[a-z0-9-]{3,20}$/;function $e(s){try{let t=new URL(s).pathname.split("/").filter(Boolean);if(t.length===0)return null;let r=t[t.length-1];return te.test(r)?r:null}catch{return null}}var Ve=5*6e4,qe=50,K=class{constructor(e){this.http=e;this.prewarmCache=new Map}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>Ve&&this.prewarmCache.delete(t);for(;this.prewarmCache.size>qe;){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))return;let n=this.doCreate(e).catch(i=>{throw this.prewarmCache.delete(r),i});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 ee(r.error?.code,r.error?.message??"links.create failed");return r.data}async revoke(e){if(!te.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 ee(t.error?.code,t.error?.message??"links.revoke failed");return{shortcode:t.data.shortcode,revoked:!0}}async getStats(e){if(!te.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 ee(t.error?.code,t.error?.message??"links.getStats failed");return t.data}};var B=class{constructor(e){this.http=e}async reportInstall(e={}){let t=await this.http.post("/attribution/install",e);if(!t.success||!t.data)throw new Error(t.error?.message??"attribution.reportInstall failed");return t.data}async checkDeferred(e={}){let t=await this.http.post("/attribution/deferred",e);if(!t.success)throw new Error(t.error?.message??"attribution.checkDeferred failed");return t.data??null}};var z=class{constructor(e){this.http=e}async createTicket(e){let t=await this.http.post("/support/tickets",e);if(!t.success||!t.data)throw new Error(t.error?.message??"support.createTicket failed");return t.data}async submitCsat(e){let t=await this.http.post("/support/csat",e);if(!t.success||!t.data)throw new Error(t.error?.message??"support.submitCsat failed");return t.data}};var 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 We="3.0.1",G=class G{constructor(e){this.autoTrackCleanup=null;this.http=new V(e),this.storage=new W,this.consent=new x(e.defaultConsent===!0,this.http),this.analytics=new C(e,this.http,this.storage,this.consent),this.flags=new _(e,this.http,this.storage),this.surveys=new P(e,this.http,this.storage),this.messages=new L(e,this.http,this.storage),this.chatbot=new D(e,this.http,this.storage),this.auth=new M(e,this.http,this.storage,this.analytics,this.flags,this.chatbot),this.webPush=new H(this.http),this.audiences=new F(this.http),this.links=new K(this.http),this.attribution=new B(this.http),this.support=new z(this.http),this.kb=new j(this.http),e.autoTrack!==!1&&(this.autoTrackCleanup=fe(this.analytics,e))}static init(e){return ce(e.apiKey),new G(e)}static sanitizeHtml(e){return Z(e)}static safeUrl(e){return S(e)}track(e,t){this.analytics.track(e,t)}page(e){this.analytics.page(e)}identify(e,t,r){this.analytics.identify(e,t,r),this.flags.setContext({userId:e})}reset(){this.analytics.reset(),this.flags.reset(),this.chatbot.newSession()}async flush(){await this.analytics.flush()}destroy(){this.autoTrackCleanup&&(this.autoTrackCleanup(),this.autoTrackCleanup=null),this.analytics.destroy(),this.flags.destroy(),this.messages.destroy()}};G.version=We;var re=G,xt=re;export{C as Analytics,F as Audiences,M as Auth,u as AuthError,D as Chatbot,x as Consent,N as EmailAlreadyTakenError,_ as FeatureFlags,L as InAppMessaging,h as LinkError,K as Links,re as SendoraCloud,P as Surveys,p as ValidationError,H as WebPush,xt as default,$e as extractShortcodeFromUrl,S as safeUrl,Z as sanitizeHtml};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sendoracloud/sdk-web",
3
- "version": "2.20.0",
3
+ "version": "3.0.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",