@sendoracloud/sdk-web 2.8.0 → 2.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +1 -1
- package/dist/index.d.cts +64 -2
- package/dist/index.d.ts +64 -2
- package/dist/index.js +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";var B=Object.defineProperty;var ue=Object.getOwnPropertyDescriptor;var le=Object.getOwnPropertyNames;var he=Object.prototype.hasOwnProperty;var de=(r,e)=>{for(var t in e)B(r,t,{get:e[t],enumerable:!0})},pe=(r,e,t,s)=>{if(e&&typeof e=="object"||typeof e=="function")for(let n of le(e))!he.call(r,n)&&n!==t&&B(r,n,{get:()=>e[n],enumerable:!(s=ue(e,n))||s.enumerable});return r};var ge=r=>pe(B({},"__esModule",{value:!0}),r);var Fe={};de(Fe,{Analytics:()=>I,Auth:()=>U,AuthError:()=>c,Chatbot:()=>_,Consent:()=>b,EmailAlreadyTakenError:()=>C,FeatureFlags:()=>k,InAppMessaging:()=>T,SendoraCloud:()=>$,Surveys:()=>E,ValidationError:()=>d,default:()=>De,safeUrl:()=>f,sanitizeHtml:()=>K});module.exports=ge(Fe);var Q=/^[a-zA-Z][a-zA-Z0-9._:-]{0,127}$/;var fe=new Set(["__proto__","constructor","prototype"]),d=class extends Error{constructor(e){super(`[SendoraCloud] ${e}`),this.name="SendoraValidationError"}};function X(r){if(typeof r!="string"||!Q.test(r))throw new d(`Invalid event name: must match ${Q}, got "${String(r)}".`)}function G(r){if(r==null)return;if(typeof r!="object"||Array.isArray(r))throw new d("Properties must be a plain object, not an array or primitive.");let e=J(r);if(e>5)throw new d(`Properties nested too deeply (${e} levels, max 5).`);Z(r);let t;try{t=JSON.stringify(r).length}catch{throw new d("Properties contain a circular reference or unserializable value.")}if(t>32768)throw new d(`Properties too large (${t} bytes, max 32768).`)}function J(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=J(r[s],e+1);n>t&&(t=n)}return t}function Z(r){if(!(r==null||typeof r!="object"))for(let e of Object.keys(r)){if(fe.has(e))throw new d(`Forbidden property key "${e}" (prototype pollution guard).`);Z(r[e])}}var me=/^pk_[A-Za-z0-9_-]{16,}$/;function ee(r){if(!r||typeof r!="string")throw new d("apiKey is required.");if(r.startsWith("sk_")||r.startsWith("sendora_secret_"))throw new d("Secret keys must NEVER be used in browser SDK. Use a publishable key (pk_...).");if(!me.test(r))throw new d("Invalid publishable key format. Expected pk_... (17+ chars).")}function te(r){let e=r.replace(/\/$/,"");if(!(e.startsWith("http://localhost")||e.startsWith("http://127.0.0.1"))&&!e.startsWith("https://"))throw new d("apiUrl must be https:// (only http://localhost is allowed for dev).")}var ye="https://api.sendoracloud.com",we=1e4,F=class{constructor(e){let t=(e.apiUrl??ye).replace(/\/$/,"");te(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)}delete(e,t){return this.request("DELETE",e,void 0,t)}async request(e,t,s,n){let o=`${this.baseUrl}/api/v1${t}`,i=typeof AbortController<"u"?new AbortController:null,a=i?setTimeout(()=>i.abort(),we):null;try{let u=await fetch(o,{method:e,headers:{"Content-Type":"application/json","X-API-Key":this.apiKey,...n??{}},body:s?JSON.stringify(s):void 0,signal:i?i.signal:void 0});a&&clearTimeout(a);let h;try{h=await u.json()}catch{h={success:!1,error:{code:"PARSE_ERROR",message:`HTTP ${u.status}`}}}if(this.debug&&console.debug(`[SendoraCloud] ${e} ${t} \u2192 ${u.status}`),!u.ok)if(t.startsWith("/auth-service/")&&!this.debug)console.warn(`[SendoraCloud] ${e} ${t} \u2192 HTTP ${u.status}`);else{let z=h?.error?.code??"HTTP_ERROR",S=h?.error?.message??`HTTP ${u.status}`,O=h?.error?.details?.fieldErrors,g=O?` (${Object.entries(O).map(([l,w])=>`${l}: ${(w??[]).join(", ")}`).join("; ")})`:"";console.warn(`[SendoraCloud] ${e} ${t} \u2192 HTTP ${u.status} \u2014 ${z}: ${S}${g}`)}return h}catch(u){a&&clearTimeout(a);let h=u instanceof Error&&u.name==="AbortError"?"timeout":"error";return console.warn(`[SendoraCloud] ${e} ${t} network ${h}`),{success:!1,error:{code:"NETWORK_ERROR",message:h}}}}};var N="sendora_",H=class{constructor(){this.memoryStore=new Map;this.useLocalStorage=this.checkLocalStorage()}get(e){let t=N+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=N+e;if(this.useLocalStorage)try{localStorage.setItem(s,t);return}catch{}this.memoryStore.set(s,t)}remove(e){let t=N+e;if(this.useLocalStorage)try{localStorage.removeItem(t)}catch{}this.memoryStore.delete(t)}clearPrefix(e){let t=N+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 A(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 se="2.8.0",re=500,ve=10,Se=6e4,I=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 o=this.storage.get("anonymous_id");if(o?this.anonymousId=o:(this.anonymousId=A("anon_"),this.storage.set("anonymous_id",this.anonymousId)),this.persistUserId){let i=this.storage.get("user_id");i&&(this.userId=i)}this.timer=setInterval(()=>this.flush(),this.flushInterval),typeof window<"u"&&window.addEventListener("beforeunload",()=>this.flush()),this.consent.subscribe(i=>{i&&this.flush()})}track(e,t){X(e),t&&G(t),this.queue.length>=re&&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=A("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(i=>({projectId:this.projectId,module:"custom",eventType:i.eventType,userId:i.userId,anonymousId:i.anonymousId,identityToken:i.identityToken,timestamp:i.timestamp??new Date().toISOString(),properties:i.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(Se,1e3*2**this.consecutiveFailures);this.nextFlushAfter=Date.now()+n;let o=Math.max(0,re-this.queue.length);o>0&&this.queue.unshift(...e.slice(0,o))}destroy(){this.timer&&clearInterval(this.timer),this.timer=null,this.flush()}getContext(){return typeof window>"u"?{sdk:{name:"@sendoracloud/sdk-web",version:se}}:{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:se}}}};var k=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 o=JSON.parse(n);if(Array.isArray(o))for(let i of o)Y(i)&&this.cache.set(i.key,i)}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&&Y(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)Y(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 Y(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,E=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 o=await this.http.post("/surveys/responses",{surveyId:e,answers:t,completed:s});return o.success&&(this.activeSurvey=null),o.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(ke)}function ke(r){if(r==null||typeof r!="object")return!1;let e=r;return typeof e.id=="string"&&typeof e.text=="string"}function f(r){if(!(!r||typeof r!="string"))try{let e=typeof window<"u"?window.location.href:"https://local/",t=new URL(r,e);return t.protocol==="http:"||t.protocol==="https:"?t.href:void 0}catch{return}}var Ee=new Set(["b","i","u","em","strong","a","p","br","span","ul","ol","li","h1","h2","h3","h4"]),Te={a:new Set(["href","target","rel"])};function K(r){if(typeof DOMParser>"u")return"";let t=new DOMParser().parseFromString(`<div>${r}</div>`,"text/html").body.firstElementChild;return t?(ne(t),t.innerHTML):""}function ne(r){let e=Array.from(r.children);for(let t of e){let s=t.tagName.toLowerCase();if(!Ee.has(s)){let o=document.createTextNode(t.textContent??"");t.replaceWith(o);continue}let n=Te[s]??new Set;for(let o of Array.from(t.attributes)){if(!n.has(o.name.toLowerCase())){t.removeAttribute(o.name);continue}if(o.name.toLowerCase()==="href"){let i=f(o.value);i?t.setAttribute("href",i):t.removeAttribute(o.name)}}s==="a"&&(t.setAttribute("rel","noopener noreferrer"),t.setAttribute("target","_blank")),ne(t)}}var T=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(_e);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 _e(r){return{...r,content:{...r.content,ctaUrl:f(r.content.ctaUrl),imageUrl:f(r.content.imageUrl)}}}var be=4e3,_=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??A("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>be)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 o={role:"assistant",content:n.data.reply,timestamp:new Date().toISOString()};return this.messages.push(o),this.onMessage&&this.onMessage(o),o}return null}getMessages(){return[...this.messages]}newSession(){this.messages=[],this.sessionId=A("chat_"),this.storage.set("chat_session_id",this.sessionId)}getSessionId(){return this.sessionId}};var b=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 R="auth_access_token",P="auth_access_expires",m="auth_refresh_token",L="auth_user",Re=3e4;function p(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 C=class extends Error{constructor(e="An account with this email already exists"){super(e),this.name="EmailAlreadyTakenError"}},c=class extends Error{constructor(e,t){super(t),this.name="AuthError",this.code=e}},U=class{constructor(e,t,s,n,o,i){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=o,this.chatbot=i;let a=this.storage.get(L),u=this.storage.get(R),h=this.storage.get(P);if(a&&u)try{let v=JSON.parse(a);if(typeof v.id!="string"||v.id.length===0)throw new Error("invalid cache");this.user=v,this.accessToken=u,this.accessExpiresAt=h?Number(h):0,this.analytics.identify(this.user.id)}catch{this.storage.remove(L),this.storage.remove(R),this.storage.remove(P),this.storage.remove(m)}}getCurrentUser(){return this.user}async getAccessToken(){return this.accessToken?this.accessExpiresAt&&Date.now()<this.accessExpiresAt-Re?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||!p(t.data))throw new c(t.error?.code??"AUTH_ERROR",t.error?.message??"Anonymous signup failed");return this.persist(t.data),t.data.user})}signUp(e,t,s){return this.serialize(async()=>{let n=this.storage.get(m);if(n){let i=await this.http.post("/auth-service/upgrade",{refreshToken:n,email:e,password:t,name:s?.name});if(i.success&&p(i.data))return this.persist(i.data),i.data.user;let a=i.error?.code??"",u=i.error?.status;if(a==="CONFLICT"||a==="EMAIL_ALREADY_TAKEN"||u===409)throw new C(i.error?.message);if(a==="FORBIDDEN_NON_ANONYMOUS"||a==="INVALID_REFRESH"||a==="UNAUTHORIZED"||a==="REFRESH_EXPIRED")await this.analytics.flush(),this.wipeLocalIdentity();else throw new c(a||"AUTH_ERROR",i.error?.message??"Upgrade failed")}let o=await this.http.post("/auth-service/signup",{email:e,password:t,name:s?.name,metadata:s?.metadata});if(!o.success||!p(o.data)){let i=o.error?.code??"";throw i==="CONFLICT"?new C(o.error?.message):new c(i||"AUTH_ERROR",o.error?.message??"Signup failed")}return this.persist(o.data),o.data.user})}signIn(e,t){return this.serialize(async()=>{this.user!==null&&(await this.analytics.flush(),this.wipeLocalIdentity());let n=await this.http.post("/auth-service/login",{email:e,password:t});if(!n.success||!n.data)throw new c(n.error?.code??"AUTH_ERROR",n.error?.message??"Login failed");let o=n.data;if(o.mfaRequired===!0&&typeof o.mfaChallengeToken=="string"){let i=o.user;return{mfaRequired:!0,mfaChallengeToken:o.mfaChallengeToken,userId:i?.id??""}}if(!p(n.data))throw new c(n.error?.code??"AUTH_ERROR",n.error?.message??"Login failed");return this.persist(n.data),n.data.user})}loginSocial(e){return this.serialize(async()=>{this.user!==null&&(await this.analytics.flush(),this.wipeLocalIdentity());let s=await this.http.post("/auth-service/login/social",e);if(!s.success||!p(s.data))throw new c(s.error?.code??"SOCIAL_LOGIN_FAILED",s.error?.message??"Social login failed");return this.persist(s.data),s.data.user})}signInWithGoogle(e,t){return this.loginSocial({provider:"google",code:e,redirectUri:t})}signInWithGitHub(e,t){return this.loginSocial({provider:"github",code:e,redirectUri:t})}signInWithApple(e){return this.loginSocial({provider:"apple",...e})}signInWithMicrosoft(e,t){return this.loginSocial({provider:"microsoft",code:e,redirectUri:t})}signInWithLinkedIn(e,t){return this.loginSocial({provider:"linkedin",code:e,redirectUri:t})}signInWithFacebook(e,t){return this.loginSocial({provider:"facebook",code:e,redirectUri:t})}signInWithDiscord(e,t){return this.loginSocial({provider:"discord",code:e,redirectUri:t})}challengeMfa(e,t){return this.serialize(async()=>{let s=await this.http.post("/auth-service/mfa/challenge",{challengeToken:e,code:t});if(!s.success||!p(s.data))throw new c(s.error?.code??"MFA_CHALLENGE_FAILED",s.error?.message??"Invalid MFA code");return this.persist(s.data),s.data.user})}async sendMagicLink(e){let t=await this.http.post("/auth-service/magic-link/request",{email:e});if(!t.success)throw new c(t.error?.code??"MAGIC_LINK_REQUEST_FAILED",t.error?.message??"Failed to send link")}async sendEmailOtp(e){let t=await this.http.post("/auth-service/email-otp/request",{email:e});if(!t.success)throw new c(t.error?.code??"EMAIL_OTP_REQUEST_FAILED",t.error?.message??"Failed to send code")}verifyEmailOtp(e,t){return this.serialize(async()=>{this.user!==null&&(await this.analytics.flush(),this.wipeLocalIdentity());let n=await this.http.post("/auth-service/email-otp/verify",{email:e,code:t});if(!n.success||!p(n.data))throw new c(n.error?.code??"EMAIL_OTP_INVALID",n.error?.message??"Wrong or expired code");return this.persist(n.data),n.data.user})}async requestPasswordReset(e){let t=await this.http.post("/auth-service/password/forgot",{email:e});if(!t.success)throw new c(t.error?.code??"PASSWORD_RESET_REQUEST_FAILED",t.error?.message??"Failed to send reset email")}async resetPassword(e,t){let s=await this.http.post("/auth-service/password/reset",{token:e,newPassword:t});if(!s.success)throw new c(s.error?.code??"PASSWORD_RESET_FAILED",s.error?.message??"Reset failed")}async verifyEmail(e){let t=await this.http.post("/auth-service/email/verify",{token:e});if(!t.success)throw new c(t.error?.code??"EMAIL_VERIFICATION_FAILED",t.error?.message??"Verification failed")}async sendVerificationEmail(){let e=await this.getAccessToken();if(!e)throw new c("UNAUTHORIZED","Sign in before requesting a verification email");let t=await this.http.post("/auth-service/email/verify/resend",{},{Authorization:`Bearer ${e}`});if(!t.success)throw new c(t.error?.code??"VERIFICATION_RESEND_FAILED",t.error?.message??"Failed to send verification email")}verifyMagicLink(e){return this.serialize(async()=>{this.user!==null&&(await this.analytics.flush(),this.wipeLocalIdentity());let s=await this.http.post("/auth-service/magic-link/verify",{token:e});if(!s.success||!p(s.data))throw new c(s.error?.code??"MAGIC_LINK_INVALID",s.error?.message??"Invalid or expired magic link");return this.persist(s.data),s.data.user})}async enrollMfa(){let e=await this.http.post("/auth-service/mfa/enroll/start",{},this.bearerHeaders());if(!e.success||!e.data)throw new c(e.error?.code??"MFA_ENROLL_FAILED",e.error?.message??"MFA enrollment failed");return e.data}async confirmMfa(e){let t=await this.http.post("/auth-service/mfa/enroll/confirm",{code:e},this.bearerHeaders());return!!(t.success&&t.data?.confirmed)}async disableMfa(){await this.http.post("/auth-service/mfa/disable",{},this.bearerHeaders())}async registerPasskey(e){if(typeof navigator>"u"||!navigator.credentials)throw new c("PASSKEY_UNSUPPORTED","WebAuthn not supported in this environment");let t=await this.http.post("/auth-service/passkeys/register/start",{},this.bearerHeaders());if(!t.success||!t.data)throw new c(t.error?.code??"PASSKEY_START_FAILED",t.error?.message??"Passkey start failed");let s=Pe(t.data),n;try{let a=await navigator.credentials.create({publicKey:s});if(!a)throw new Error("create returned null");n=a}catch(a){throw new c("PASSKEY_USER_CANCELLED",a instanceof Error?a.message:"User cancelled passkey registration")}let o=Ue(n),i=await this.http.post("/auth-service/passkeys/register/finish",{response:o,name:e},this.bearerHeaders());if(!i.success||!i.data)throw new c(i.error?.code??"PASSKEY_FINISH_FAILED",i.error?.message??"Passkey verification failed");return i.data}signInWithPasskey(e){return this.serialize(async()=>{if(typeof navigator>"u"||!navigator.credentials)throw new c("PASSKEY_UNSUPPORTED","WebAuthn not supported in this environment");this.user!==null&&(await this.analytics.flush(),this.wipeLocalIdentity());let s=await this.http.post("/auth-service/passkeys/authenticate/start",{userId:e??null});if(!s.success||!s.data)throw new c(s.error?.code??"PASSKEY_START_FAILED",s.error?.message??"Passkey start failed");let n=Ce(s.data),o;try{let u=await navigator.credentials.get({publicKey:n});if(!u)throw new Error("get returned null");o=u}catch(u){throw new c("PASSKEY_USER_CANCELLED",u instanceof Error?u.message:"User cancelled passkey signin")}let i=xe(o),a=await this.http.post("/auth-service/passkeys/authenticate/finish",{response:i,userId:e??null});if(!a.success||!p(a.data))throw new c(a.error?.code??"PASSKEY_AUTH_FAILED",a.error?.message??"Passkey signin failed");return this.persist(a.data),a.data.user})}async listPasskeys(){let e=await this.http.get("/auth-service/passkeys",this.bearerHeaders());return!e.success||!Array.isArray(e.data)?[]:e.data}async deletePasskey(e){await this.http.delete(`/auth-service/passkeys/${e}`,this.bearerHeaders())}async listMySessions(){let e=await this.http.get("/auth-service/sessions/me",this.bearerHeaders());return!e.success||!Array.isArray(e.data)?[]:e.data}async revokeSession(e){await this.http.delete(`/auth-service/sessions/me/${e}`,this.bearerHeaders())}async revokeAllSessions(){await this.http.delete("/auth-service/sessions/me",this.bearerHeaders())}async startSso(e){let t=await this.http.post("/auth-service/sso/oidc/start",{returnTo:e});if(!t.success||!t.data)throw new c(t.error?.code??"SSO_START_FAILED",t.error?.message??"SSO start failed");return t.data}async signInWithSso(e){let t=e??(typeof window<"u"?window.location.origin:"");if(!t)throw new c("SSO_NO_RETURN_TO","returnTo required outside browser context");let s=await this.startSso(t);typeof window<"u"&&(window.location.href=s.authorizationUrl)}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 c("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 o=await this.http.post("/auth-service/token/refresh",{refreshToken:n});if(!o.success||!o.data)throw new c(o.error?.code??"SSO_EXCHANGE_FAILED",o.error?.message??"Could not exchange SSO token");let i=o.data;this.accessToken=i.accessToken,this.accessExpiresAt=Date.now()+i.expiresIn*1e3,this.storage.set(R,i.accessToken),this.storage.set(P,String(this.accessExpiresAt)),this.storage.set(m,i.refreshToken);let a=Oe(i.accessToken),u={id:a?.sub??"",email:a?.email??null,emailVerified:a?.email_verified??!0,name:a?.name??null,isAnonymous:!1};return this.user=u,this.storage.set(L,JSON.stringify(u)),this.analytics.identify(u.id),this.flags.setContext({userId:u.id}),u})}bearerHeaders(){if(!this.accessToken)throw new c("NOT_SIGNED_IN","Not signed in");return{Authorization:`Bearer ${this.accessToken}`}}signOut(){return this.serialize(async()=>{let e=this.storage.get(m);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(m);return e?(this.refreshInflight=(async()=>{try{let t=await this.http.post("/auth-service/token/refresh",{refreshToken:e});if(!t.success||!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(R,s.accessToken),this.storage.set(P,String(this.accessExpiresAt)),this.storage.set(m,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(L,JSON.stringify(e.user)),this.storage.set(R,e.tokens.accessToken),this.storage.set(P,String(this.accessExpiresAt)),this.storage.set(m,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(L),this.storage.remove(R),this.storage.remove(P),this.storage.remove(m),this.analytics.reset(),this.analytics.dropQueue(),this.flags.reset(),this.chatbot.newSession()}};function M(r){let e=(4-r.length%4)%4,t=(r+"===".slice(0,e)).replace(/-/g,"+").replace(/_/g,"/"),s=atob(t),n=new ArrayBuffer(s.length),o=new Uint8Array(n);for(let i=0;i<s.length;i++)o[i]=s.charCodeAt(i);return n}function y(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 Pe(r){return{challenge:M(r.challenge),rp:r.rp,user:{id:M(r.user.id),name:r.user.name,displayName:r.user.displayName},pubKeyCredParams:r.pubKeyCredParams,timeout:r.timeout,attestation:r.attestation,excludeCredentials:r.excludeCredentials?.map(e=>({id:M(e.id),type:"public-key",transports:e.transports})),authenticatorSelection:r.authenticatorSelection}}function Ce(r){return{challenge:M(r.challenge),timeout:r.timeout,rpId:r.rpId,allowCredentials:r.allowCredentials?.map(e=>({id:M(e.id),type:"public-key",transports:e.transports})),userVerification:r.userVerification}}function Ue(r){let e=r.response;return{id:r.id,rawId:y(r.rawId),type:r.type,response:{clientDataJSON:y(e.clientDataJSON),attestationObject:y(e.attestationObject),transports:typeof e.getTransports=="function"?e.getTransports():void 0},clientExtensionResults:r.getClientExtensionResults()}}function Oe(r){try{let e=r.split(".");if(e.length!==3)return null;let t=e[1].replace(/-/g,"+").replace(/_/g,"/"),s=atob(t+"===".slice(0,(4-t.length%4)%4));return JSON.parse(s)}catch{return null}}function xe(r){let e=r.response;return{id:r.id,rawId:y(r.rawId),type:r.type,response:{clientDataJSON:y(e.clientDataJSON),authenticatorData:y(e.authenticatorData),signature:y(e.signature),userHandle:e.userHandle?y(e.userHandle):null},clientExtensionResults:r.getClientExtensionResults()}}function Le(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 ae(r,e){if(typeof window>"u")return()=>{};let t=Le(e.autoTrack),s=e.sessionIdleMs??18e5,n=[],o="sendora_session_id",i="sendora_session_last_activity";function a(){return ie(o)??""}function u(l){oe(o,l)}function h(){let l=ie(i);return l&&Number(l)||0}function v(l){oe(i,String(l))}function z(){return`sess_${Date.now().toString(36)}_${Math.random().toString(36).slice(2,10)}`}function S(l){let w=Date.now(),x=h();if(!a()||x&&w-x>s){let D=z();u(D),l&&t.sessionStart&&r.track("session.start",{sessionId:D})}v(w)}if(S(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=()=>{S(!0),r.page()},w=()=>l();window.addEventListener("popstate",w),n.push(()=>window.removeEventListener("popstate",w));let x=window;if(!x.__sendoraHistoryPatched){x.__sendoraHistoryPatched=!0;let D=history.pushState,ce=history.replaceState;history.pushState=function(...q){let j=D.apply(this,q);return window.dispatchEvent(new Event("sendora:locationchange")),j},history.replaceState=function(...q){let j=ce.apply(this,q);return window.dispatchEvent(new Event("sendora:locationchange")),j}}let W=()=>l();window.addEventListener("sendora:locationchange",W),n.push(()=>window.removeEventListener("sendora:locationchange",W))}if(t.appBackground){let l=()=>{S(!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 O=0,g=()=>{let l=Date.now();l-O<5e3||(O=l,S(!0))};return window.addEventListener("click",g,{passive:!0}),window.addEventListener("keydown",g,{passive:!0}),window.addEventListener("scroll",g,{passive:!0}),n.push(()=>{window.removeEventListener("click",g),window.removeEventListener("keydown",g),window.removeEventListener("scroll",g)}),()=>{for(let l of n)try{l()}catch{}}}function ie(r){try{return window.sessionStorage?.getItem(r)??null}catch{return null}}function oe(r,e){try{window.sessionStorage?.setItem(r,e)}catch{}}var Me="2.8.0",V=class V{constructor(e){this.autoTrackCleanup=null;this.http=new F(e),this.storage=new H,this.consent=new b(e.defaultConsent===!0),this.analytics=new I(e,this.http,this.storage,this.consent),this.flags=new k(e,this.http,this.storage),this.surveys=new E(e,this.http,this.storage),this.messages=new T(e,this.http,this.storage),this.chatbot=new _(e,this.http,this.storage),this.auth=new U(e,this.http,this.storage,this.analytics,this.flags,this.chatbot),e.autoTrack!==!1&&(this.autoTrackCleanup=ae(this.analytics,e))}static init(e){return ee(e.apiKey),new V(e)}static sanitizeHtml(e){return K(e)}static safeUrl(e){return f(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()}};V.version=Me;var $=V,De=$;0&&(module.exports={Analytics,Auth,AuthError,Chatbot,Consent,EmailAlreadyTakenError,FeatureFlags,InAppMessaging,SendoraCloud,Surveys,ValidationError,safeUrl,sanitizeHtml});
|
|
1
|
+
"use strict";var Y=Object.defineProperty;var le=Object.getOwnPropertyDescriptor;var de=Object.getOwnPropertyNames;var he=Object.prototype.hasOwnProperty;var pe=(r,e)=>{for(var t in e)Y(r,t,{get:e[t],enumerable:!0})},ge=(r,e,t,s)=>{if(e&&typeof e=="object"||typeof e=="function")for(let n of de(e))!he.call(r,n)&&n!==t&&Y(r,n,{get:()=>e[n],enumerable:!(s=le(e,n))||s.enumerable});return r};var fe=r=>ge(Y({},"__esModule",{value:!0}),r);var Ke={};pe(Ke,{Analytics:()=>k,Auth:()=>O,AuthError:()=>c,Chatbot:()=>_,Consent:()=>P,EmailAlreadyTakenError:()=>U,FeatureFlags:()=>b,InAppMessaging:()=>T,SendoraCloud:()=>V,Surveys:()=>E,ValidationError:()=>h,WebPush:()=>x,default:()=>He,safeUrl:()=>w,sanitizeHtml:()=>W});module.exports=fe(Ke);var X=/^[a-zA-Z][a-zA-Z0-9._:-]{0,127}$/;var me=new Set(["__proto__","constructor","prototype"]),h=class extends Error{constructor(e){super(`[SendoraCloud] ${e}`),this.name="SendoraValidationError"}};function J(r){if(typeof r!="string"||!X.test(r))throw new h(`Invalid event name: must match ${X}, got "${String(r)}".`)}function G(r){if(r==null)return;if(typeof r!="object"||Array.isArray(r))throw new h("Properties must be a plain object, not an array or primitive.");let e=Z(r);if(e>5)throw new h(`Properties nested too deeply (${e} levels, max 5).`);ee(r);let t;try{t=JSON.stringify(r).length}catch{throw new h("Properties contain a circular reference or unserializable value.")}if(t>32768)throw new h(`Properties too large (${t} bytes, max 32768).`)}function Z(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=Z(r[s],e+1);n>t&&(t=n)}return t}function ee(r){if(!(r==null||typeof r!="object"))for(let e of Object.keys(r)){if(me.has(e))throw new h(`Forbidden property key "${e}" (prototype pollution guard).`);ee(r[e])}}var ye=/^pk_[A-Za-z0-9_-]{16,}$/;function te(r){if(!r||typeof r!="string")throw new h("apiKey is required.");if(r.startsWith("sk_")||r.startsWith("sendora_secret_"))throw new h("Secret keys must NEVER be used in browser SDK. Use a publishable key (pk_...).");if(!ye.test(r))throw new h("Invalid publishable key format. Expected pk_... (17+ chars).")}function se(r){let e=r.replace(/\/$/,"");if(!(e.startsWith("http://localhost")||e.startsWith("http://127.0.0.1"))&&!e.startsWith("https://"))throw new h("apiUrl must be https:// (only http://localhost is allowed for dev).")}var we="https://api.sendoracloud.com",ve=1e4,H=class{constructor(e){let t=(e.apiUrl??we).replace(/\/$/,"");se(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)}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 u=await fetch(i,{method:e,headers:{"Content-Type":"application/json","X-API-Key":this.apiKey,...n??{}},body:s?JSON.stringify(s):void 0,signal:o?o.signal:void 0});a&&clearTimeout(a);let d;try{d=await u.json()}catch{d={success:!1,error:{code:"PARSE_ERROR",message:`HTTP ${u.status}`}}}if(this.debug&&console.debug(`[SendoraCloud] ${e} ${t} \u2192 ${u.status}`),!u.ok)if(t.startsWith("/auth-service/")&&!this.debug)console.warn(`[SendoraCloud] ${e} ${t} \u2192 HTTP ${u.status}`);else{let L=d?.error?.code??"HTTP_ERROR",f=d?.error?.message??`HTTP ${u.status}`,m=d?.error?.details?.fieldErrors,y=m?` (${Object.entries(m).map(([l,A])=>`${l}: ${(A??[]).join(", ")}`).join("; ")})`:"";console.warn(`[SendoraCloud] ${e} ${t} \u2192 HTTP ${u.status} \u2014 ${L}: ${f}${y}`)}return d}catch(u){a&&clearTimeout(a);let d=u instanceof Error&&u.name==="AbortError"?"timeout":"error";return console.warn(`[SendoraCloud] ${e} ${t} network ${d}`),{success:!1,error:{code:"NETWORK_ERROR",message:d}}}}};var K="sendora_",$=class{constructor(){this.memoryStore=new Map;this.useLocalStorage=this.checkLocalStorage()}get(e){let t=K+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=K+e;if(this.useLocalStorage)try{localStorage.setItem(s,t);return}catch{}this.memoryStore.set(s,t)}remove(e){let t=K+e;if(this.useLocalStorage)try{localStorage.removeItem(t)}catch{}this.memoryStore.delete(t)}clearPrefix(e){let t=K+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 I(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 re="2.9.0",ne=500,Se=10,Ae=6e4,k=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=I("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){J(e),t&&G(t),this.queue.length>=ne&&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=I("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>Se)return;let n=Math.min(Ae,1e3*2**this.consecutiveFailures);this.nextFlushAfter=Date.now()+n;let i=Math.max(0,ne-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:re}}:{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:re}}}};var b=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 Ie=32*1024,E=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||!ke(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 ke(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 w(r){if(!(!r||typeof r!="string"))try{let e=typeof window<"u"?window.location.href:"https://local/",t=new URL(r,e);return t.protocol==="http:"||t.protocol==="https:"?t.href:void 0}catch{return}}var Ee=new Set(["b","i","u","em","strong","a","p","br","span","ul","ol","li","h1","h2","h3","h4"]),Te={a:new Set(["href","target","rel"])};function W(r){if(typeof DOMParser>"u")return"";let t=new DOMParser().parseFromString(`<div>${r}</div>`,"text/html").body.firstElementChild;return t?(ie(t),t.innerHTML):""}function ie(r){let e=Array.from(r.children);for(let t of e){let s=t.tagName.toLowerCase();if(!Ee.has(s)){let i=document.createTextNode(t.textContent??"");t.replaceWith(i);continue}let n=Te[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=w(i.value);o?t.setAttribute("href",o):t.removeAttribute(i.name)}}s==="a"&&(t.setAttribute("rel","noopener noreferrer"),t.setAttribute("target","_blank")),ie(t)}}var T=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(_e);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 _e(r){return{...r,content:{...r.content,ctaUrl:w(r.content.ctaUrl),imageUrl:w(r.content.imageUrl)}}}var Pe=4e3,_=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??I("chat_"),n||this.storage.set("chat_session_id",this.sessionId)}init(e){if(typeof e!="string"||!/^[A-Za-z0-9_-]{6,64}$/.test(e))throw new Error("[SendoraCloud] Invalid chatbotId.");this.chatbotId=e}onNewMessage(e){this.onMessage=e}async sendMessage(e,t){if(!this.chatbotId||typeof e!="string"||e.length===0||e.length>Pe)return null;let s={role:"user",content:e,timestamp:new Date().toISOString()};this.messages.push(s);let n=await this.http.post("/chatbot/message",{chatbotId:this.chatbotId,sessionId:this.sessionId,message:e,userId:t});if(n.success&&n.data&&typeof n.data.reply=="string"){let i={role:"assistant",content:n.data.reply,timestamp:new Date().toISOString()};return this.messages.push(i),this.onMessage&&this.onMessage(i),i}return null}getMessages(){return[...this.messages]}newSession(){this.messages=[],this.sessionId=I("chat_"),this.storage.set("chat_session_id",this.sessionId)}getSessionId(){return this.sessionId}};var P=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 R="auth_access_token",C="auth_access_expires",v="auth_refresh_token",D="auth_user",Re=3e4;function p(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"}},c=class extends Error{constructor(e,t){super(t),this.name="AuthError",this.code=e}},O=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(D),u=this.storage.get(R),d=this.storage.get(C);if(a&&u)try{let g=JSON.parse(a);if(typeof g.id!="string"||g.id.length===0)throw new Error("invalid cache");this.user=g,this.accessToken=u,this.accessExpiresAt=d?Number(d):0,this.analytics.identify(this.user.id)}catch{this.storage.remove(D),this.storage.remove(R),this.storage.remove(C),this.storage.remove(v)}}getCurrentUser(){return this.user}async getAccessToken(){return this.accessToken?this.accessExpiresAt&&Date.now()<this.accessExpiresAt-Re?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||!p(t.data))throw new c(t.error?.code??"AUTH_ERROR",t.error?.message??"Anonymous signup failed");return this.persist(t.data),t.data.user})}signUp(e,t,s){return this.serialize(async()=>{let n=this.storage.get(v);if(n){let o=await this.http.post("/auth-service/upgrade",{refreshToken:n,email:e,password:t,name:s?.name});if(o.success&&p(o.data))return this.persist(o.data),o.data.user;let a=o.error?.code??"",u=o.error?.status;if(a==="CONFLICT"||a==="EMAIL_ALREADY_TAKEN"||u===409)throw new 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 c(a||"AUTH_ERROR",o.error?.message??"Upgrade failed")}let i=await this.http.post("/auth-service/signup",{email:e,password:t,name:s?.name,metadata:s?.metadata});if(!i.success||!p(i.data)){let o=i.error?.code??"";throw o==="CONFLICT"?new U(i.error?.message):new c(o||"AUTH_ERROR",i.error?.message??"Signup failed")}return this.persist(i.data),i.data.user})}signIn(e,t){return this.serialize(async()=>{this.user!==null&&(await this.analytics.flush(),this.wipeLocalIdentity());let n=await this.http.post("/auth-service/login",{email:e,password:t});if(!n.success||!n.data)throw new c(n.error?.code??"AUTH_ERROR",n.error?.message??"Login failed");let i=n.data;if(i.mfaRequired===!0&&typeof i.mfaChallengeToken=="string"){let o=i.user;return{mfaRequired:!0,mfaChallengeToken:i.mfaChallengeToken,userId:o?.id??""}}if(!p(n.data))throw new c(n.error?.code??"AUTH_ERROR",n.error?.message??"Login failed");return this.persist(n.data),n.data.user})}loginSocial(e){return this.serialize(async()=>{this.user!==null&&(await this.analytics.flush(),this.wipeLocalIdentity());let s=await this.http.post("/auth-service/login/social",e);if(!s.success||!p(s.data))throw new c(s.error?.code??"SOCIAL_LOGIN_FAILED",s.error?.message??"Social login failed");return this.persist(s.data),s.data.user})}signInWithGoogle(e,t){return this.loginSocial({provider:"google",code:e,redirectUri:t})}signInWithGitHub(e,t){return this.loginSocial({provider:"github",code:e,redirectUri:t})}signInWithApple(e){return this.loginSocial({provider:"apple",...e})}signInWithMicrosoft(e,t){return this.loginSocial({provider:"microsoft",code:e,redirectUri:t})}signInWithLinkedIn(e,t){return this.loginSocial({provider:"linkedin",code:e,redirectUri:t})}signInWithFacebook(e,t){return this.loginSocial({provider:"facebook",code:e,redirectUri:t})}signInWithDiscord(e,t){return this.loginSocial({provider:"discord",code:e,redirectUri:t})}challengeMfa(e,t){return this.serialize(async()=>{let s=await this.http.post("/auth-service/mfa/challenge",{challengeToken:e,code:t});if(!s.success||!p(s.data))throw new c(s.error?.code??"MFA_CHALLENGE_FAILED",s.error?.message??"Invalid MFA code");return this.persist(s.data),s.data.user})}async sendMagicLink(e){let t=await this.http.post("/auth-service/magic-link/request",{email:e});if(!t.success)throw new c(t.error?.code??"MAGIC_LINK_REQUEST_FAILED",t.error?.message??"Failed to send link")}async sendEmailOtp(e){let t=await this.http.post("/auth-service/email-otp/request",{email:e});if(!t.success)throw new c(t.error?.code??"EMAIL_OTP_REQUEST_FAILED",t.error?.message??"Failed to send code")}verifyEmailOtp(e,t){return this.serialize(async()=>{this.user!==null&&(await this.analytics.flush(),this.wipeLocalIdentity());let n=await this.http.post("/auth-service/email-otp/verify",{email:e,code:t});if(!n.success||!p(n.data))throw new c(n.error?.code??"EMAIL_OTP_INVALID",n.error?.message??"Wrong or expired code");return this.persist(n.data),n.data.user})}async requestPasswordReset(e){let t=await this.http.post("/auth-service/password/forgot",{email:e});if(!t.success)throw new c(t.error?.code??"PASSWORD_RESET_REQUEST_FAILED",t.error?.message??"Failed to send reset email")}async resetPassword(e,t){let s=await this.http.post("/auth-service/password/reset",{token:e,newPassword:t});if(!s.success)throw new c(s.error?.code??"PASSWORD_RESET_FAILED",s.error?.message??"Reset failed")}async verifyEmail(e){let t=await this.http.post("/auth-service/email/verify",{token:e});if(!t.success)throw new c(t.error?.code??"EMAIL_VERIFICATION_FAILED",t.error?.message??"Verification failed")}async sendVerificationEmail(){let e=await this.getAccessToken();if(!e)throw new c("UNAUTHORIZED","Sign in before requesting a verification email");let t=await this.http.post("/auth-service/email/verify/resend",{},{Authorization:`Bearer ${e}`});if(!t.success)throw new c(t.error?.code??"VERIFICATION_RESEND_FAILED",t.error?.message??"Failed to send verification email")}verifyMagicLink(e){return this.serialize(async()=>{this.user!==null&&(await this.analytics.flush(),this.wipeLocalIdentity());let s=await this.http.post("/auth-service/magic-link/verify",{token:e});if(!s.success||!p(s.data))throw new c(s.error?.code??"MAGIC_LINK_INVALID",s.error?.message??"Invalid or expired magic link");return this.persist(s.data),s.data.user})}async enrollMfa(){let e=await this.http.post("/auth-service/mfa/enroll/start",{},this.bearerHeaders());if(!e.success||!e.data)throw new c(e.error?.code??"MFA_ENROLL_FAILED",e.error?.message??"MFA enrollment failed");return e.data}async confirmMfa(e){let t=await this.http.post("/auth-service/mfa/enroll/confirm",{code:e},this.bearerHeaders());return!!(t.success&&t.data?.confirmed)}async disableMfa(){await this.http.post("/auth-service/mfa/disable",{},this.bearerHeaders())}async registerPasskey(e){if(typeof navigator>"u"||!navigator.credentials)throw new c("PASSKEY_UNSUPPORTED","WebAuthn not supported in this environment");let t=await this.http.post("/auth-service/passkeys/register/start",{},this.bearerHeaders());if(!t.success||!t.data)throw new c(t.error?.code??"PASSKEY_START_FAILED",t.error?.message??"Passkey start failed");let s=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 c("PASSKEY_USER_CANCELLED",a instanceof Error?a.message:"User cancelled passkey registration")}let i=Oe(n),o=await this.http.post("/auth-service/passkeys/register/finish",{response:i,name:e},this.bearerHeaders());if(!o.success||!o.data)throw new c(o.error?.code??"PASSKEY_FINISH_FAILED",o.error?.message??"Passkey verification failed");return o.data}signInWithPasskey(e){return this.serialize(async()=>{if(typeof navigator>"u"||!navigator.credentials)throw new c("PASSKEY_UNSUPPORTED","WebAuthn not supported in this environment");this.user!==null&&(await this.analytics.flush(),this.wipeLocalIdentity());let s=await this.http.post("/auth-service/passkeys/authenticate/start",{userId:e??null});if(!s.success||!s.data)throw new c(s.error?.code??"PASSKEY_START_FAILED",s.error?.message??"Passkey start failed");let n=Ue(s.data),i;try{let u=await navigator.credentials.get({publicKey:n});if(!u)throw new Error("get returned null");i=u}catch(u){throw new c("PASSKEY_USER_CANCELLED",u instanceof Error?u.message:"User cancelled passkey signin")}let o=Le(i),a=await this.http.post("/auth-service/passkeys/authenticate/finish",{response:o,userId:e??null});if(!a.success||!p(a.data))throw new c(a.error?.code??"PASSKEY_AUTH_FAILED",a.error?.message??"Passkey signin failed");return this.persist(a.data),a.data.user})}async listPasskeys(){let e=await this.http.get("/auth-service/passkeys",this.bearerHeaders());return!e.success||!Array.isArray(e.data)?[]:e.data}async deletePasskey(e){await this.http.delete(`/auth-service/passkeys/${e}`,this.bearerHeaders())}async listMySessions(){let e=await this.http.get("/auth-service/sessions/me",this.bearerHeaders());return!e.success||!Array.isArray(e.data)?[]:e.data}async revokeSession(e){await this.http.delete(`/auth-service/sessions/me/${e}`,this.bearerHeaders())}async revokeAllSessions(){await this.http.delete("/auth-service/sessions/me",this.bearerHeaders())}async startSso(e){let t=await this.http.post("/auth-service/sso/oidc/start",{returnTo:e});if(!t.success||!t.data)throw new c(t.error?.code??"SSO_START_FAILED",t.error?.message??"SSO start failed");return t.data}async signInWithSso(e){let t=e??(typeof window<"u"?window.location.origin:"");if(!t)throw new c("SSO_NO_RETURN_TO","returnTo required outside browser context");let s=await this.startSso(t);typeof window<"u"&&(window.location.href=s.authorizationUrl)}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 c("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 c(i.error?.code??"SSO_EXCHANGE_FAILED",i.error?.message??"Could not exchange SSO token");let o=i.data;this.accessToken=o.accessToken,this.accessExpiresAt=Date.now()+o.expiresIn*1e3,this.storage.set(R,o.accessToken),this.storage.set(C,String(this.accessExpiresAt)),this.storage.set(v,o.refreshToken);let a=xe(o.accessToken),u={id:a?.sub??"",email:a?.email??null,emailVerified:a?.email_verified??!0,name:a?.name??null,isAnonymous:!1};return this.user=u,this.storage.set(D,JSON.stringify(u)),this.analytics.identify(u.id),this.flags.setContext({userId:u.id}),u})}bearerHeaders(){if(!this.accessToken)throw new c("NOT_SIGNED_IN","Not signed in");return{Authorization:`Bearer ${this.accessToken}`}}signOut(){return this.serialize(async()=>{let e=this.storage.get(v);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(v);return e?(this.refreshInflight=(async()=>{try{let t=await this.http.post("/auth-service/token/refresh",{refreshToken:e});if(!t.success||!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(R,s.accessToken),this.storage.set(C,String(this.accessExpiresAt)),this.storage.set(v,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(D,JSON.stringify(e.user)),this.storage.set(R,e.tokens.accessToken),this.storage.set(C,String(this.accessExpiresAt)),this.storage.set(v,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(D),this.storage.remove(R),this.storage.remove(C),this.storage.remove(v),this.analytics.reset(),this.analytics.dropQueue(),this.flags.reset(),this.chatbot.newSession()}};function N(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 S(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:N(r.challenge),rp:r.rp,user:{id:N(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:N(e.id),type:"public-key",transports:e.transports})),authenticatorSelection:r.authenticatorSelection}}function Ue(r){return{challenge:N(r.challenge),timeout:r.timeout,rpId:r.rpId,allowCredentials:r.allowCredentials?.map(e=>({id:N(e.id),type:"public-key",transports:e.transports})),userVerification:r.userVerification}}function Oe(r){let e=r.response;return{id:r.id,rawId:S(r.rawId),type:r.type,response:{clientDataJSON:S(e.clientDataJSON),attestationObject:S(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 Le(r){let e=r.response;return{id:r.id,rawId:S(r.rawId),type:r.type,response:{clientDataJSON:S(e.clientDataJSON),authenticatorData:S(e.authenticatorData),signature:S(e.signature),userHandle:e.userHandle?S(e.userHandle):null},clientExtensionResults:r.getClientExtensionResults()}}function Me(r){return r===!1?{pageViews:!1,sessionStart:!1,appOpen:!1,appBackground:!1}:r===!0||r===void 0?{pageViews:!0,sessionStart:!0,appOpen:!0,appBackground:!0}:{pageViews:r.pageViews??!0,sessionStart:r.sessionStart??!0,appOpen:r.appOpen??!0,appBackground:r.appBackground??!0}}function ce(r,e){if(typeof window>"u")return()=>{};let t=Me(e.autoTrack),s=e.sessionIdleMs??18e5,n=[],i="sendora_session_id",o="sendora_session_last_activity";function a(){return oe(i)??""}function u(l){ae(i,l)}function d(){let l=oe(o);return l&&Number(l)||0}function g(l){ae(o,String(l))}function L(){return`sess_${Date.now().toString(36)}_${Math.random().toString(36).slice(2,10)}`}function f(l){let A=Date.now(),M=d();if(!a()||M&&A-M>s){let F=L();u(F),l&&t.sessionStart&&r.track("session.start",{sessionId:F})}g(A)}if(f(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=()=>{f(!0),r.page()},A=()=>l();window.addEventListener("popstate",A),n.push(()=>window.removeEventListener("popstate",A));let M=window;if(!M.__sendoraHistoryPatched){M.__sendoraHistoryPatched=!0;let F=history.pushState,ue=history.replaceState;history.pushState=function(...j){let B=F.apply(this,j);return window.dispatchEvent(new Event("sendora:locationchange")),B},history.replaceState=function(...j){let B=ue.apply(this,j);return window.dispatchEvent(new Event("sendora:locationchange")),B}}let q=()=>l();window.addEventListener("sendora:locationchange",q),n.push(()=>window.removeEventListener("sendora:locationchange",q))}if(t.appBackground){let l=()=>{f(!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 m=0,y=()=>{let l=Date.now();l-m<5e3||(m=l,f(!0))};return window.addEventListener("click",y,{passive:!0}),window.addEventListener("keydown",y,{passive:!0}),window.addEventListener("scroll",y,{passive:!0}),n.push(()=>{window.removeEventListener("click",y),window.removeEventListener("keydown",y),window.removeEventListener("scroll",y)}),()=>{for(let l of n)try{l()}catch{}}}function oe(r){try{return window.sessionStorage?.getItem(r)??null}catch{return null}}function ae(r,e){try{window.sessionStorage?.setItem(r,e)}catch{}}var x=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:De(i)}),u=a.toJSON(),d=u.endpoint,g=u.keys?.p256dh??"",L=u.keys?.auth??"",f=await Ne(d),m=await this.http.post("/push/tokens",{orgId:this.orgId,platform:"web",token:f,webEndpoint:d,webP256dh:g,webAuth:L,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(!m?.data?.tokenId)throw new Error("[sendoracloud] backend returned no tokenId on subscribe.");return{tokenId:m.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 De(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 Ne(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 Fe="2.9.0",z=class z{constructor(e){this.autoTrackCleanup=null;this.http=new H(e),this.storage=new $,this.consent=new P(e.defaultConsent===!0),this.analytics=new k(e,this.http,this.storage,this.consent),this.flags=new b(e,this.http,this.storage),this.surveys=new E(e,this.http,this.storage),this.messages=new T(e,this.http,this.storage),this.chatbot=new _(e,this.http,this.storage),this.auth=new O(e,this.http,this.storage,this.analytics,this.flags,this.chatbot),this.webPush=new x(this.http,e.orgId),e.autoTrack!==!1&&(this.autoTrackCleanup=ce(this.analytics,e))}static init(e){return te(e.apiKey),new z(e)}static sanitizeHtml(e){return W(e)}static safeUrl(e){return w(e)}track(e,t){this.analytics.track(e,t)}page(e){this.analytics.page(e)}identify(e,t,s){this.analytics.identify(e,t,s),this.flags.setContext({userId:e})}reset(){this.analytics.reset(),this.flags.reset(),this.chatbot.newSession()}async flush(){await this.analytics.flush()}destroy(){this.autoTrackCleanup&&(this.autoTrackCleanup(),this.autoTrackCleanup=null),this.analytics.destroy(),this.flags.destroy(),this.messages.destroy()}};z.version=Fe;var V=z,He=V;0&&(module.exports={Analytics,Auth,AuthError,Chatbot,Consent,EmailAlreadyTakenError,FeatureFlags,InAppMessaging,SendoraCloud,Surveys,ValidationError,WebPush,safeUrl,sanitizeHtml});
|
package/dist/index.d.cts
CHANGED
|
@@ -2,6 +2,10 @@
|
|
|
2
2
|
interface SendoraConfig {
|
|
3
3
|
/** Publishable (pk_...) key. Secret (sk_...) keys are refused — they must never ship in browser bundles. */
|
|
4
4
|
apiKey: string;
|
|
5
|
+
/** Org UUID. Required for `webPush.subscribe()`. Backend derives from
|
|
6
|
+
* apiKey for events, but Web Push needs it explicitly to fetch the
|
|
7
|
+
* per-org VAPID public key. */
|
|
8
|
+
orgId?: string;
|
|
5
9
|
projectId?: string;
|
|
6
10
|
/** Override API base URL. Must be https:// unless it's http://localhost. */
|
|
7
11
|
apiUrl?: string;
|
|
@@ -594,6 +598,63 @@ declare class Auth {
|
|
|
594
598
|
private wipeLocalIdentity;
|
|
595
599
|
}
|
|
596
600
|
|
|
601
|
+
/**
|
|
602
|
+
* Browser Web Push (RFC 8030) helper.
|
|
603
|
+
*
|
|
604
|
+
* One-call wiring:
|
|
605
|
+
* 1. Asks browser for Notification permission.
|
|
606
|
+
* 2. Registers a service worker (caller-supplied path).
|
|
607
|
+
* 3. Fetches the org's VAPID public key from `/orgs/:orgId/push/vapid`.
|
|
608
|
+
* 4. Calls `pushManager.subscribe({ userVisibleOnly: true,
|
|
609
|
+
* applicationServerKey: <urlBase64ToUint8> })`.
|
|
610
|
+
* 5. POSTs the resulting subscription parts (endpoint + p256dh + auth)
|
|
611
|
+
* to `/push/tokens` with `platform="web"` so the backend can target
|
|
612
|
+
* this browser via Web Push.
|
|
613
|
+
*
|
|
614
|
+
* Returns `{ tokenId, subscription }` on success; throws on permission
|
|
615
|
+
* denial / SW registration failure / VAPID unavailable.
|
|
616
|
+
*
|
|
617
|
+
* The service-worker file (e.g. `/sendora-sw.js`) is the customer's
|
|
618
|
+
* responsibility — it must call `self.registration.showNotification()`
|
|
619
|
+
* on `push` events. Sample SW lives in the docs.
|
|
620
|
+
*/
|
|
621
|
+
declare class WebPush {
|
|
622
|
+
private http;
|
|
623
|
+
private orgId;
|
|
624
|
+
constructor(http: HttpClient, orgId?: string);
|
|
625
|
+
/** Set / override the orgId. Helper resolves it from config when omitted. */
|
|
626
|
+
setOrgId(orgId: string): void;
|
|
627
|
+
/**
|
|
628
|
+
* Cheap capability check. Returns false on Safari < 16, in iframes
|
|
629
|
+
* without Permissions-Policy, in private browsing on some browsers,
|
|
630
|
+
* or when the server doesn't expose Push (e.g. http://localhost).
|
|
631
|
+
*/
|
|
632
|
+
static isSupported(): boolean;
|
|
633
|
+
/** Current Notification permission. "default" = not yet decided. */
|
|
634
|
+
static permission(): NotificationPermission;
|
|
635
|
+
/**
|
|
636
|
+
* Subscribe this browser to push.
|
|
637
|
+
*
|
|
638
|
+
* @param opts.serviceWorkerPath Path to the SW file (e.g. "/sendora-sw.js").
|
|
639
|
+
* @param opts.userId Optional — link subscription to this user id.
|
|
640
|
+
* @param opts.scope SW scope; defaults to "/".
|
|
641
|
+
*/
|
|
642
|
+
subscribe(opts: {
|
|
643
|
+
serviceWorkerPath: string;
|
|
644
|
+
userId?: string;
|
|
645
|
+
scope?: string;
|
|
646
|
+
}): Promise<{
|
|
647
|
+
tokenId: string;
|
|
648
|
+
subscription: PushSubscription;
|
|
649
|
+
}>;
|
|
650
|
+
/**
|
|
651
|
+
* Unsubscribe this browser. Cancels the browser-side Push subscription
|
|
652
|
+
* and best-effort marks the Sendora token inactive (HTTP error
|
|
653
|
+
* tolerated — the browser-side cancel is what matters).
|
|
654
|
+
*/
|
|
655
|
+
unsubscribe(scope?: string): Promise<boolean>;
|
|
656
|
+
}
|
|
657
|
+
|
|
597
658
|
/**
|
|
598
659
|
* Return the URL if it is http/https; otherwise undefined.
|
|
599
660
|
* Rejects javascript:, data:, vbscript:, and other script-bearing schemes.
|
|
@@ -645,6 +706,7 @@ declare class SendoraCloud {
|
|
|
645
706
|
readonly chatbot: Chatbot;
|
|
646
707
|
readonly consent: Consent;
|
|
647
708
|
readonly auth: Auth;
|
|
709
|
+
readonly webPush: WebPush;
|
|
648
710
|
private http;
|
|
649
711
|
private storage;
|
|
650
712
|
private autoTrackCleanup;
|
|
@@ -656,7 +718,7 @@ declare class SendoraCloud {
|
|
|
656
718
|
/** Return the URL if it's http(s); undefined otherwise. */
|
|
657
719
|
static safeUrl(u?: string): string | undefined;
|
|
658
720
|
/** SDK version. */
|
|
659
|
-
static readonly version = "2.
|
|
721
|
+
static readonly version = "2.9.0";
|
|
660
722
|
track(eventType: string, properties?: Record<string, unknown>): void;
|
|
661
723
|
page(properties?: Record<string, unknown>): void;
|
|
662
724
|
identify(userId: string, traits?: UserTraits, opts?: IdentifyOptions): void;
|
|
@@ -665,4 +727,4 @@ declare class SendoraCloud {
|
|
|
665
727
|
destroy(): void;
|
|
666
728
|
}
|
|
667
729
|
|
|
668
|
-
export { Analytics, Auth, AuthError, type AuthTokens, type AuthUser, Chatbot, Consent, EmailAlreadyTakenError, FeatureFlags, type FlagValue, type IdentifyOptions, type InAppMessage, InAppMessaging, SendoraCloud, type SendoraConfig, type SurveyConfig, type SurveyQuestion, Surveys, type TrackEvent, type UserTraits, ValidationError, SendoraCloud as default, safeUrl, sanitizeHtml };
|
|
730
|
+
export { Analytics, Auth, AuthError, type AuthTokens, type AuthUser, Chatbot, Consent, EmailAlreadyTakenError, FeatureFlags, type FlagValue, type IdentifyOptions, type InAppMessage, InAppMessaging, SendoraCloud, type SendoraConfig, type SurveyConfig, type SurveyQuestion, Surveys, type TrackEvent, type UserTraits, ValidationError, WebPush, SendoraCloud as default, safeUrl, sanitizeHtml };
|
package/dist/index.d.ts
CHANGED
|
@@ -2,6 +2,10 @@
|
|
|
2
2
|
interface SendoraConfig {
|
|
3
3
|
/** Publishable (pk_...) key. Secret (sk_...) keys are refused — they must never ship in browser bundles. */
|
|
4
4
|
apiKey: string;
|
|
5
|
+
/** Org UUID. Required for `webPush.subscribe()`. Backend derives from
|
|
6
|
+
* apiKey for events, but Web Push needs it explicitly to fetch the
|
|
7
|
+
* per-org VAPID public key. */
|
|
8
|
+
orgId?: string;
|
|
5
9
|
projectId?: string;
|
|
6
10
|
/** Override API base URL. Must be https:// unless it's http://localhost. */
|
|
7
11
|
apiUrl?: string;
|
|
@@ -594,6 +598,63 @@ declare class Auth {
|
|
|
594
598
|
private wipeLocalIdentity;
|
|
595
599
|
}
|
|
596
600
|
|
|
601
|
+
/**
|
|
602
|
+
* Browser Web Push (RFC 8030) helper.
|
|
603
|
+
*
|
|
604
|
+
* One-call wiring:
|
|
605
|
+
* 1. Asks browser for Notification permission.
|
|
606
|
+
* 2. Registers a service worker (caller-supplied path).
|
|
607
|
+
* 3. Fetches the org's VAPID public key from `/orgs/:orgId/push/vapid`.
|
|
608
|
+
* 4. Calls `pushManager.subscribe({ userVisibleOnly: true,
|
|
609
|
+
* applicationServerKey: <urlBase64ToUint8> })`.
|
|
610
|
+
* 5. POSTs the resulting subscription parts (endpoint + p256dh + auth)
|
|
611
|
+
* to `/push/tokens` with `platform="web"` so the backend can target
|
|
612
|
+
* this browser via Web Push.
|
|
613
|
+
*
|
|
614
|
+
* Returns `{ tokenId, subscription }` on success; throws on permission
|
|
615
|
+
* denial / SW registration failure / VAPID unavailable.
|
|
616
|
+
*
|
|
617
|
+
* The service-worker file (e.g. `/sendora-sw.js`) is the customer's
|
|
618
|
+
* responsibility — it must call `self.registration.showNotification()`
|
|
619
|
+
* on `push` events. Sample SW lives in the docs.
|
|
620
|
+
*/
|
|
621
|
+
declare class WebPush {
|
|
622
|
+
private http;
|
|
623
|
+
private orgId;
|
|
624
|
+
constructor(http: HttpClient, orgId?: string);
|
|
625
|
+
/** Set / override the orgId. Helper resolves it from config when omitted. */
|
|
626
|
+
setOrgId(orgId: string): void;
|
|
627
|
+
/**
|
|
628
|
+
* Cheap capability check. Returns false on Safari < 16, in iframes
|
|
629
|
+
* without Permissions-Policy, in private browsing on some browsers,
|
|
630
|
+
* or when the server doesn't expose Push (e.g. http://localhost).
|
|
631
|
+
*/
|
|
632
|
+
static isSupported(): boolean;
|
|
633
|
+
/** Current Notification permission. "default" = not yet decided. */
|
|
634
|
+
static permission(): NotificationPermission;
|
|
635
|
+
/**
|
|
636
|
+
* Subscribe this browser to push.
|
|
637
|
+
*
|
|
638
|
+
* @param opts.serviceWorkerPath Path to the SW file (e.g. "/sendora-sw.js").
|
|
639
|
+
* @param opts.userId Optional — link subscription to this user id.
|
|
640
|
+
* @param opts.scope SW scope; defaults to "/".
|
|
641
|
+
*/
|
|
642
|
+
subscribe(opts: {
|
|
643
|
+
serviceWorkerPath: string;
|
|
644
|
+
userId?: string;
|
|
645
|
+
scope?: string;
|
|
646
|
+
}): Promise<{
|
|
647
|
+
tokenId: string;
|
|
648
|
+
subscription: PushSubscription;
|
|
649
|
+
}>;
|
|
650
|
+
/**
|
|
651
|
+
* Unsubscribe this browser. Cancels the browser-side Push subscription
|
|
652
|
+
* and best-effort marks the Sendora token inactive (HTTP error
|
|
653
|
+
* tolerated — the browser-side cancel is what matters).
|
|
654
|
+
*/
|
|
655
|
+
unsubscribe(scope?: string): Promise<boolean>;
|
|
656
|
+
}
|
|
657
|
+
|
|
597
658
|
/**
|
|
598
659
|
* Return the URL if it is http/https; otherwise undefined.
|
|
599
660
|
* Rejects javascript:, data:, vbscript:, and other script-bearing schemes.
|
|
@@ -645,6 +706,7 @@ declare class SendoraCloud {
|
|
|
645
706
|
readonly chatbot: Chatbot;
|
|
646
707
|
readonly consent: Consent;
|
|
647
708
|
readonly auth: Auth;
|
|
709
|
+
readonly webPush: WebPush;
|
|
648
710
|
private http;
|
|
649
711
|
private storage;
|
|
650
712
|
private autoTrackCleanup;
|
|
@@ -656,7 +718,7 @@ declare class SendoraCloud {
|
|
|
656
718
|
/** Return the URL if it's http(s); undefined otherwise. */
|
|
657
719
|
static safeUrl(u?: string): string | undefined;
|
|
658
720
|
/** SDK version. */
|
|
659
|
-
static readonly version = "2.
|
|
721
|
+
static readonly version = "2.9.0";
|
|
660
722
|
track(eventType: string, properties?: Record<string, unknown>): void;
|
|
661
723
|
page(properties?: Record<string, unknown>): void;
|
|
662
724
|
identify(userId: string, traits?: UserTraits, opts?: IdentifyOptions): void;
|
|
@@ -665,4 +727,4 @@ declare class SendoraCloud {
|
|
|
665
727
|
destroy(): void;
|
|
666
728
|
}
|
|
667
729
|
|
|
668
|
-
export { Analytics, Auth, AuthError, type AuthTokens, type AuthUser, Chatbot, Consent, EmailAlreadyTakenError, FeatureFlags, type FlagValue, type IdentifyOptions, type InAppMessage, InAppMessaging, SendoraCloud, type SendoraConfig, type SurveyConfig, type SurveyQuestion, Surveys, type TrackEvent, type UserTraits, ValidationError, SendoraCloud as default, safeUrl, sanitizeHtml };
|
|
730
|
+
export { Analytics, Auth, AuthError, type AuthTokens, type AuthUser, Chatbot, Consent, EmailAlreadyTakenError, FeatureFlags, type FlagValue, type IdentifyOptions, type InAppMessage, InAppMessaging, SendoraCloud, type SendoraConfig, type SurveyConfig, type SurveyQuestion, Surveys, type TrackEvent, type UserTraits, ValidationError, WebPush, SendoraCloud as default, safeUrl, sanitizeHtml };
|
package/dist/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
var Y=/^[a-zA-Z][a-zA-Z0-9._:-]{0,127}$/;var ce=new Set(["__proto__","constructor","prototype"]),d=class extends Error{constructor(e){super(`[SendoraCloud] ${e}`),this.name="SendoraValidationError"}};function Q(r){if(typeof r!="string"||!Y.test(r))throw new d(`Invalid event name: must match ${Y}, got "${String(r)}".`)}function X(r){if(r==null)return;if(typeof r!="object"||Array.isArray(r))throw new d("Properties must be a plain object, not an array or primitive.");let e=G(r);if(e>5)throw new d(`Properties nested too deeply (${e} levels, max 5).`);J(r);let t;try{t=JSON.stringify(r).length}catch{throw new d("Properties contain a circular reference or unserializable value.")}if(t>32768)throw new d(`Properties too large (${t} bytes, max 32768).`)}function G(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=G(r[s],e+1);n>t&&(t=n)}return t}function J(r){if(!(r==null||typeof r!="object"))for(let e of Object.keys(r)){if(ce.has(e))throw new d(`Forbidden property key "${e}" (prototype pollution guard).`);J(r[e])}}var ue=/^pk_[A-Za-z0-9_-]{16,}$/;function Z(r){if(!r||typeof r!="string")throw new d("apiKey is required.");if(r.startsWith("sk_")||r.startsWith("sendora_secret_"))throw new d("Secret keys must NEVER be used in browser SDK. Use a publishable key (pk_...).");if(!ue.test(r))throw new d("Invalid publishable key format. Expected pk_... (17+ chars).")}function ee(r){let e=r.replace(/\/$/,"");if(!(e.startsWith("http://localhost")||e.startsWith("http://127.0.0.1"))&&!e.startsWith("https://"))throw new d("apiUrl must be https:// (only http://localhost is allowed for dev).")}var le="https://api.sendoracloud.com",he=1e4,F=class{constructor(e){let t=(e.apiUrl??le).replace(/\/$/,"");ee(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)}delete(e,t){return this.request("DELETE",e,void 0,t)}async request(e,t,s,n){let o=`${this.baseUrl}/api/v1${t}`,i=typeof AbortController<"u"?new AbortController:null,a=i?setTimeout(()=>i.abort(),he):null;try{let u=await fetch(o,{method:e,headers:{"Content-Type":"application/json","X-API-Key":this.apiKey,...n??{}},body:s?JSON.stringify(s):void 0,signal:i?i.signal:void 0});a&&clearTimeout(a);let h;try{h=await u.json()}catch{h={success:!1,error:{code:"PARSE_ERROR",message:`HTTP ${u.status}`}}}if(this.debug&&console.debug(`[SendoraCloud] ${e} ${t} \u2192 ${u.status}`),!u.ok)if(t.startsWith("/auth-service/")&&!this.debug)console.warn(`[SendoraCloud] ${e} ${t} \u2192 HTTP ${u.status}`);else{let $=h?.error?.code??"HTTP_ERROR",S=h?.error?.message??`HTTP ${u.status}`,E=h?.error?.details?.fieldErrors,g=E?` (${Object.entries(E).map(([l,y])=>`${l}: ${(y??[]).join(", ")}`).join("; ")})`:"";console.warn(`[SendoraCloud] ${e} ${t} \u2192 HTTP ${u.status} \u2014 ${$}: ${S}${g}`)}return h}catch(u){a&&clearTimeout(a);let h=u instanceof Error&&u.name==="AbortError"?"timeout":"error";return console.warn(`[SendoraCloud] ${e} ${t} network ${h}`),{success:!1,error:{code:"NETWORK_ERROR",message:h}}}}};var N="sendora_",H=class{constructor(){this.memoryStore=new Map;this.useLocalStorage=this.checkLocalStorage()}get(e){let t=N+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=N+e;if(this.useLocalStorage)try{localStorage.setItem(s,t);return}catch{}this.memoryStore.set(s,t)}remove(e){let t=N+e;if(this.useLocalStorage)try{localStorage.removeItem(t)}catch{}this.memoryStore.delete(t)}clearPrefix(e){let t=N+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 A(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 te="2.8.0",se=500,de=10,pe=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 o=this.storage.get("anonymous_id");if(o?this.anonymousId=o:(this.anonymousId=A("anon_"),this.storage.set("anonymous_id",this.anonymousId)),this.persistUserId){let i=this.storage.get("user_id");i&&(this.userId=i)}this.timer=setInterval(()=>this.flush(),this.flushInterval),typeof window<"u"&&window.addEventListener("beforeunload",()=>this.flush()),this.consent.subscribe(i=>{i&&this.flush()})}track(e,t){Q(e),t&&X(t),this.queue.length>=se&&this.queue.shift(),this.queue.push({eventType:e,properties:t??{},userId:this.userId??void 0,anonymousId:this.anonymousId,timestamp:new Date().toISOString(),identityToken:this.identityToken??void 0}),this.queue.length>=this.maxQueueSize&&this.flush()}page(e){if(typeof window>"u")return;let t=this.stripQueryParams?window.location.origin+window.location.pathname:window.location.href;this.track("page.viewed",{url:t,path:window.location.pathname,title:document.title,referrer:document.referrer||void 0,...e})}identify(e,t,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=A("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(i=>({projectId:this.projectId,module:"custom",eventType:i.eventType,userId:i.userId,anonymousId:i.anonymousId,identityToken:i.identityToken,timestamp:i.timestamp??new Date().toISOString(),properties:i.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>de)return;let n=Math.min(pe,1e3*2**this.consecutiveFailures);this.nextFlushAfter=Date.now()+n;let o=Math.max(0,se-this.queue.length);o>0&&this.queue.unshift(...e.slice(0,o))}destroy(){this.timer&&clearInterval(this.timer),this.timer=null,this.flush()}getContext(){return typeof window>"u"?{sdk:{name:"@sendoracloud/sdk-web",version:te}}:{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:te}}}};var b=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 o=JSON.parse(n);if(Array.isArray(o))for(let i of o)q(i)&&this.cache.set(i.key,i)}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 ge=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||!fe(s.data)?null:(this.activeSurvey=s.data,this.storage.set(t,"1"),this.onSurveyDisplay&&this.onSurveyDisplay(s.data),s.data)}async submitResponse(e,t,s=!0){let n=0;try{n=JSON.stringify(t).length}catch{return!1}if(n>ge)return!1;let o=await this.http.post("/surveys/responses",{surveyId:e,answers:t,completed:s});return o.success&&(this.activeSurvey=null),o.success}dismiss(){this.activeSurvey=null}getActiveSurvey(){return this.activeSurvey}};function fe(r){if(r==null||typeof r!="object")return!1;let e=r;return typeof e.id!="string"||typeof e.name!="string"||!Array.isArray(e.questions)?!1:e.questions.every(me)}function me(r){if(r==null||typeof r!="object")return!1;let e=r;return typeof e.id=="string"&&typeof e.text=="string"}function w(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 ye=new Set(["b","i","u","em","strong","a","p","br","span","ul","ol","li","h1","h2","h3","h4"]),we={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?(re(t),t.innerHTML):""}function re(r){let e=Array.from(r.children);for(let t of e){let s=t.tagName.toLowerCase();if(!ye.has(s)){let o=document.createTextNode(t.textContent??"");t.replaceWith(o);continue}let n=we[s]??new Set;for(let o of Array.from(t.attributes)){if(!n.has(o.name.toLowerCase())){t.removeAttribute(o.name);continue}if(o.name.toLowerCase()==="href"){let i=w(o.value);i?t.setAttribute("href",i):t.removeAttribute(o.name)}}s==="a"&&(t.setAttribute("rel","noopener noreferrer"),t.setAttribute("target","_blank")),re(t)}}var P=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(ve);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 ve(r){return{...r,content:{...r.content,ctaUrl:w(r.content.ctaUrl),imageUrl:w(r.content.imageUrl)}}}var Se=4e3,C=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??A("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>Se)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 o={role:"assistant",content:n.data.reply,timestamp:new Date().toISOString()};return this.messages.push(o),this.onMessage&&this.onMessage(o),o}return null}getMessages(){return[...this.messages]}newSession(){this.messages=[],this.sessionId=A("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 I="auth_access_token",k="auth_access_expires",f="auth_refresh_token",O="auth_user",Ae=3e4;function p(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 L=class extends Error{constructor(e="An account with this email already exists"){super(e),this.name="EmailAlreadyTakenError"}},c=class extends Error{constructor(e,t){super(t),this.name="AuthError",this.code=e}},M=class{constructor(e,t,s,n,o,i){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=o,this.chatbot=i;let a=this.storage.get(O),u=this.storage.get(I),h=this.storage.get(k);if(a&&u)try{let v=JSON.parse(a);if(typeof v.id!="string"||v.id.length===0)throw new Error("invalid cache");this.user=v,this.accessToken=u,this.accessExpiresAt=h?Number(h):0,this.analytics.identify(this.user.id)}catch{this.storage.remove(O),this.storage.remove(I),this.storage.remove(k),this.storage.remove(f)}}getCurrentUser(){return this.user}async getAccessToken(){return this.accessToken?this.accessExpiresAt&&Date.now()<this.accessExpiresAt-Ae?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||!p(t.data))throw new c(t.error?.code??"AUTH_ERROR",t.error?.message??"Anonymous signup failed");return this.persist(t.data),t.data.user})}signUp(e,t,s){return this.serialize(async()=>{let n=this.storage.get(f);if(n){let i=await this.http.post("/auth-service/upgrade",{refreshToken:n,email:e,password:t,name:s?.name});if(i.success&&p(i.data))return this.persist(i.data),i.data.user;let a=i.error?.code??"",u=i.error?.status;if(a==="CONFLICT"||a==="EMAIL_ALREADY_TAKEN"||u===409)throw new L(i.error?.message);if(a==="FORBIDDEN_NON_ANONYMOUS"||a==="INVALID_REFRESH"||a==="UNAUTHORIZED"||a==="REFRESH_EXPIRED")await this.analytics.flush(),this.wipeLocalIdentity();else throw new c(a||"AUTH_ERROR",i.error?.message??"Upgrade failed")}let o=await this.http.post("/auth-service/signup",{email:e,password:t,name:s?.name,metadata:s?.metadata});if(!o.success||!p(o.data)){let i=o.error?.code??"";throw i==="CONFLICT"?new L(o.error?.message):new c(i||"AUTH_ERROR",o.error?.message??"Signup failed")}return this.persist(o.data),o.data.user})}signIn(e,t){return this.serialize(async()=>{this.user!==null&&(await this.analytics.flush(),this.wipeLocalIdentity());let n=await this.http.post("/auth-service/login",{email:e,password:t});if(!n.success||!n.data)throw new c(n.error?.code??"AUTH_ERROR",n.error?.message??"Login failed");let o=n.data;if(o.mfaRequired===!0&&typeof o.mfaChallengeToken=="string"){let i=o.user;return{mfaRequired:!0,mfaChallengeToken:o.mfaChallengeToken,userId:i?.id??""}}if(!p(n.data))throw new c(n.error?.code??"AUTH_ERROR",n.error?.message??"Login failed");return this.persist(n.data),n.data.user})}loginSocial(e){return this.serialize(async()=>{this.user!==null&&(await this.analytics.flush(),this.wipeLocalIdentity());let s=await this.http.post("/auth-service/login/social",e);if(!s.success||!p(s.data))throw new c(s.error?.code??"SOCIAL_LOGIN_FAILED",s.error?.message??"Social login failed");return this.persist(s.data),s.data.user})}signInWithGoogle(e,t){return this.loginSocial({provider:"google",code:e,redirectUri:t})}signInWithGitHub(e,t){return this.loginSocial({provider:"github",code:e,redirectUri:t})}signInWithApple(e){return this.loginSocial({provider:"apple",...e})}signInWithMicrosoft(e,t){return this.loginSocial({provider:"microsoft",code:e,redirectUri:t})}signInWithLinkedIn(e,t){return this.loginSocial({provider:"linkedin",code:e,redirectUri:t})}signInWithFacebook(e,t){return this.loginSocial({provider:"facebook",code:e,redirectUri:t})}signInWithDiscord(e,t){return this.loginSocial({provider:"discord",code:e,redirectUri:t})}challengeMfa(e,t){return this.serialize(async()=>{let s=await this.http.post("/auth-service/mfa/challenge",{challengeToken:e,code:t});if(!s.success||!p(s.data))throw new c(s.error?.code??"MFA_CHALLENGE_FAILED",s.error?.message??"Invalid MFA code");return this.persist(s.data),s.data.user})}async sendMagicLink(e){let t=await this.http.post("/auth-service/magic-link/request",{email:e});if(!t.success)throw new c(t.error?.code??"MAGIC_LINK_REQUEST_FAILED",t.error?.message??"Failed to send link")}async sendEmailOtp(e){let t=await this.http.post("/auth-service/email-otp/request",{email:e});if(!t.success)throw new c(t.error?.code??"EMAIL_OTP_REQUEST_FAILED",t.error?.message??"Failed to send code")}verifyEmailOtp(e,t){return this.serialize(async()=>{this.user!==null&&(await this.analytics.flush(),this.wipeLocalIdentity());let n=await this.http.post("/auth-service/email-otp/verify",{email:e,code:t});if(!n.success||!p(n.data))throw new c(n.error?.code??"EMAIL_OTP_INVALID",n.error?.message??"Wrong or expired code");return this.persist(n.data),n.data.user})}async requestPasswordReset(e){let t=await this.http.post("/auth-service/password/forgot",{email:e});if(!t.success)throw new c(t.error?.code??"PASSWORD_RESET_REQUEST_FAILED",t.error?.message??"Failed to send reset email")}async resetPassword(e,t){let s=await this.http.post("/auth-service/password/reset",{token:e,newPassword:t});if(!s.success)throw new c(s.error?.code??"PASSWORD_RESET_FAILED",s.error?.message??"Reset failed")}async verifyEmail(e){let t=await this.http.post("/auth-service/email/verify",{token:e});if(!t.success)throw new c(t.error?.code??"EMAIL_VERIFICATION_FAILED",t.error?.message??"Verification failed")}async sendVerificationEmail(){let e=await this.getAccessToken();if(!e)throw new c("UNAUTHORIZED","Sign in before requesting a verification email");let t=await this.http.post("/auth-service/email/verify/resend",{},{Authorization:`Bearer ${e}`});if(!t.success)throw new c(t.error?.code??"VERIFICATION_RESEND_FAILED",t.error?.message??"Failed to send verification email")}verifyMagicLink(e){return this.serialize(async()=>{this.user!==null&&(await this.analytics.flush(),this.wipeLocalIdentity());let s=await this.http.post("/auth-service/magic-link/verify",{token:e});if(!s.success||!p(s.data))throw new c(s.error?.code??"MAGIC_LINK_INVALID",s.error?.message??"Invalid or expired magic link");return this.persist(s.data),s.data.user})}async enrollMfa(){let e=await this.http.post("/auth-service/mfa/enroll/start",{},this.bearerHeaders());if(!e.success||!e.data)throw new c(e.error?.code??"MFA_ENROLL_FAILED",e.error?.message??"MFA enrollment failed");return e.data}async confirmMfa(e){let t=await this.http.post("/auth-service/mfa/enroll/confirm",{code:e},this.bearerHeaders());return!!(t.success&&t.data?.confirmed)}async disableMfa(){await this.http.post("/auth-service/mfa/disable",{},this.bearerHeaders())}async registerPasskey(e){if(typeof navigator>"u"||!navigator.credentials)throw new c("PASSKEY_UNSUPPORTED","WebAuthn not supported in this environment");let t=await this.http.post("/auth-service/passkeys/register/start",{},this.bearerHeaders());if(!t.success||!t.data)throw new c(t.error?.code??"PASSKEY_START_FAILED",t.error?.message??"Passkey start failed");let s=Ie(t.data),n;try{let a=await navigator.credentials.create({publicKey:s});if(!a)throw new Error("create returned null");n=a}catch(a){throw new c("PASSKEY_USER_CANCELLED",a instanceof Error?a.message:"User cancelled passkey registration")}let o=Ee(n),i=await this.http.post("/auth-service/passkeys/register/finish",{response:o,name:e},this.bearerHeaders());if(!i.success||!i.data)throw new c(i.error?.code??"PASSKEY_FINISH_FAILED",i.error?.message??"Passkey verification failed");return i.data}signInWithPasskey(e){return this.serialize(async()=>{if(typeof navigator>"u"||!navigator.credentials)throw new c("PASSKEY_UNSUPPORTED","WebAuthn not supported in this environment");this.user!==null&&(await this.analytics.flush(),this.wipeLocalIdentity());let s=await this.http.post("/auth-service/passkeys/authenticate/start",{userId:e??null});if(!s.success||!s.data)throw new c(s.error?.code??"PASSKEY_START_FAILED",s.error?.message??"Passkey start failed");let n=ke(s.data),o;try{let u=await navigator.credentials.get({publicKey:n});if(!u)throw new Error("get returned null");o=u}catch(u){throw new c("PASSKEY_USER_CANCELLED",u instanceof Error?u.message:"User cancelled passkey signin")}let i=_e(o),a=await this.http.post("/auth-service/passkeys/authenticate/finish",{response:i,userId:e??null});if(!a.success||!p(a.data))throw new c(a.error?.code??"PASSKEY_AUTH_FAILED",a.error?.message??"Passkey signin failed");return this.persist(a.data),a.data.user})}async listPasskeys(){let e=await this.http.get("/auth-service/passkeys",this.bearerHeaders());return!e.success||!Array.isArray(e.data)?[]:e.data}async deletePasskey(e){await this.http.delete(`/auth-service/passkeys/${e}`,this.bearerHeaders())}async listMySessions(){let e=await this.http.get("/auth-service/sessions/me",this.bearerHeaders());return!e.success||!Array.isArray(e.data)?[]:e.data}async revokeSession(e){await this.http.delete(`/auth-service/sessions/me/${e}`,this.bearerHeaders())}async revokeAllSessions(){await this.http.delete("/auth-service/sessions/me",this.bearerHeaders())}async startSso(e){let t=await this.http.post("/auth-service/sso/oidc/start",{returnTo:e});if(!t.success||!t.data)throw new c(t.error?.code??"SSO_START_FAILED",t.error?.message??"SSO start failed");return t.data}async signInWithSso(e){let t=e??(typeof window<"u"?window.location.origin:"");if(!t)throw new c("SSO_NO_RETURN_TO","returnTo required outside browser context");let s=await this.startSso(t);typeof window<"u"&&(window.location.href=s.authorizationUrl)}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 c("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 o=await this.http.post("/auth-service/token/refresh",{refreshToken:n});if(!o.success||!o.data)throw new c(o.error?.code??"SSO_EXCHANGE_FAILED",o.error?.message??"Could not exchange SSO token");let i=o.data;this.accessToken=i.accessToken,this.accessExpiresAt=Date.now()+i.expiresIn*1e3,this.storage.set(I,i.accessToken),this.storage.set(k,String(this.accessExpiresAt)),this.storage.set(f,i.refreshToken);let a=Te(i.accessToken),u={id:a?.sub??"",email:a?.email??null,emailVerified:a?.email_verified??!0,name:a?.name??null,isAnonymous:!1};return this.user=u,this.storage.set(O,JSON.stringify(u)),this.analytics.identify(u.id),this.flags.setContext({userId:u.id}),u})}bearerHeaders(){if(!this.accessToken)throw new c("NOT_SIGNED_IN","Not signed in");return{Authorization:`Bearer ${this.accessToken}`}}signOut(){return this.serialize(async()=>{let e=this.storage.get(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||!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(I,s.accessToken),this.storage.set(k,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(O,JSON.stringify(e.user)),this.storage.set(I,e.tokens.accessToken),this.storage.set(k,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(O),this.storage.remove(I),this.storage.remove(k),this.storage.remove(f),this.analytics.reset(),this.analytics.dropQueue(),this.flags.reset(),this.chatbot.newSession()}};function x(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),o=new Uint8Array(n);for(let i=0;i<s.length;i++)o[i]=s.charCodeAt(i);return n}function m(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 Ie(r){return{challenge:x(r.challenge),rp:r.rp,user:{id:x(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:x(e.id),type:"public-key",transports:e.transports})),authenticatorSelection:r.authenticatorSelection}}function ke(r){return{challenge:x(r.challenge),timeout:r.timeout,rpId:r.rpId,allowCredentials:r.allowCredentials?.map(e=>({id:x(e.id),type:"public-key",transports:e.transports})),userVerification:r.userVerification}}function Ee(r){let e=r.response;return{id:r.id,rawId:m(r.rawId),type:r.type,response:{clientDataJSON:m(e.clientDataJSON),attestationObject:m(e.attestationObject),transports:typeof e.getTransports=="function"?e.getTransports():void 0},clientExtensionResults:r.getClientExtensionResults()}}function Te(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 _e(r){let e=r.response;return{id:r.id,rawId:m(r.rawId),type:r.type,response:{clientDataJSON:m(e.clientDataJSON),authenticatorData:m(e.authenticatorData),signature:m(e.signature),userHandle:e.userHandle?m(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 oe(r,e){if(typeof window>"u")return()=>{};let t=be(e.autoTrack),s=e.sessionIdleMs??18e5,n=[],o="sendora_session_id",i="sendora_session_last_activity";function a(){return ne(o)??""}function u(l){ie(o,l)}function h(){let l=ne(i);return l&&Number(l)||0}function v(l){ie(i,String(l))}function $(){return`sess_${Date.now().toString(36)}_${Math.random().toString(36).slice(2,10)}`}function S(l){let y=Date.now(),T=h();if(!a()||T&&y-T>s){let D=$();u(D),l&&t.sessionStart&&r.track("session.start",{sessionId:D})}v(y)}if(S(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=()=>{S(!0),r.page()},y=()=>l();window.addEventListener("popstate",y),n.push(()=>window.removeEventListener("popstate",y));let T=window;if(!T.__sendoraHistoryPatched){T.__sendoraHistoryPatched=!0;let D=history.pushState,ae=history.replaceState;history.pushState=function(...z){let W=D.apply(this,z);return window.dispatchEvent(new Event("sendora:locationchange")),W},history.replaceState=function(...z){let W=ae.apply(this,z);return window.dispatchEvent(new Event("sendora:locationchange")),W}}let V=()=>l();window.addEventListener("sendora:locationchange",V),n.push(()=>window.removeEventListener("sendora:locationchange",V))}if(t.appBackground){let l=()=>{S(!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 E=0,g=()=>{let l=Date.now();l-E<5e3||(E=l,S(!0))};return window.addEventListener("click",g,{passive:!0}),window.addEventListener("keydown",g,{passive:!0}),window.addEventListener("scroll",g,{passive:!0}),n.push(()=>{window.removeEventListener("click",g),window.removeEventListener("keydown",g),window.removeEventListener("scroll",g)}),()=>{for(let l of n)try{l()}catch{}}}function ne(r){try{return window.sessionStorage?.getItem(r)??null}catch{return null}}function ie(r,e){try{window.sessionStorage?.setItem(r,e)}catch{}}var Re="2.8.0",K=class K{constructor(e){this.autoTrackCleanup=null;this.http=new F(e),this.storage=new H,this.consent=new U(e.defaultConsent===!0),this.analytics=new _(e,this.http,this.storage,this.consent),this.flags=new b(e,this.http,this.storage),this.surveys=new R(e,this.http,this.storage),this.messages=new P(e,this.http,this.storage),this.chatbot=new C(e,this.http,this.storage),this.auth=new M(e,this.http,this.storage,this.analytics,this.flags,this.chatbot),e.autoTrack!==!1&&(this.autoTrackCleanup=oe(this.analytics,e))}static init(e){return Z(e.apiKey),new K(e)}static sanitizeHtml(e){return j(e)}static safeUrl(e){return w(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()}};K.version=Re;var B=K,it=B;export{_ as Analytics,M as Auth,c as AuthError,C as Chatbot,U as Consent,L as EmailAlreadyTakenError,b as FeatureFlags,P as InAppMessaging,B as SendoraCloud,R as Surveys,d as ValidationError,it as default,w as safeUrl,j as sanitizeHtml};
|
|
1
|
+
var Q=/^[a-zA-Z][a-zA-Z0-9._:-]{0,127}$/;var ue=new Set(["__proto__","constructor","prototype"]),h=class extends Error{constructor(e){super(`[SendoraCloud] ${e}`),this.name="SendoraValidationError"}};function X(r){if(typeof r!="string"||!Q.test(r))throw new h(`Invalid event name: must match ${Q}, got "${String(r)}".`)}function J(r){if(r==null)return;if(typeof r!="object"||Array.isArray(r))throw new h("Properties must be a plain object, not an array or primitive.");let e=G(r);if(e>5)throw new h(`Properties nested too deeply (${e} levels, max 5).`);Z(r);let t;try{t=JSON.stringify(r).length}catch{throw new h("Properties contain a circular reference or unserializable value.")}if(t>32768)throw new h(`Properties too large (${t} bytes, max 32768).`)}function G(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=G(r[s],e+1);n>t&&(t=n)}return t}function Z(r){if(!(r==null||typeof r!="object"))for(let e of Object.keys(r)){if(ue.has(e))throw new h(`Forbidden property key "${e}" (prototype pollution guard).`);Z(r[e])}}var le=/^pk_[A-Za-z0-9_-]{16,}$/;function ee(r){if(!r||typeof r!="string")throw new h("apiKey is required.");if(r.startsWith("sk_")||r.startsWith("sendora_secret_"))throw new h("Secret keys must NEVER be used in browser SDK. Use a publishable key (pk_...).");if(!le.test(r))throw new h("Invalid publishable key format. Expected pk_... (17+ chars).")}function te(r){let e=r.replace(/\/$/,"");if(!(e.startsWith("http://localhost")||e.startsWith("http://127.0.0.1"))&&!e.startsWith("https://"))throw new h("apiUrl must be https:// (only http://localhost is allowed for dev).")}var de="https://api.sendoracloud.com",he=1e4,H=class{constructor(e){let t=(e.apiUrl??de).replace(/\/$/,"");te(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)}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(),he):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 E=d?.error?.code??"HTTP_ERROR",f=d?.error?.message??`HTTP ${c.status}`,m=d?.error?.details?.fieldErrors,y=m?` (${Object.entries(m).map(([l,S])=>`${l}: ${(S??[]).join(", ")}`).join("; ")})`:"";console.warn(`[SendoraCloud] ${e} ${t} \u2192 HTTP ${c.status} \u2014 ${E}: ${f}${y}`)}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 K="sendora_",$=class{constructor(){this.memoryStore=new Map;this.useLocalStorage=this.checkLocalStorage()}get(e){let t=K+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=K+e;if(this.useLocalStorage)try{localStorage.setItem(s,t);return}catch{}this.memoryStore.set(s,t)}remove(e){let t=K+e;if(this.useLocalStorage)try{localStorage.removeItem(t)}catch{}this.memoryStore.delete(t)}clearPrefix(e){let t=K+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 I(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 se="2.9.0",re=500,pe=10,ge=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=I("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){X(e),t&&J(t),this.queue.length>=re&&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=I("anon_"),this.storage.set("anonymous_id",this.anonymousId),this.storage.clearPrefix("iam_shown_"),this.storage.clearPrefix("survey_shown_")}dropQueue(){this.queue=[]}async flush(){if(this.queue.length===0||!this.consent.isGranted()||Date.now()<this.nextFlushAfter)return;let e=this.queue.splice(0,100),t=e.map(o=>({projectId:this.projectId,module:"custom",eventType:o.eventType,userId:o.userId,anonymousId:o.anonymousId,identityToken:o.identityToken,timestamp:o.timestamp??new Date().toISOString(),properties:o.properties??{},context:this.getContext(),consent:["analytics"]}));if((await this.http.post("/events/batch",{events:t})).success){this.consecutiveFailures=0;return}if(this.consecutiveFailures++,this.consecutiveFailures>pe)return;let n=Math.min(ge,1e3*2**this.consecutiveFailures);this.nextFlushAfter=Date.now()+n;let i=Math.max(0,re-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:se}}:{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:se}}}};var P=class{constructor(e,t,s){this.cache=new Map;this.refreshInterval=null;this.http=t,this.storage=s;let n=this.storage.get("flags");if(n)try{let i=JSON.parse(n);if(Array.isArray(i))for(let o of i)j(o)&&this.cache.set(o.key,o)}catch{}}setContext(e){this.userId=e.userId,this.entityId=e.entityId,this.environment=e.environment}async getFlag(e){if(typeof e!="string"||e.length===0||e.length>128)return null;let t=this.cache.get(e);if(t)return t.value;let s=await this.http.post("/feature-flags/evaluate",{key:e,userId:this.userId,entityId:this.entityId,environment:this.environment});return s.success&&s.data&&j(s.data)?(this.cache.set(e,s.data),this.persistCache(),s.data.value):null}async isEnabled(e,t=!1){let s=await this.getFlag(e);return typeof s=="boolean"?s:t}async refreshAll(){let e=await this.http.post("/feature-flags/evaluate-all",{userId:this.userId,entityId:this.entityId,environment:this.environment});if(e.success&&Array.isArray(e.data)){this.cache.clear();for(let t of e.data)j(t)&&this.cache.set(t.key,t);this.persistCache()}}startAutoRefresh(e=6e4){this.stopAutoRefresh(),this.refreshAll(),this.refreshInterval=setInterval(()=>this.refreshAll(),e)}stopAutoRefresh(){this.refreshInterval&&clearInterval(this.refreshInterval),this.refreshInterval=null}getAllFlags(){let e={};for(let[t,s]of this.cache)e[t]=s.value;return e}reset(){this.cache.clear(),this.storage.remove("flags"),this.userId=void 0,this.entityId=void 0,this.environment=void 0}destroy(){this.stopAutoRefresh()}persistCache(){this.storage.set("flags",JSON.stringify(Array.from(this.cache.values())))}};function j(r){if(r==null||typeof r!="object")return!1;let e=r;return Object.prototype.hasOwnProperty.call(e,"__proto__")||Object.prototype.hasOwnProperty.call(e,"constructor")?!1:typeof e.key=="string"&&"value"in e}var fe=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||!me(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>fe)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 me(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(ye)}function ye(r){if(r==null||typeof r!="object")return!1;let e=r;return typeof e.id=="string"&&typeof e.text=="string"}function A(r){if(!(!r||typeof r!="string"))try{let e=typeof window<"u"?window.location.href:"https://local/",t=new URL(r,e);return t.protocol==="http:"||t.protocol==="https:"?t.href:void 0}catch{return}}var we=new Set(["b","i","u","em","strong","a","p","br","span","ul","ol","li","h1","h2","h3","h4"]),ve={a:new Set(["href","target","rel"])};function B(r){if(typeof DOMParser>"u")return"";let t=new DOMParser().parseFromString(`<div>${r}</div>`,"text/html").body.firstElementChild;return t?(ne(t),t.innerHTML):""}function ne(r){let e=Array.from(r.children);for(let t of e){let s=t.tagName.toLowerCase();if(!we.has(s)){let i=document.createTextNode(t.textContent??"");t.replaceWith(i);continue}let n=ve[s]??new Set;for(let i of Array.from(t.attributes)){if(!n.has(i.name.toLowerCase())){t.removeAttribute(i.name);continue}if(i.name.toLowerCase()==="href"){let o=A(i.value);o?t.setAttribute("href",o):t.removeAttribute(i.name)}}s==="a"&&(t.setAttribute("rel","noopener noreferrer"),t.setAttribute("target","_blank")),ne(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(Se);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 Se(r){return{...r,content:{...r.content,ctaUrl:A(r.content.ctaUrl),imageUrl:A(r.content.imageUrl)}}}var Ae=4e3,U=class{constructor(e,t,s){this.chatbotId=null;this.messages=[];this.http=t,this.storage=s;let n=this.storage.get("chat_session_id");this.sessionId=n??I("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>Ae)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=I("chat_"),this.storage.set("chat_session_id",this.sessionId)}getSessionId(){return this.sessionId}};var O=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 k="auth_access_token",b="auth_access_expires",w="auth_refresh_token",x="auth_user",Ie=3e4;function p(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 M=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}},D=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(k),d=this.storage.get(b);if(a&&c)try{let g=JSON.parse(a);if(typeof g.id!="string"||g.id.length===0)throw new Error("invalid cache");this.user=g,this.accessToken=c,this.accessExpiresAt=d?Number(d):0,this.analytics.identify(this.user.id)}catch{this.storage.remove(x),this.storage.remove(k),this.storage.remove(b),this.storage.remove(w)}}getCurrentUser(){return this.user}async getAccessToken(){return this.accessToken?this.accessExpiresAt&&Date.now()<this.accessExpiresAt-Ie?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||!p(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(w);if(n){let o=await this.http.post("/auth-service/upgrade",{refreshToken:n,email:e,password:t,name:s?.name});if(o.success&&p(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 M(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||!p(i.data)){let o=i.error?.code??"";throw o==="CONFLICT"?new M(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(!p(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||!p(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||!p(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||!p(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||!p(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=ke(t.data),n;try{let a=await navigator.credentials.create({publicKey:s});if(!a)throw new Error("create returned null");n=a}catch(a){throw new u("PASSKEY_USER_CANCELLED",a instanceof Error?a.message:"User cancelled passkey registration")}let i=Ee(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=be(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=_e(i),a=await this.http.post("/auth-service/passkeys/authenticate/finish",{response:o,userId:e??null});if(!a.success||!p(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(k,o.accessToken),this.storage.set(b,String(this.accessExpiresAt)),this.storage.set(w,o.refreshToken);let a=Te(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(w);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(w);return e?(this.refreshInflight=(async()=>{try{let t=await this.http.post("/auth-service/token/refresh",{refreshToken:e});if(!t.success||!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(k,s.accessToken),this.storage.set(b,String(this.accessExpiresAt)),this.storage.set(w,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(k,e.tokens.accessToken),this.storage.set(b,String(this.accessExpiresAt)),this.storage.set(w,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(k),this.storage.remove(b),this.storage.remove(w),this.analytics.reset(),this.analytics.dropQueue(),this.flags.reset(),this.chatbot.newSession()}};function L(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 v(r){let e=r instanceof Uint8Array?r:new Uint8Array(r),t="";for(let s=0;s<e.length;s++)t+=String.fromCharCode(e[s]);return btoa(t).replace(/\+/g,"-").replace(/\//g,"_").replace(/=+$/,"")}function ke(r){return{challenge:L(r.challenge),rp:r.rp,user:{id:L(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:L(e.id),type:"public-key",transports:e.transports})),authenticatorSelection:r.authenticatorSelection}}function be(r){return{challenge:L(r.challenge),timeout:r.timeout,rpId:r.rpId,allowCredentials:r.allowCredentials?.map(e=>({id:L(e.id),type:"public-key",transports:e.transports})),userVerification:r.userVerification}}function Ee(r){let e=r.response;return{id:r.id,rawId:v(r.rawId),type:r.type,response:{clientDataJSON:v(e.clientDataJSON),attestationObject:v(e.attestationObject),transports:typeof e.getTransports=="function"?e.getTransports():void 0},clientExtensionResults:r.getClientExtensionResults()}}function Te(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 _e(r){let e=r.response;return{id:r.id,rawId:v(r.rawId),type:r.type,response:{clientDataJSON:v(e.clientDataJSON),authenticatorData:v(e.authenticatorData),signature:v(e.signature),userHandle:e.userHandle?v(e.userHandle):null},clientExtensionResults:r.getClientExtensionResults()}}function Pe(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 ae(r,e){if(typeof window>"u")return()=>{};let t=Pe(e.autoTrack),s=e.sessionIdleMs??18e5,n=[],i="sendora_session_id",o="sendora_session_last_activity";function a(){return ie(i)??""}function c(l){oe(i,l)}function d(){let l=ie(o);return l&&Number(l)||0}function g(l){oe(o,String(l))}function E(){return`sess_${Date.now().toString(36)}_${Math.random().toString(36).slice(2,10)}`}function f(l){let S=Date.now(),T=d();if(!a()||T&&S-T>s){let F=E();c(F),l&&t.sessionStart&&r.track("session.start",{sessionId:F})}g(S)}if(f(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=()=>{f(!0),r.page()},S=()=>l();window.addEventListener("popstate",S),n.push(()=>window.removeEventListener("popstate",S));let T=window;if(!T.__sendoraHistoryPatched){T.__sendoraHistoryPatched=!0;let F=history.pushState,ce=history.replaceState;history.pushState=function(...z){let q=F.apply(this,z);return window.dispatchEvent(new Event("sendora:locationchange")),q},history.replaceState=function(...z){let q=ce.apply(this,z);return window.dispatchEvent(new Event("sendora:locationchange")),q}}let V=()=>l();window.addEventListener("sendora:locationchange",V),n.push(()=>window.removeEventListener("sendora:locationchange",V))}if(t.appBackground){let l=()=>{f(!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 m=0,y=()=>{let l=Date.now();l-m<5e3||(m=l,f(!0))};return window.addEventListener("click",y,{passive:!0}),window.addEventListener("keydown",y,{passive:!0}),window.addEventListener("scroll",y,{passive:!0}),n.push(()=>{window.removeEventListener("click",y),window.removeEventListener("keydown",y),window.removeEventListener("scroll",y)}),()=>{for(let l of n)try{l()}catch{}}}function ie(r){try{return window.sessionStorage?.getItem(r)??null}catch{return null}}function oe(r,e){try{window.sessionStorage?.setItem(r,e)}catch{}}var N=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:Re(i)}),c=a.toJSON(),d=c.endpoint,g=c.keys?.p256dh??"",E=c.keys?.auth??"",f=await Ce(d),m=await this.http.post("/push/tokens",{orgId:this.orgId,platform:"web",token:f,webEndpoint:d,webP256dh:g,webAuth:E,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(!m?.data?.tokenId)throw new Error("[sendoracloud] backend returned no tokenId on subscribe.");return{tokenId:m.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 Re(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 Ce(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 Ue="2.9.0",W=class W{constructor(e){this.autoTrackCleanup=null;this.http=new H(e),this.storage=new $,this.consent=new O(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 R(e,this.http,this.storage),this.messages=new C(e,this.http,this.storage),this.chatbot=new U(e,this.http,this.storage),this.auth=new D(e,this.http,this.storage,this.analytics,this.flags,this.chatbot),this.webPush=new N(this.http,e.orgId),e.autoTrack!==!1&&(this.autoTrackCleanup=ae(this.analytics,e))}static init(e){return ee(e.apiKey),new W(e)}static sanitizeHtml(e){return B(e)}static safeUrl(e){return A(e)}track(e,t){this.analytics.track(e,t)}page(e){this.analytics.page(e)}identify(e,t,s){this.analytics.identify(e,t,s),this.flags.setContext({userId:e})}reset(){this.analytics.reset(),this.flags.reset(),this.chatbot.newSession()}async flush(){await this.analytics.flush()}destroy(){this.autoTrackCleanup&&(this.autoTrackCleanup(),this.autoTrackCleanup=null),this.analytics.destroy(),this.flags.destroy(),this.messages.destroy()}};W.version=Ue;var Y=W,lt=Y;export{_ as Analytics,D as Auth,u as AuthError,U as Chatbot,O as Consent,M as EmailAlreadyTakenError,P as FeatureFlags,C as InAppMessaging,Y as SendoraCloud,R as Surveys,h as ValidationError,N as WebPush,lt as default,A as safeUrl,B as sanitizeHtml};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sendoracloud/sdk-web",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.9.0",
|
|
4
4
|
"description": "Sendora Cloud Web SDK — analytics, feature flags, surveys, in-app messaging, chatbot, auth. Zero dependencies, TypeScript, SSR-safe.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.cjs",
|