@sendoracloud/sdk-web 2.14.0 → 2.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -1 +1 @@
1
- "use strict";var J=Object.defineProperty;var ye=Object.getOwnPropertyDescriptor;var we=Object.getOwnPropertyNames;var ve=Object.prototype.hasOwnProperty;var ke=(r,e)=>{for(var t in e)J(r,t,{get:e[t],enumerable:!0})},Ae=(r,e,t,s)=>{if(e&&typeof e=="object"||typeof e=="function")for(let n of we(e))!ve.call(r,n)&&n!==t&&J(r,n,{get:()=>e[n],enumerable:!(s=ye(e,n))||s.enumerable});return r};var Ie=r=>Ae(J({},"__esModule",{value:!0}),r);var Qe={};ke(Qe,{Analytics:()=>E,Audiences:()=>N,Auth:()=>x,AuthError:()=>u,Chatbot:()=>P,Consent:()=>C,EmailAlreadyTakenError:()=>U,FeatureFlags:()=>T,InAppMessaging:()=>_,LinkError:()=>d,Links:()=>M,SendoraCloud:()=>j,Surveys:()=>R,ValidationError:()=>p,WebPush:()=>O,default:()=>Ye,extractShortcodeFromUrl:()=>fe,safeUrl:()=>k,sanitizeHtml:()=>z});module.exports=Ie(Qe);var se=/^[a-zA-Z][a-zA-Z0-9._:-]{0,127}$/;var Se=new Set(["__proto__","constructor","prototype"]),p=class extends Error{constructor(e){super(`[SendoraCloud] ${e}`),this.name="SendoraValidationError"}};function re(r){if(typeof r!="string"||!se.test(r))throw new p(`Invalid event name: must match ${se}, got "${String(r)}".`)}function ne(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=ie(r);if(e>5)throw new p(`Properties nested too deeply (${e} levels, max 5).`);oe(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 ie(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=ie(r[s],e+1);n>t&&(t=n)}return t}function oe(r){if(!(r==null||typeof r!="object"))for(let e of Object.keys(r)){if(Se.has(e))throw new p(`Forbidden property key "${e}" (prototype pollution guard).`);oe(r[e])}}var be=/^pk_[A-Za-z0-9_-]{16,}$/;function ae(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(!be.test(r))throw new p("Invalid publishable key format. Expected pk_... (17+ chars).")}function ce(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 Ee="https://api.sendoracloud.com",Te=1e4,V=class{constructor(e){let t=(e.apiUrl??Ee).replace(/\/$/,"");ce(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(),Te):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 h;try{h=await c.json()}catch{h={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 g=h?.error?.code??"HTTP_ERROR",y=h?.error?.message??`HTTP ${c.status}`,w=h?.error?.details?.fieldErrors,v=w?` (${Object.entries(w).map(([l,S])=>`${l}: ${(S??[]).join(", ")}`).join("; ")})`:"";console.warn(`[SendoraCloud] ${e} ${t} \u2192 HTTP ${c.status} \u2014 ${g}: ${y}${v}`)}return h}catch(c){a&&clearTimeout(a);let h=c instanceof Error&&c.name==="AbortError"?"timeout":"error";return console.warn(`[SendoraCloud] ${e} ${t} network ${h}`),{success:!1,error:{code:"NETWORK_ERROR",message:h}}}}};var W="sendora_",B=class{constructor(){this.memoryStore=new Map;this.useLocalStorage=this.checkLocalStorage()}get(e){let t=W+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=W+e;if(this.useLocalStorage)try{localStorage.setItem(s,t);return}catch{}this.memoryStore.set(s,t)}remove(e){let t=W+e;if(this.useLocalStorage)try{localStorage.removeItem(t)}catch{}this.memoryStore.delete(t)}clearPrefix(e){let t=W+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 b(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 ue="2.9.0",le=500,Re=10,_e=6e4,E=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=b("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){re(e),t&&ne(t),this.queue.length>=le&&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=b("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>Re)return;let n=Math.min(_e,1e3*2**this.consecutiveFailures);this.nextFlushAfter=Date.now()+n;let i=Math.max(0,le-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:ue}}:{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:ue}}}};var T=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)Z(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&&Z(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)Z(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 Z(r){if(r==null||typeof r!="object")return!1;let e=r;return Object.prototype.hasOwnProperty.call(e,"__proto__")||Object.prototype.hasOwnProperty.call(e,"constructor")?!1:typeof e.key=="string"&&"value"in e}var Pe=32*1024,R=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||!Ce(s.data)?null:(this.activeSurvey=s.data,this.storage.set(t,"1"),this.onSurveyDisplay&&this.onSurveyDisplay(s.data),s.data)}async submitResponse(e,t,s=!0){let n=0;try{n=JSON.stringify(t).length}catch{return!1}if(n>Pe)return!1;let i=await this.http.post("/surveys/responses",{surveyId:e,answers:t,completed:s});return i.success&&(this.activeSurvey=null),i.success}dismiss(){this.activeSurvey=null}getActiveSurvey(){return this.activeSurvey}};function Ce(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(Le)}function Le(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 De=new Set(["b","i","u","em","strong","a","p","br","span","ul","ol","li","h1","h2","h3","h4"]),Ue={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?(de(t),t.innerHTML):""}function de(r){let e=Array.from(r.children);for(let t of e){let s=t.tagName.toLowerCase();if(!De.has(s)){let i=document.createTextNode(t.textContent??"");t.replaceWith(i);continue}let n=Ue[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")),de(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(xe);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 xe(r){return{...r,content:{...r.content,ctaUrl:k(r.content.ctaUrl),imageUrl:k(r.content.imageUrl)}}}var Oe=4e3,P=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??b("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>Oe)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=b("chat_"),this.storage.set("chat_session_id",this.sessionId)}getSessionId(){return this.sessionId}};var C=class{constructor(e){this.listeners=new Set;this.granted=e}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)}emit(){for(let e of this.listeners)try{e(this.granted)}catch{}}};var L="auth_access_token",D="auth_access_expires",f="auth_refresh_token",F="auth_user",Ne=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 U=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}},x=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.http=t,this.storage=s,this.analytics=n,this.flags=i,this.chatbot=o;let a=this.storage.get(F),c=this.storage.get(L),h=this.storage.get(f),I=this.storage.get(D);if(a&&(c||h))try{let g=JSON.parse(a);if(typeof g.id!="string"||g.id.length===0)throw new Error("invalid cache");this.user=g,c?(this.accessToken=c,this.accessExpiresAt=I?Number(I):0):(this.accessToken=null,this.accessExpiresAt=0),this.analytics.identify(this.user.id)}catch{this.storage.remove(F),this.storage.remove(L),this.storage.remove(D),this.storage.remove(f)}}getCurrentUser(){return this.user}async getAccessToken(){return this.accessToken?this.accessExpiresAt&&Date.now()<this.accessExpiresAt-Ne?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(f);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 U(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 U(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=He(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=Ke(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=Fe(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=Ve(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)}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");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");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(L,o.accessToken),this.storage.set(D,String(this.accessExpiresAt)),this.storage.set(f,o.refreshToken);let a=$e(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(F,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(f);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(f);return e?(this.refreshInflight=(async()=>{try{let t=await this.http.post("/auth-service/token/refresh",{refreshToken:e});if(!t.success)return Me(t.error?.code)&&this.wipeLocalIdentity(),null;if(!t.data||typeof t.data!="object"||typeof t.data.accessToken!="string"||typeof t.data.refreshToken!="string"||typeof t.data.expiresIn!="number")return null;let s=t.data;return this.accessToken=s.accessToken,this.accessExpiresAt=Date.now()+s.expiresIn*1e3,this.storage.set(L,s.accessToken),this.storage.set(D,String(this.accessExpiresAt)),this.storage.set(f,s.refreshToken),s.accessToken}catch{return null}finally{this.refreshInflight=null}})(),this.refreshInflight):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(L,e.tokens.accessToken),this.storage.set(D,String(this.accessExpiresAt)),this.storage.set(f,e.tokens.refreshToken),this.analytics.identify(e.user.id),this.flags.setContext({userId:e.user.id})}wipeLocalIdentity(){this.user=null,this.accessToken=null,this.accessExpiresAt=0,this.storage.remove(F),this.storage.remove(L),this.storage.remove(D),this.storage.remove(f),this.analytics.reset(),this.analytics.dropQueue(),this.flags.reset(),this.chatbot.newSession()}};function Me(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 He(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 Fe(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 Ke(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 $e(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 Ve(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 We(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 ge(r,e){if(typeof window>"u")return()=>{};let t=We(e.autoTrack),s=e.sessionIdleMs??18e5,n=[],i="sendora_session_id",o="sendora_session_last_activity";function a(){return he(i)??""}function c(l){pe(i,l)}function h(){let l=he(o);return l&&Number(l)||0}function I(l){pe(o,String(l))}function g(){return`sess_${Date.now().toString(36)}_${Math.random().toString(36).slice(2,10)}`}function y(l){let S=Date.now(),H=h();if(!a()||H&&S-H>s){let $=g();c($),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,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 he(r){try{return window.sessionStorage?.getItem(r)??null}catch{return null}}function pe(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:Be(i)}),c=a.toJSON(),h=c.endpoint,I=c.keys?.p256dh??"",g=c.keys?.auth??"",y=await ze(h),w=await this.http.post("/push/tokens",{orgId:this.orgId,platform:"web",token:y,webEndpoint:h,webP256dh:I,webAuth:g,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}};function Be(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 ze(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 d=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 d("NOT_FOUND",e):t==="FORBIDDEN"||t==="UNAUTHORIZED"?new d("UNAUTHORIZED",e):t==="RATE_LIMITED"?new d("RATE_LIMITED",e):t==="ENTITLEMENT_ERROR"||/plan limit/i.test(e)?new d("PLAN_LIMIT",e):t==="VALIDATION_ERROR"?/iOS bundle|Android package/i.test(e)?new d("BUNDLE_MISMATCH",e):/2KB|10KB|linkData/i.test(e)?new d("DATA_TOO_LARGE",e):/fallbackUrl/i.test(e)&&/apps/i.test(e)?new d("FALLBACK_REQUIRED",e):new d("INVALID_INPUT",e):t==="PARSE_ERROR"||t==="HTTP_ERROR"||/HTTP \d{3}/.test(e)?new d("NETWORK",e):new d("UNKNOWN",e||`Unknown LinkError (code=${r??"none"})`)}var te=/^[a-z0-9-]{3,20}$/;function fe(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 qe=5*6e4,je=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>qe&&this.prewarmCache.delete(t);for(;this.prewarmCache.size>je;){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 d("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 d("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 d("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 q=class{constructor(e){this.http=e}async reportInstall(e={}){let t=await this.http.post("/attribution/install",e);if(!t.success||!t.data)throw new Error(t.error?.message??"attribution.reportInstall failed");return t.data}async checkDeferred(e={}){let t=await this.http.post("/attribution/deferred",e);if(!t.success)throw new Error(t.error?.message??"attribution.checkDeferred failed");return t.data??null}};var Ge="2.14.0",G=class G{constructor(e){this.autoTrackCleanup=null;this.http=new V(e),this.storage=new B,this.consent=new C(e.defaultConsent===!0),this.analytics=new E(e,this.http,this.storage,this.consent),this.flags=new T(e,this.http,this.storage),this.surveys=new R(e,this.http,this.storage),this.messages=new _(e,this.http,this.storage),this.chatbot=new P(e,this.http,this.storage),this.auth=new x(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 q(this.http),e.autoTrack!==!1&&(this.autoTrackCleanup=ge(this.analytics,e))}static init(e){return ae(e.apiKey),new G(e)}static sanitizeHtml(e){return z(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()}};G.version=Ge;var j=G,Ye=j;0&&(module.exports={Analytics,Audiences,Auth,AuthError,Chatbot,Consent,EmailAlreadyTakenError,FeatureFlags,InAppMessaging,LinkError,Links,SendoraCloud,Surveys,ValidationError,WebPush,extractShortcodeFromUrl,safeUrl,sanitizeHtml});
1
+ "use strict";var Z=Object.defineProperty;var we=Object.getOwnPropertyDescriptor;var ve=Object.getOwnPropertyNames;var ke=Object.prototype.hasOwnProperty;var Ae=(r,e)=>{for(var t in e)Z(r,t,{get:e[t],enumerable:!0})},Ie=(r,e,t,s)=>{if(e&&typeof e=="object"||typeof e=="function")for(let n of ve(e))!ke.call(r,n)&&n!==t&&Z(r,n,{get:()=>e[n],enumerable:!(s=we(e,n))||s.enumerable});return r};var Se=r=>Ie(Z({},"__esModule",{value:!0}),r);var Xe={};Ae(Xe,{Analytics:()=>T,Audiences:()=>N,Auth:()=>x,AuthError:()=>u,Chatbot:()=>P,Consent:()=>_,EmailAlreadyTakenError:()=>U,FeatureFlags:()=>E,InAppMessaging:()=>C,LinkError:()=>d,Links:()=>M,SendoraCloud:()=>G,Surveys:()=>R,ValidationError:()=>p,WebPush:()=>O,default:()=>Qe,extractShortcodeFromUrl:()=>me,safeUrl:()=>k,sanitizeHtml:()=>z});module.exports=Se(Xe);var re=/^[a-zA-Z][a-zA-Z0-9._:-]{0,127}$/;var be=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(be.has(e))throw new p(`Forbidden property key "${e}" (prototype pollution guard).`);ae(r[e])}}var Te=/^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(!Te.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 Ee="https://api.sendoracloud.com",Re=1e4,V=class{constructor(e){let t=(e.apiUrl??Ee).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(),Re):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 h;try{h=await c.json()}catch{h={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 g=h?.error?.code??"HTTP_ERROR",y=h?.error?.message??`HTTP ${c.status}`,w=h?.error?.details?.fieldErrors,v=w?` (${Object.entries(w).map(([l,S])=>`${l}: ${(S??[]).join(", ")}`).join("; ")})`:"";console.warn(`[SendoraCloud] ${e} ${t} \u2192 HTTP ${c.status} \u2014 ${g}: ${y}${v}`)}return h}catch(c){a&&clearTimeout(a);let h=c instanceof Error&&c.name==="AbortError"?"timeout":"error";return console.warn(`[SendoraCloud] ${e} ${t} network ${h}`),{success:!1,error:{code:"NETWORK_ERROR",message:h}}}}};var W="sendora_",B=class{constructor(){this.memoryStore=new Map;this.useLocalStorage=this.checkLocalStorage()}get(e){let t=W+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=W+e;if(this.useLocalStorage)try{localStorage.setItem(s,t);return}catch{}this.memoryStore.set(s,t)}remove(e){let t=W+e;if(this.useLocalStorage)try{localStorage.removeItem(t)}catch{}this.memoryStore.delete(t)}clearPrefix(e){let t=W+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 b(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,Ce=10,Pe=6e4,T=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=b("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=b("anon_"),this.storage.set("anonymous_id",this.anonymousId),this.storage.clearPrefix("iam_shown_"),this.storage.clearPrefix("survey_shown_")}dropQueue(){this.queue=[]}async flush(){if(this.queue.length===0||!this.consent.isGranted()||Date.now()<this.nextFlushAfter)return;let e=this.queue.splice(0,100),t=e.map(o=>({projectId:this.projectId,module:"custom",eventType:o.eventType,userId:o.userId,anonymousId:o.anonymousId,identityToken:o.identityToken,timestamp:o.timestamp??new Date().toISOString(),properties:o.properties??{},context:this.getContext(),consent:["analytics"]}));if((await this.http.post("/events/batch",{events:t})).success){this.consecutiveFailures=0;return}if(this.consecutiveFailures++,this.consecutiveFailures>Ce)return;let n=Math.min(Pe,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 E=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)ee(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&&ee(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)ee(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 ee(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 _e=32*1024,R=class{constructor(e,t,s){this.activeSurvey=null;this.http=t,this.storage=s}onDisplay(e){this.onSurveyDisplay=e}async showSurvey(e){if(typeof e!="string"||!/^[A-Za-z0-9_-]{6,64}$/.test(e))return null;let t=`survey_shown_${e}`;if(this.storage.get(t))return null;let s=await this.http.get(`/surveys/${encodeURIComponent(e)}/config`);return!s.success||!s.data||!Le(s.data)?null:(this.activeSurvey=s.data,this.storage.set(t,"1"),this.onSurveyDisplay&&this.onSurveyDisplay(s.data),s.data)}async submitResponse(e,t,s=!0){let n=0;try{n=JSON.stringify(t).length}catch{return!1}if(n>_e)return!1;let i=await this.http.post("/surveys/responses",{surveyId:e,answers:t,completed:s});return i.success&&(this.activeSurvey=null),i.success}dismiss(){this.activeSurvey=null}getActiveSurvey(){return this.activeSurvey}};function Le(r){if(r==null||typeof r!="object")return!1;let e=r;return typeof e.id!="string"||typeof e.name!="string"||!Array.isArray(e.questions)?!1:e.questions.every(De)}function De(r){if(r==null||typeof r!="object")return!1;let e=r;return typeof e.id=="string"&&typeof e.text=="string"}function 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"]),xe={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(!Ue.has(s)){let i=document.createTextNode(t.textContent??"");t.replaceWith(i);continue}let n=xe[s]??new Set;for(let i of Array.from(t.attributes)){if(!n.has(i.name.toLowerCase())){t.removeAttribute(i.name);continue}if(i.name.toLowerCase()==="href"){let o=k(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 C=class{constructor(e,t,s){this.messages=[];this.pollInterval=null;this.http=t,this.storage=s}onDisplay(e){this.onMessageDisplay=e}async checkMessages(){let e=await this.http.get("/in-app-messages/active");if(!e.success||!Array.isArray(e.data))return null;this.messages=e.data.map(Oe);for(let t of this.messages){let s=`iam_shown_${t.id}`;if(!this.storage.get(s))return this.storage.set(s,"1"),this.onMessageDisplay&&this.onMessageDisplay(t),t}return null}async recordAction(e,t){await this.http.post("/in-app-messages/impressions",{messageId:e,action:t})}startPolling(e=3e5){this.stopPolling(),this.checkMessages(),this.pollInterval=setInterval(()=>this.checkMessages(),e)}stopPolling(){this.pollInterval&&clearInterval(this.pollInterval),this.pollInterval=null}getMessages(){return this.messages}destroy(){this.stopPolling()}};function Oe(r){return{...r,content:{...r.content,ctaUrl:k(r.content.ctaUrl),imageUrl:k(r.content.imageUrl)}}}var Ne=4e3,P=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??b("chat_"),n||this.storage.set("chat_session_id",this.sessionId)}init(e){if(typeof e!="string"||!/^[A-Za-z0-9_-]{6,64}$/.test(e))throw new Error("[SendoraCloud] Invalid chatbotId.");this.chatbotId=e}onNewMessage(e){this.onMessage=e}async sendMessage(e,t){if(!this.chatbotId||typeof e!="string"||e.length===0||e.length>Ne)return null;let s={role:"user",content:e,timestamp:new Date().toISOString()};this.messages.push(s);let n=await this.http.post("/chatbot/message",{chatbotId:this.chatbotId,sessionId:this.sessionId,message:e,userId:t});if(n.success&&n.data&&typeof n.data.reply=="string"){let i={role:"assistant",content:n.data.reply,timestamp:new Date().toISOString()};return this.messages.push(i),this.onMessage&&this.onMessage(i),i}return null}getMessages(){return[...this.messages]}newSession(){this.messages=[],this.sessionId=b("chat_"),this.storage.set("chat_session_id",this.sessionId)}getSessionId(){return this.sessionId}};var _=class{constructor(e){this.listeners=new Set;this.granted=e}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)}emit(){for(let e of this.listeners)try{e(this.granted)}catch{}}};var L="auth_access_token",D="auth_access_expires",f="auth_refresh_token",F="auth_user",Me=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 U=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}},x=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.http=t,this.storage=s,this.analytics=n,this.flags=i,this.chatbot=o;let a=this.storage.get(F),c=this.storage.get(L),h=this.storage.get(f),I=this.storage.get(D);if(a&&(c||h))try{let g=JSON.parse(a);if(typeof g.id!="string"||g.id.length===0)throw new Error("invalid cache");this.user=g,c?(this.accessToken=c,this.accessExpiresAt=I?Number(I):0):(this.accessToken=null,this.accessExpiresAt=0),this.analytics.identify(this.user.id)}catch{this.storage.remove(F),this.storage.remove(L),this.storage.remove(D),this.storage.remove(f)}}getCurrentUser(){return this.user}async getAccessToken(){return this.accessToken?this.accessExpiresAt&&Date.now()<this.accessExpiresAt-Me?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(f);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 U(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 U(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=Fe(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=$e(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=Ke(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=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 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)}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");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");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(L,o.accessToken),this.storage.set(D,String(this.accessExpiresAt)),this.storage.set(f,o.refreshToken);let a=Ve(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(F,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(f);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(f);return e?(this.refreshInflight=(async()=>{try{let t=await this.http.post("/auth-service/token/refresh",{refreshToken:e});if(!t.success)return He(t.error?.code)&&this.wipeLocalIdentity(),null;if(!t.data||typeof t.data!="object"||typeof t.data.accessToken!="string"||typeof t.data.refreshToken!="string"||typeof t.data.expiresIn!="number")return null;let s=t.data;return this.accessToken=s.accessToken,this.accessExpiresAt=Date.now()+s.expiresIn*1e3,this.storage.set(L,s.accessToken),this.storage.set(D,String(this.accessExpiresAt)),this.storage.set(f,s.refreshToken),s.accessToken}catch{return null}finally{this.refreshInflight=null}})(),this.refreshInflight):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(L,e.tokens.accessToken),this.storage.set(D,String(this.accessExpiresAt)),this.storage.set(f,e.tokens.refreshToken),this.analytics.identify(e.user.id),this.flags.setContext({userId:e.user.id})}wipeLocalIdentity(){this.user=null,this.accessToken=null,this.accessExpiresAt=0,this.storage.remove(F),this.storage.remove(L),this.storage.remove(D),this.storage.remove(f),this.analytics.reset(),this.analytics.dropQueue(),this.flags.reset(),this.chatbot.newSession()}};function He(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 Fe(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 Ke(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 $e(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 Ve(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 fe(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 pe(i)??""}function c(l){ge(i,l)}function h(){let l=pe(o);return l&&Number(l)||0}function I(l){ge(o,String(l))}function g(){return`sess_${Date.now().toString(36)}_${Math.random().toString(36).slice(2,10)}`}function y(l){let S=Date.now(),H=h();if(!a()||H&&S-H>s){let $=g();c($),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,ye=history.replaceState;history.pushState=function(...X){let J=$.apply(this,X);return window.dispatchEvent(new Event("sendora:locationchange")),J},history.replaceState=function(...X){let J=ye.apply(this,X);return window.dispatchEvent(new Event("sendora:locationchange")),J}}let Q=()=>l();window.addEventListener("sendora:locationchange",Q),n.push(()=>window.removeEventListener("sendora:locationchange",Q))}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 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)}),c=a.toJSON(),h=c.endpoint,I=c.keys?.p256dh??"",g=c.keys?.auth??"",y=await qe(h),w=await this.http.post("/push/tokens",{orgId:this.orgId,platform:"web",token:y,webEndpoint:h,webP256dh:I,webAuth:g,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}};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 qe(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 d=class extends Error{constructor(e,t,s){super(t),this.name="LinkError",this.code=e,this.statusCode=s}};function te(r,e){let t=(r??"").toUpperCase();return t==="NOT_FOUND"?new d("NOT_FOUND",e):t==="FORBIDDEN"||t==="UNAUTHORIZED"?new d("UNAUTHORIZED",e):t==="RATE_LIMITED"?new d("RATE_LIMITED",e):t==="ENTITLEMENT_ERROR"||/plan limit/i.test(e)?new d("PLAN_LIMIT",e):t==="VALIDATION_ERROR"?/iOS bundle|Android package/i.test(e)?new d("BUNDLE_MISMATCH",e):/2KB|10KB|linkData/i.test(e)?new d("DATA_TOO_LARGE",e):/fallbackUrl/i.test(e)&&/apps/i.test(e)?new d("FALLBACK_REQUIRED",e):new d("INVALID_INPUT",e):t==="PARSE_ERROR"||t==="HTTP_ERROR"||/HTTP \d{3}/.test(e)?new d("NETWORK",e):new d("UNKNOWN",e||`Unknown LinkError (code=${r??"none"})`)}var se=/^[a-z0-9-]{3,20}$/;function me(r){try{let t=new URL(r).pathname.split("/").filter(Boolean);if(t.length===0)return null;let s=t[t.length-1];return se.test(s)?s:null}catch{return null}}var je=5*6e4,Ge=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>je&&this.prewarmCache.delete(t);for(;this.prewarmCache.size>Ge;){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 d("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 te(s.error?.code,s.error?.message??"links.create failed");return s.data}async revoke(e){if(!se.test(e))throw new d("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 te(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 d("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 te(t.error?.code,t.error?.message??"links.getStats failed");return t.data}};var q=class{constructor(e){this.http=e}async reportInstall(e={}){let t=await this.http.post("/attribution/install",e);if(!t.success||!t.data)throw new Error(t.error?.message??"attribution.reportInstall failed");return t.data}async checkDeferred(e={}){let t=await this.http.post("/attribution/deferred",e);if(!t.success)throw new Error(t.error?.message??"attribution.checkDeferred failed");return t.data??null}};var j=class{constructor(e){this.http=e}async createTicket(e){let t=await this.http.post("/support/tickets",e);if(!t.success||!t.data)throw new Error(t.error?.message??"support.createTicket failed");return t.data}async submitCsat(e){let t=await this.http.post("/support/csat",e);if(!t.success||!t.data)throw new Error(t.error?.message??"support.submitCsat failed");return t.data}};var Ye="2.15.0",Y=class Y{constructor(e){this.autoTrackCleanup=null;this.http=new V(e),this.storage=new B,this.consent=new _(e.defaultConsent===!0),this.analytics=new T(e,this.http,this.storage,this.consent),this.flags=new E(e,this.http,this.storage),this.surveys=new R(e,this.http,this.storage),this.messages=new C(e,this.http,this.storage),this.chatbot=new P(e,this.http,this.storage),this.auth=new x(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 q(this.http),this.support=new j(this.http),e.autoTrack!==!1&&(this.autoTrackCleanup=fe(this.analytics,e))}static init(e){return ce(e.apiKey),new Y(e)}static sanitizeHtml(e){return z(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()}};Y.version=Ye;var G=Y,Qe=G;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
@@ -906,6 +906,59 @@ declare class Attribution {
906
906
  checkDeferred(input?: AttributionDeferredInput): Promise<AttributionDeferredMatch | null>;
907
907
  }
908
908
 
909
+ /**
910
+ * In-product "Contact support" + post-resolution CSAT helper.
911
+ * Mirrors backend `/support/{tickets,csat}` SDK routes and the
912
+ * RN SDK's `SendoraCloud.support.*` namespace 1:1.
913
+ *
914
+ * Typical web usage:
915
+ * const t = await sendora.support.createTicket({
916
+ * subject: "Can't reset password",
917
+ * body: textarea.value,
918
+ * priority: "high",
919
+ * email: currentUser.email,
920
+ * userId: currentUser.id,
921
+ * });
922
+ * // Open the portal URL so the user can reply without auth:
923
+ * window.open(t.portalUrl, "_blank");
924
+ *
925
+ * After resolution emails ask for a rating:
926
+ * await sendora.support.submitCsat({ ticketId, rating: 5 });
927
+ */
928
+ interface CreateTicketInput {
929
+ subject: string;
930
+ body: string;
931
+ priority?: "low" | "normal" | "high" | "urgent";
932
+ category?: string;
933
+ email?: string;
934
+ userId?: string;
935
+ metadata?: Record<string, unknown>;
936
+ }
937
+ interface CreatedTicket {
938
+ id: string;
939
+ subject: string;
940
+ status: string;
941
+ priority: string;
942
+ portalToken: string;
943
+ portalUrl: string;
944
+ }
945
+ interface SubmitCsatInput {
946
+ ticketId: string;
947
+ rating: 1 | 2 | 3 | 4 | 5;
948
+ comment?: string;
949
+ }
950
+ interface CsatRating {
951
+ id: string;
952
+ rating: number;
953
+ comment: string | null;
954
+ }
955
+ declare class Support {
956
+ private http;
957
+ constructor(http: HttpClient);
958
+ createTicket(input: CreateTicketInput): Promise<CreatedTicket>;
959
+ submitCsat(input: SubmitCsatInput): Promise<CsatRating>;
960
+ }
961
+
909
962
  /**
910
963
  * Return the URL if it is http/https; otherwise undefined.
911
964
  * Rejects javascript:, data:, vbscript:, and other script-bearing schemes.
@@ -969,6 +1022,9 @@ declare class SendoraCloud {
969
1022
  * landing pages to surface campaign-matched onboarding. Mirrors
970
1023
  * RN SDK's `attribution.*` surface. Added 2.14.0. */
971
1024
  readonly attribution: Attribution;
1025
+ /** In-product "Contact support" widget + post-resolution CSAT.
1026
+ * Mirrors RN SDK's `support.*` surface. Added 2.15.0. */
1027
+ readonly support: Support;
972
1028
  private http;
973
1029
  private storage;
974
1030
  private autoTrackCleanup;
@@ -980,7 +1036,7 @@ declare class SendoraCloud {
980
1036
  /** Return the URL if it's http(s); undefined otherwise. */
981
1037
  static safeUrl(u?: string): string | undefined;
982
1038
  /** SDK version. */
983
- static readonly version = "2.14.0";
1039
+ static readonly version = "2.15.0";
984
1040
  track(eventType: string, properties?: Record<string, unknown>): void;
985
1041
  page(properties?: Record<string, unknown>): void;
986
1042
  identify(userId: string, traits?: UserTraits, opts?: IdentifyOptions): void;
package/dist/index.d.ts CHANGED
@@ -906,6 +906,59 @@ declare class Attribution {
906
906
  checkDeferred(input?: AttributionDeferredInput): Promise<AttributionDeferredMatch | null>;
907
907
  }
908
908
 
909
+ /**
910
+ * In-product "Contact support" + post-resolution CSAT helper.
911
+ * Mirrors backend `/support/{tickets,csat}` SDK routes and the
912
+ * RN SDK's `SendoraCloud.support.*` namespace 1:1.
913
+ *
914
+ * Typical web usage:
915
+ * const t = await sendora.support.createTicket({
916
+ * subject: "Can't reset password",
917
+ * body: textarea.value,
918
+ * priority: "high",
919
+ * email: currentUser.email,
920
+ * userId: currentUser.id,
921
+ * });
922
+ * // Open the portal URL so the user can reply without auth:
923
+ * window.open(t.portalUrl, "_blank");
924
+ *
925
+ * After resolution emails ask for a rating:
926
+ * await sendora.support.submitCsat({ ticketId, rating: 5 });
927
+ */
928
+ interface CreateTicketInput {
929
+ subject: string;
930
+ body: string;
931
+ priority?: "low" | "normal" | "high" | "urgent";
932
+ category?: string;
933
+ email?: string;
934
+ userId?: string;
935
+ metadata?: Record<string, unknown>;
936
+ }
937
+ interface CreatedTicket {
938
+ id: string;
939
+ subject: string;
940
+ status: string;
941
+ priority: string;
942
+ portalToken: string;
943
+ portalUrl: string;
944
+ }
945
+ interface SubmitCsatInput {
946
+ ticketId: string;
947
+ rating: 1 | 2 | 3 | 4 | 5;
948
+ comment?: string;
949
+ }
950
+ interface CsatRating {
951
+ id: string;
952
+ rating: number;
953
+ comment: string | null;
954
+ }
955
+ declare class Support {
956
+ private http;
957
+ constructor(http: HttpClient);
958
+ createTicket(input: CreateTicketInput): Promise<CreatedTicket>;
959
+ submitCsat(input: SubmitCsatInput): Promise<CsatRating>;
960
+ }
961
+
909
962
  /**
910
963
  * Return the URL if it is http/https; otherwise undefined.
911
964
  * Rejects javascript:, data:, vbscript:, and other script-bearing schemes.
@@ -969,6 +1022,9 @@ declare class SendoraCloud {
969
1022
  * landing pages to surface campaign-matched onboarding. Mirrors
970
1023
  * RN SDK's `attribution.*` surface. Added 2.14.0. */
971
1024
  readonly attribution: Attribution;
1025
+ /** In-product "Contact support" widget + post-resolution CSAT.
1026
+ * Mirrors RN SDK's `support.*` surface. Added 2.15.0. */
1027
+ readonly support: Support;
972
1028
  private http;
973
1029
  private storage;
974
1030
  private autoTrackCleanup;
@@ -980,7 +1036,7 @@ declare class SendoraCloud {
980
1036
  /** Return the URL if it's http(s); undefined otherwise. */
981
1037
  static safeUrl(u?: string): string | undefined;
982
1038
  /** SDK version. */
983
- static readonly version = "2.14.0";
1039
+ static readonly version = "2.15.0";
984
1040
  track(eventType: string, properties?: Record<string, unknown>): void;
985
1041
  page(properties?: Record<string, unknown>): void;
986
1042
  identify(userId: string, traits?: UserTraits, opts?: IdentifyOptions): void;
package/dist/index.js CHANGED
@@ -1 +1 @@
1
- var te=/^[a-zA-Z][a-zA-Z0-9._:-]{0,127}$/;var fe=new Set(["__proto__","constructor","prototype"]),p=class extends Error{constructor(e){super(`[SendoraCloud] ${e}`),this.name="SendoraValidationError"}};function se(r){if(typeof r!="string"||!te.test(r))throw new p(`Invalid event name: must match ${te}, got "${String(r)}".`)}function re(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=ne(r);if(e>5)throw new p(`Properties nested too deeply (${e} levels, max 5).`);ie(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 ne(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=ne(r[s],e+1);n>t&&(t=n)}return t}function ie(r){if(!(r==null||typeof r!="object"))for(let e of Object.keys(r)){if(fe.has(e))throw new p(`Forbidden property key "${e}" (prototype pollution guard).`);ie(r[e])}}var me=/^pk_[A-Za-z0-9_-]{16,}$/;function oe(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(!me.test(r))throw new p("Invalid publishable key format. Expected pk_... (17+ chars).")}function ae(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 ye="https://api.sendoracloud.com",we=1e4,V=class{constructor(e){let t=(e.apiUrl??ye).replace(/\/$/,"");ae(t),this.apiKey=e.apiKey,this.baseUrl=t,this.debug=!!e.debug}get(e,t){return this.request("GET",e,void 0,t)}post(e,t,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(),we):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 g=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 ${g}: ${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 W="sendora_",B=class{constructor(){this.memoryStore=new Map;this.useLocalStorage=this.checkLocalStorage()}get(e){let t=W+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=W+e;if(this.useLocalStorage)try{localStorage.setItem(s,t);return}catch{}this.memoryStore.set(s,t)}remove(e){let t=W+e;if(this.useLocalStorage)try{localStorage.removeItem(t)}catch{}this.memoryStore.delete(t)}clearPrefix(e){let t=W+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 b(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 ce="2.9.0",ue=500,ve=10,ke=6e4,_=class{constructor(e,t,s,n){this.queue=[];this.timer=null;this.userId=null;this.identityToken=null;this.consecutiveFailures=0;this.nextFlushAfter=0;this.http=t,this.storage=s,this.consent=n,this.projectId=e.projectId,this.flushInterval=e.flushInterval??5e3,this.maxQueueSize=e.maxQueueSize??20,this.persistUserId=!!e.persistUserId,this.stripQueryParams=e.stripQueryParams!==!1;let i=this.storage.get("anonymous_id");if(i?this.anonymousId=i:(this.anonymousId=b("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){se(e),t&&re(t),this.queue.length>=ue&&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=b("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>ve)return;let n=Math.min(ke,1e3*2**this.consecutiveFailures);this.nextFlushAfter=Date.now()+n;let i=Math.max(0,ue-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:ce}}:{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:ce}}}};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)Q(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&&Q(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)Q(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 Q(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 Ae=32*1024,C=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||!Ie(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>Ae)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 Ie(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(Se)}function Se(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 be=new Set(["b","i","u","em","strong","a","p","br","span","ul","ol","li","h1","h2","h3","h4"]),Ee={a:new Set(["href","target","rel"])};function X(r){if(typeof DOMParser>"u")return"";let t=new DOMParser().parseFromString(`<div>${r}</div>`,"text/html").body.firstElementChild;return t?(le(t),t.innerHTML):""}function le(r){let e=Array.from(r.children);for(let t of e){let s=t.tagName.toLowerCase();if(!be.has(s)){let i=document.createTextNode(t.textContent??"");t.replaceWith(i);continue}let n=Ee[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")),le(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(Te);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 Te(r){return{...r,content:{...r.content,ctaUrl:S(r.content.ctaUrl),imageUrl:S(r.content.imageUrl)}}}var Re=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??b("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>Re)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=b("chat_"),this.storage.set("chat_session_id",this.sessionId)}getSessionId(){return this.sessionId}};var U=class{constructor(e){this.listeners=new Set;this.granted=e}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)}emit(){for(let e of this.listeners)try{e(this.granted)}catch{}}};var E="auth_access_token",T="auth_access_expires",f="auth_refresh_token",x="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.http=t,this.storage=s,this.analytics=n,this.flags=i,this.chatbot=o;let a=this.storage.get(x),c=this.storage.get(E),d=this.storage.get(f),A=this.storage.get(T);if(a&&(c||d))try{let g=JSON.parse(a);if(typeof g.id!="string"||g.id.length===0)throw new Error("invalid cache");this.user=g,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(x),this.storage.remove(E),this.storage.remove(T),this.storage.remove(f)}}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(f);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=Ce(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=De(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=Le(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=xe(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)}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");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");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(E,o.accessToken),this.storage.set(T,String(this.accessExpiresAt)),this.storage.set(f,o.refreshToken);let a=Ue(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(x,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(f);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(f);return e?(this.refreshInflight=(async()=>{try{let t=await this.http.post("/auth-service/token/refresh",{refreshToken:e});if(!t.success)return Pe(t.error?.code)&&this.wipeLocalIdentity(),null;if(!t.data||typeof t.data!="object"||typeof t.data.accessToken!="string"||typeof t.data.refreshToken!="string"||typeof t.data.expiresIn!="number")return null;let s=t.data;return this.accessToken=s.accessToken,this.accessExpiresAt=Date.now()+s.expiresIn*1e3,this.storage.set(E,s.accessToken),this.storage.set(T,String(this.accessExpiresAt)),this.storage.set(f,s.refreshToken),s.accessToken}catch{return null}finally{this.refreshInflight=null}})(),this.refreshInflight):null}persist(e){this.user=e.user,this.accessToken=e.tokens.accessToken,this.accessExpiresAt=Date.now()+e.tokens.expiresIn*1e3,this.storage.set(x,JSON.stringify(e.user)),this.storage.set(E,e.tokens.accessToken),this.storage.set(T,String(this.accessExpiresAt)),this.storage.set(f,e.tokens.refreshToken),this.analytics.identify(e.user.id),this.flags.setContext({userId:e.user.id})}wipeLocalIdentity(){this.user=null,this.accessToken=null,this.accessExpiresAt=0,this.storage.remove(x),this.storage.remove(E),this.storage.remove(T),this.storage.remove(f),this.analytics.reset(),this.analytics.dropQueue(),this.flags.reset(),this.chatbot.newSession()}};function Pe(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 Ce(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 Le(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 De(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 Ue(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 xe(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 Oe(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 pe(r,e){if(typeof window>"u")return()=>{};let t=Oe(e.autoTrack),s=e.sessionIdleMs??18e5,n=[],i="sendora_session_id",o="sendora_session_last_activity";function a(){return de(i)??""}function c(l){he(i,l)}function d(){let l=de(o);return l&&Number(l)||0}function A(l){he(o,String(l))}function g(){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 $=g();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,ge=history.replaceState;history.pushState=function(...G){let Y=$.apply(this,G);return window.dispatchEvent(new Event("sendora:locationchange")),Y},history.replaceState=function(...G){let Y=ge.apply(this,G);return window.dispatchEvent(new Event("sendora:locationchange")),Y}}let j=()=>l();window.addEventListener("sendora:locationchange",j),n.push(()=>window.removeEventListener("sendora:locationchange",j))}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 de(r){try{return window.sessionStorage?.getItem(r)??null}catch{return null}}function he(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:Ne(i)}),c=a.toJSON(),d=c.endpoint,A=c.keys?.p256dh??"",g=c.keys?.auth??"",y=await Me(d),w=await this.http.post("/push/tokens",{orgId:this.orgId,platform:"web",token:y,webEndpoint:d,webP256dh:A,webAuth:g,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}};function Ne(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 Me(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 J(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 Z=/^[a-z0-9-]{3,20}$/;function He(r){try{let t=new URL(r).pathname.split("/").filter(Boolean);if(t.length===0)return null;let s=t[t.length-1];return Z.test(s)?s:null}catch{return null}}var Fe=5*6e4,Ke=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>Fe&&this.prewarmCache.delete(t);for(;this.prewarmCache.size>Ke;){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 J(s.error?.code,s.error?.message??"links.create failed");return s.data}async revoke(e){if(!Z.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 J(t.error?.code,t.error?.message??"links.revoke failed");return{shortcode:t.data.shortcode,revoked:!0}}async getStats(e){if(!Z.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 J(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 $e="2.14.0",q=class q{constructor(e){this.autoTrackCleanup=null;this.http=new V(e),this.storage=new B,this.consent=new U(e.defaultConsent===!0),this.analytics=new _(e,this.http,this.storage,this.consent),this.flags=new P(e,this.http,this.storage),this.surveys=new C(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 z(this.http),e.autoTrack!==!1&&(this.autoTrackCleanup=pe(this.analytics,e))}static init(e){return oe(e.apiKey),new q(e)}static sanitizeHtml(e){return X(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()}};q.version=$e;var ee=q,Tt=ee;export{_ as Analytics,F as Audiences,M as Auth,u as AuthError,D as Chatbot,U as Consent,N as EmailAlreadyTakenError,P as FeatureFlags,L as InAppMessaging,h as LinkError,K as Links,ee as SendoraCloud,C as Surveys,p as ValidationError,H as WebPush,Tt as default,He as extractShortcodeFromUrl,S as safeUrl,X as sanitizeHtml};
1
+ var se=/^[a-zA-Z][a-zA-Z0-9._:-]{0,127}$/;var me=new Set(["__proto__","constructor","prototype"]),p=class extends Error{constructor(e){super(`[SendoraCloud] ${e}`),this.name="SendoraValidationError"}};function re(r){if(typeof r!="string"||!se.test(r))throw new p(`Invalid event name: must match ${se}, got "${String(r)}".`)}function ne(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=ie(r);if(e>5)throw new p(`Properties nested too deeply (${e} levels, max 5).`);oe(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 ie(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=ie(r[s],e+1);n>t&&(t=n)}return t}function oe(r){if(!(r==null||typeof r!="object"))for(let e of Object.keys(r)){if(me.has(e))throw new p(`Forbidden property key "${e}" (prototype pollution guard).`);oe(r[e])}}var ye=/^pk_[A-Za-z0-9_-]{16,}$/;function ae(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(!ye.test(r))throw new p("Invalid publishable key format. Expected pk_... (17+ chars).")}function ce(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 we="https://api.sendoracloud.com",ve=1e4,V=class{constructor(e){let t=(e.apiUrl??we).replace(/\/$/,"");ce(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(),ve):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 g=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 ${g}: ${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 W="sendora_",B=class{constructor(){this.memoryStore=new Map;this.useLocalStorage=this.checkLocalStorage()}get(e){let t=W+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=W+e;if(this.useLocalStorage)try{localStorage.setItem(s,t);return}catch{}this.memoryStore.set(s,t)}remove(e){let t=W+e;if(this.useLocalStorage)try{localStorage.removeItem(t)}catch{}this.memoryStore.delete(t)}clearPrefix(e){let t=W+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 b(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 ue="2.9.0",le=500,ke=10,Ae=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=b("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){re(e),t&&ne(t),this.queue.length>=le&&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=b("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>ke)return;let n=Math.min(Ae,1e3*2**this.consecutiveFailures);this.nextFlushAfter=Date.now()+n;let i=Math.max(0,le-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:ue}}:{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:ue}}}};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)X(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&&X(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)X(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 X(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 Ie=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||!Se(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>Ie)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 Se(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(be)}function be(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 Te=new Set(["b","i","u","em","strong","a","p","br","span","ul","ol","li","h1","h2","h3","h4"]),Ee={a:new Set(["href","target","rel"])};function J(r){if(typeof DOMParser>"u")return"";let t=new DOMParser().parseFromString(`<div>${r}</div>`,"text/html").body.firstElementChild;return t?(de(t),t.innerHTML):""}function de(r){let e=Array.from(r.children);for(let t of e){let s=t.tagName.toLowerCase();if(!Te.has(s)){let i=document.createTextNode(t.textContent??"");t.replaceWith(i);continue}let n=Ee[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")),de(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(Re);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 Re(r){return{...r,content:{...r.content,ctaUrl:S(r.content.ctaUrl),imageUrl:S(r.content.imageUrl)}}}var Ce=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??b("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>Ce)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=b("chat_"),this.storage.set("chat_session_id",this.sessionId)}getSessionId(){return this.sessionId}};var U=class{constructor(e){this.listeners=new Set;this.granted=e}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)}emit(){for(let e of this.listeners)try{e(this.granted)}catch{}}};var T="auth_access_token",E="auth_access_expires",f="auth_refresh_token",x="auth_user",Pe=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.http=t,this.storage=s,this.analytics=n,this.flags=i,this.chatbot=o;let a=this.storage.get(x),c=this.storage.get(T),d=this.storage.get(f),A=this.storage.get(E);if(a&&(c||d))try{let g=JSON.parse(a);if(typeof g.id!="string"||g.id.length===0)throw new Error("invalid cache");this.user=g,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(x),this.storage.remove(T),this.storage.remove(E),this.storage.remove(f)}}getCurrentUser(){return this.user}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,s){return this.serialize(async()=>{let n=this.storage.get(f);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=Le(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=De(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=Oe(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)}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");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");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(T,o.accessToken),this.storage.set(E,String(this.accessExpiresAt)),this.storage.set(f,o.refreshToken);let a=xe(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(x,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(f);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(f);return e?(this.refreshInflight=(async()=>{try{let t=await this.http.post("/auth-service/token/refresh",{refreshToken:e});if(!t.success)return _e(t.error?.code)&&this.wipeLocalIdentity(),null;if(!t.data||typeof t.data!="object"||typeof t.data.accessToken!="string"||typeof t.data.refreshToken!="string"||typeof t.data.expiresIn!="number")return null;let s=t.data;return this.accessToken=s.accessToken,this.accessExpiresAt=Date.now()+s.expiresIn*1e3,this.storage.set(T,s.accessToken),this.storage.set(E,String(this.accessExpiresAt)),this.storage.set(f,s.refreshToken),s.accessToken}catch{return null}finally{this.refreshInflight=null}})(),this.refreshInflight):null}persist(e){this.user=e.user,this.accessToken=e.tokens.accessToken,this.accessExpiresAt=Date.now()+e.tokens.expiresIn*1e3,this.storage.set(x,JSON.stringify(e.user)),this.storage.set(T,e.tokens.accessToken),this.storage.set(E,String(this.accessExpiresAt)),this.storage.set(f,e.tokens.refreshToken),this.analytics.identify(e.user.id),this.flags.setContext({userId:e.user.id})}wipeLocalIdentity(){this.user=null,this.accessToken=null,this.accessExpiresAt=0,this.storage.remove(x),this.storage.remove(T),this.storage.remove(E),this.storage.remove(f),this.analytics.reset(),this.analytics.dropQueue(),this.flags.reset(),this.chatbot.newSession()}};function _e(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 Le(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 De(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 xe(r){try{let e=r.split(".");if(e.length!==3)return null;let t=e[1].replace(/-/g,"+").replace(/_/g,"/"),s=atob(t+"===".slice(0,(4-t.length%4)%4));return JSON.parse(s)}catch{return null}}function Oe(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 Ne(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 ge(r,e){if(typeof window>"u")return()=>{};let t=Ne(e.autoTrack),s=e.sessionIdleMs??18e5,n=[],i="sendora_session_id",o="sendora_session_last_activity";function a(){return he(i)??""}function c(l){pe(i,l)}function d(){let l=he(o);return l&&Number(l)||0}function A(l){pe(o,String(l))}function g(){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 $=g();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,fe=history.replaceState;history.pushState=function(...Y){let Q=$.apply(this,Y);return window.dispatchEvent(new Event("sendora:locationchange")),Q},history.replaceState=function(...Y){let Q=fe.apply(this,Y);return window.dispatchEvent(new Event("sendora:locationchange")),Q}}let G=()=>l();window.addEventListener("sendora:locationchange",G),n.push(()=>window.removeEventListener("sendora:locationchange",G))}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 he(r){try{return window.sessionStorage?.getItem(r)??null}catch{return null}}function pe(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:Me(i)}),c=a.toJSON(),d=c.endpoint,A=c.keys?.p256dh??"",g=c.keys?.auth??"",y=await He(d),w=await this.http.post("/push/tokens",{orgId:this.orgId,platform:"web",token:y,webEndpoint:d,webP256dh:A,webAuth:g,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}};function Me(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 He(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 Z(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 ee=/^[a-z0-9-]{3,20}$/;function Fe(r){try{let t=new URL(r).pathname.split("/").filter(Boolean);if(t.length===0)return null;let s=t[t.length-1];return ee.test(s)?s:null}catch{return null}}var Ke=5*6e4,$e=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>Ke&&this.prewarmCache.delete(t);for(;this.prewarmCache.size>$e;){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 Z(s.error?.code,s.error?.message??"links.create failed");return s.data}async revoke(e){if(!ee.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 Z(t.error?.code,t.error?.message??"links.revoke failed");return{shortcode:t.data.shortcode,revoked:!0}}async getStats(e){if(!ee.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 Z(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 q=class{constructor(e){this.http=e}async createTicket(e){let t=await this.http.post("/support/tickets",e);if(!t.success||!t.data)throw new Error(t.error?.message??"support.createTicket failed");return t.data}async submitCsat(e){let t=await this.http.post("/support/csat",e);if(!t.success||!t.data)throw new Error(t.error?.message??"support.submitCsat failed");return t.data}};var Ve="2.15.0",j=class j{constructor(e){this.autoTrackCleanup=null;this.http=new V(e),this.storage=new B,this.consent=new U(e.defaultConsent===!0),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 z(this.http),this.support=new q(this.http),e.autoTrack!==!1&&(this.autoTrackCleanup=ge(this.analytics,e))}static init(e){return ae(e.apiKey),new j(e)}static sanitizeHtml(e){return J(e)}static safeUrl(e){return S(e)}track(e,t){this.analytics.track(e,t)}page(e){this.analytics.page(e)}identify(e,t,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()}};j.version=Ve;var te=j,Pt=te;export{C as Analytics,F as Audiences,M as Auth,u as AuthError,D as Chatbot,U as Consent,N as EmailAlreadyTakenError,P as FeatureFlags,L as InAppMessaging,h as LinkError,K as Links,te as SendoraCloud,_ as Surveys,p as ValidationError,H as WebPush,Pt as default,Fe as extractShortcodeFromUrl,S as safeUrl,J as sanitizeHtml};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sendoracloud/sdk-web",
3
- "version": "2.14.0",
3
+ "version": "2.15.0",
4
4
  "description": "Sendora Cloud Web SDK — analytics, feature flags, surveys, in-app messaging, chatbot, auth, deep links (Branch / Firebase parity). Zero dependencies, TypeScript, SSR-safe.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",