@sendoracloud/sdk-web 2.12.0 → 2.13.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 +107 -2
- package/dist/index.d.ts +107 -2
- package/dist/index.js +1 -1
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";var G=Object.defineProperty;var de=Object.getOwnPropertyDescriptor;var he=Object.getOwnPropertyNames;var pe=Object.prototype.hasOwnProperty;var ge=(r,e)=>{for(var t in e)G(r,t,{get:e[t],enumerable:!0})},fe=(r,e,t,s)=>{if(e&&typeof e=="object"||typeof e=="function")for(let n of he(e))!pe.call(r,n)&&n!==t&&G(r,n,{get:()=>e[n],enumerable:!(s=de(e,n))||s.enumerable});return r};var me=r=>fe(G({},"__esModule",{value:!0}),r);var Ve={};ge(Ve,{Analytics:()=>b,Audiences:()=>M,Auth:()=>O,AuthError:()=>c,Chatbot:()=>P,Consent:()=>R,EmailAlreadyTakenError:()=>x,FeatureFlags:()=>E,InAppMessaging:()=>_,SendoraCloud:()=>z,Surveys:()=>T,ValidationError:()=>h,WebPush:()=>L,default:()=>$e,safeUrl:()=>v,sanitizeHtml:()=>W});module.exports=me(Ve);var X=/^[a-zA-Z][a-zA-Z0-9._:-]{0,127}$/;var ye=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 Z(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=ee(r);if(e>5)throw new h(`Properties nested too deeply (${e} levels, max 5).`);te(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 ee(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=ee(r[s],e+1);n>t&&(t=n)}return t}function te(r){if(!(r==null||typeof r!="object"))for(let e of Object.keys(r)){if(ye.has(e))throw new h(`Forbidden property key "${e}" (prototype pollution guard).`);te(r[e])}}var we=/^pk_[A-Za-z0-9_-]{16,}$/;function se(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(!we.test(r))throw new h("Invalid publishable key format. Expected pk_... (17+ chars).")}function re(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 ve="https://api.sendoracloud.com",Ae=1e4,K=class{constructor(e){let t=(e.apiUrl??ve).replace(/\/$/,"");re(t),this.apiKey=e.apiKey,this.baseUrl=t,this.debug=!!e.debug}get(e,t){return this.request("GET",e,void 0,t)}post(e,t,s){return this.request("POST",e,t,s)}patch(e,t,s){return this.request("PATCH",e,t,s)}delete(e,t){return this.request("DELETE",e,void 0,t)}async request(e,t,s,n){let i=`${this.baseUrl}/api/v1${t}`,o=typeof AbortController<"u"?new AbortController:null,a=o?setTimeout(()=>o.abort(),Ae):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 p=d?.error?.code??"HTTP_ERROR",m=d?.error?.message??`HTTP ${u.status}`,y=d?.error?.details?.fieldErrors,w=y?` (${Object.entries(y).map(([l,S])=>`${l}: ${(S??[]).join(", ")}`).join("; ")})`:"";console.warn(`[SendoraCloud] ${e} ${t} \u2192 HTTP ${u.status} \u2014 ${p}: ${m}${w}`)}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 $="sendora_",V=class{constructor(){this.memoryStore=new Map;this.useLocalStorage=this.checkLocalStorage()}get(e){let t=$+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=$+e;if(this.useLocalStorage)try{localStorage.setItem(s,t);return}catch{}this.memoryStore.set(s,t)}remove(e){let t=$+e;if(this.useLocalStorage)try{localStorage.removeItem(t)}catch{}this.memoryStore.delete(t)}clearPrefix(e){let t=$+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 k(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 ne="2.9.0",ie=500,Ie=10,Se=6e4,b=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=k("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&&Z(t),this.queue.length>=ie&&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=k("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>Ie)return;let n=Math.min(Se,1e3*2**this.consecutiveFailures);this.nextFlushAfter=Date.now()+n;let i=Math.max(0,ie-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:ne}}:{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:ne}}}};var E=class{constructor(e,t,s){this.cache=new Map;this.refreshInterval=null;this.http=t,this.storage=s;let n=this.storage.get("flags");if(n)try{let i=JSON.parse(n);if(Array.isArray(i))for(let o of i)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 ke=32*1024,T=class{constructor(e,t,s){this.activeSurvey=null;this.http=t,this.storage=s}onDisplay(e){this.onSurveyDisplay=e}async showSurvey(e){if(typeof e!="string"||!/^[A-Za-z0-9_-]{6,64}$/.test(e))return null;let t=`survey_shown_${e}`;if(this.storage.get(t))return null;let s=await this.http.get(`/surveys/${encodeURIComponent(e)}/config`);return!s.success||!s.data||!be(s.data)?null:(this.activeSurvey=s.data,this.storage.set(t,"1"),this.onSurveyDisplay&&this.onSurveyDisplay(s.data),s.data)}async submitResponse(e,t,s=!0){let n=0;try{n=JSON.stringify(t).length}catch{return!1}if(n>ke)return!1;let i=await this.http.post("/surveys/responses",{surveyId:e,answers:t,completed:s});return i.success&&(this.activeSurvey=null),i.success}dismiss(){this.activeSurvey=null}getActiveSurvey(){return this.activeSurvey}};function be(r){if(r==null||typeof r!="object")return!1;let e=r;return typeof e.id!="string"||typeof e.name!="string"||!Array.isArray(e.questions)?!1:e.questions.every(Ee)}function Ee(r){if(r==null||typeof r!="object")return!1;let e=r;return typeof e.id=="string"&&typeof e.text=="string"}function v(r){if(!(!r||typeof r!="string"))try{let e=typeof window<"u"?window.location.href:"https://local/",t=new URL(r,e);return t.protocol==="http:"||t.protocol==="https:"?t.href:void 0}catch{return}}var Te=new Set(["b","i","u","em","strong","a","p","br","span","ul","ol","li","h1","h2","h3","h4"]),_e={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?(oe(t),t.innerHTML):""}function oe(r){let e=Array.from(r.children);for(let t of e){let s=t.tagName.toLowerCase();if(!Te.has(s)){let i=document.createTextNode(t.textContent??"");t.replaceWith(i);continue}let n=_e[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=v(i.value);o?t.setAttribute("href",o):t.removeAttribute(i.name)}}s==="a"&&(t.setAttribute("rel","noopener noreferrer"),t.setAttribute("target","_blank")),oe(t)}}var _=class{constructor(e,t,s){this.messages=[];this.pollInterval=null;this.http=t,this.storage=s}onDisplay(e){this.onMessageDisplay=e}async checkMessages(){let e=await this.http.get("/in-app-messages/active");if(!e.success||!Array.isArray(e.data))return null;this.messages=e.data.map(Pe);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 Pe(r){return{...r,content:{...r.content,ctaUrl:v(r.content.ctaUrl),imageUrl:v(r.content.imageUrl)}}}var Re=4e3,P=class{constructor(e,t,s){this.chatbotId=null;this.messages=[];this.http=t,this.storage=s;let n=this.storage.get("chat_session_id");this.sessionId=n??k("chat_"),n||this.storage.set("chat_session_id",this.sessionId)}init(e){if(typeof e!="string"||!/^[A-Za-z0-9_-]{6,64}$/.test(e))throw new Error("[SendoraCloud] Invalid chatbotId.");this.chatbotId=e}onNewMessage(e){this.onMessage=e}async sendMessage(e,t){if(!this.chatbotId||typeof e!="string"||e.length===0||e.length>Re)return null;let s={role:"user",content:e,timestamp:new Date().toISOString()};this.messages.push(s);let n=await this.http.post("/chatbot/message",{chatbotId:this.chatbotId,sessionId:this.sessionId,message:e,userId:t});if(n.success&&n.data&&typeof n.data.reply=="string"){let i={role:"assistant",content:n.data.reply,timestamp:new Date().toISOString()};return this.messages.push(i),this.onMessage&&this.onMessage(i),i}return null}getMessages(){return[...this.messages]}newSession(){this.messages=[],this.sessionId=k("chat_"),this.storage.set("chat_session_id",this.sessionId)}getSessionId(){return this.sessionId}};var R=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 C="auth_access_token",U="auth_access_expires",g="auth_refresh_token",N="auth_user",Ce=3e4;function f(r){if(!r||typeof r!="object")return!1;let e=r,t=e.user,s=e.tokens;return!(!t||typeof t.id!="string"||t.id.length===0||!s||typeof s.accessToken!="string"||s.accessToken.length===0||typeof s.refreshToken!="string"||s.refreshToken.length===0||typeof s.expiresIn!="number"||s.expiresIn<=0||t.isAnonymous!==void 0&&typeof t.isAnonymous!="boolean"||t.email!==void 0&&t.email!==null&&typeof t.email!="string"||t.emailVerified!==void 0&&typeof t.emailVerified!="boolean"||t.name!==void 0&&t.name!==null&&typeof t.name!="string")}var x=class extends Error{constructor(e="An account with this email already exists"){super(e),this.name="EmailAlreadyTakenError"}},c=class extends Error{constructor(e,t){super(t),this.name="AuthError",this.code=e}},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(N),u=this.storage.get(C),d=this.storage.get(g),I=this.storage.get(U);if(a&&(u||d))try{let p=JSON.parse(a);if(typeof p.id!="string"||p.id.length===0)throw new Error("invalid cache");this.user=p,u?(this.accessToken=u,this.accessExpiresAt=I?Number(I):0):(this.accessToken=null,this.accessExpiresAt=0),this.analytics.identify(this.user.id)}catch{this.storage.remove(N),this.storage.remove(C),this.storage.remove(U),this.storage.remove(g)}}getCurrentUser(){return this.user}async getAccessToken(){return this.accessToken?this.accessExpiresAt&&Date.now()<this.accessExpiresAt-Ce?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||!f(t.data))throw new c(t.error?.code??"AUTH_ERROR",t.error?.message??"Anonymous signup failed");return this.persist(t.data),t.data.user})}signUp(e,t,s){return this.serialize(async()=>{let n=this.storage.get(g);if(n){let o=await this.http.post("/auth-service/upgrade",{refreshToken:n,email:e,password:t,name:s?.name});if(o.success&&f(o.data))return this.persist(o.data),o.data.user;let a=o.error?.code??"",u=o.error?.status;if(a==="CONFLICT"||a==="EMAIL_ALREADY_TAKEN"||u===409)throw new x(o.error?.message);if(a==="FORBIDDEN_NON_ANONYMOUS"||a==="INVALID_REFRESH"||a==="UNAUTHORIZED"||a==="REFRESH_EXPIRED")await this.analytics.flush(),this.wipeLocalIdentity();else throw new c(a||"AUTH_ERROR",o.error?.message??"Upgrade failed")}let i=await this.http.post("/auth-service/signup",{email:e,password:t,name:s?.name,metadata:s?.metadata});if(!i.success||!f(i.data)){let o=i.error?.code??"";throw o==="CONFLICT"?new x(i.error?.message):new c(o||"AUTH_ERROR",i.error?.message??"Signup failed")}return this.persist(i.data),i.data.user})}signIn(e,t){return this.serialize(async()=>{this.user!==null&&(await this.analytics.flush(),this.wipeLocalIdentity());let n=await this.http.post("/auth-service/login",{email:e,password:t});if(!n.success||!n.data)throw new c(n.error?.code??"AUTH_ERROR",n.error?.message??"Login failed");let i=n.data;if(i.mfaRequired===!0&&typeof i.mfaChallengeToken=="string"){let o=i.user;return{mfaRequired:!0,mfaChallengeToken:i.mfaChallengeToken,userId:o?.id??""}}if(!f(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||!f(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||!f(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||!f(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||!f(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=xe(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=Le(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=Oe(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=De(i),a=await this.http.post("/auth-service/passkeys/authenticate/finish",{response:o,userId:e??null});if(!a.success||!f(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(C,o.accessToken),this.storage.set(U,String(this.accessExpiresAt)),this.storage.set(g,o.refreshToken);let a=Me(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(N,JSON.stringify(u)),this.analytics.identify(u.id),this.flags.setContext({userId:u.id}),u})}bearerHeaders(){if(!this.accessToken)throw new c("NOT_SIGNED_IN","Not signed in");return{Authorization:`Bearer ${this.accessToken}`}}signOut(){return this.serialize(async()=>{let e=this.storage.get(g);this.wipeLocalIdentity(),e&&this.http.post("/auth-service/token/revoke",{refreshToken:e}).catch(()=>{})})}serialize(e){let t=this.inflight.then(e,e);return this.inflight=t.catch(()=>{}),t}async refreshAccessToken(){if(this.refreshInflight)return this.refreshInflight;let e=this.storage.get(g);return e?(this.refreshInflight=(async()=>{try{let t=await this.http.post("/auth-service/token/refresh",{refreshToken:e});if(!t.success)return Ue(t.error?.code)&&this.wipeLocalIdentity(),null;if(!t.data||typeof t.data!="object"||typeof t.data.accessToken!="string"||typeof t.data.refreshToken!="string"||typeof t.data.expiresIn!="number")return null;let s=t.data;return this.accessToken=s.accessToken,this.accessExpiresAt=Date.now()+s.expiresIn*1e3,this.storage.set(C,s.accessToken),this.storage.set(U,String(this.accessExpiresAt)),this.storage.set(g,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(N,JSON.stringify(e.user)),this.storage.set(C,e.tokens.accessToken),this.storage.set(U,String(this.accessExpiresAt)),this.storage.set(g,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(N),this.storage.remove(C),this.storage.remove(U),this.storage.remove(g),this.analytics.reset(),this.analytics.dropQueue(),this.flags.reset(),this.chatbot.newSession()}};function Ue(r){return r?r==="INVALID_REFRESH_TOKEN"||r==="UNAUTHORIZED"||r==="HTTP_401"||r==="RATE_LIMIT_EXCEEDED"||r==="RATE_LIMIT":!1}function F(r){let e=(4-r.length%4)%4,t=(r+"===".slice(0,e)).replace(/-/g,"+").replace(/_/g,"/"),s=atob(t),n=new ArrayBuffer(s.length),i=new Uint8Array(n);for(let o=0;o<s.length;o++)i[o]=s.charCodeAt(o);return n}function A(r){let e=r instanceof Uint8Array?r:new Uint8Array(r),t="";for(let s=0;s<e.length;s++)t+=String.fromCharCode(e[s]);return btoa(t).replace(/\+/g,"-").replace(/\//g,"_").replace(/=+$/,"")}function xe(r){return{challenge:F(r.challenge),rp:r.rp,user:{id:F(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:F(e.id),type:"public-key",transports:e.transports})),authenticatorSelection:r.authenticatorSelection}}function Oe(r){return{challenge:F(r.challenge),timeout:r.timeout,rpId:r.rpId,allowCredentials:r.allowCredentials?.map(e=>({id:F(e.id),type:"public-key",transports:e.transports})),userVerification:r.userVerification}}function Le(r){let e=r.response;return{id:r.id,rawId:A(r.rawId),type:r.type,response:{clientDataJSON:A(e.clientDataJSON),attestationObject:A(e.attestationObject),transports:typeof e.getTransports=="function"?e.getTransports():void 0},clientExtensionResults:r.getClientExtensionResults()}}function Me(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 De(r){let e=r.response;return{id:r.id,rawId:A(r.rawId),type:r.type,response:{clientDataJSON:A(e.clientDataJSON),authenticatorData:A(e.authenticatorData),signature:A(e.signature),userHandle:e.userHandle?A(e.userHandle):null},clientExtensionResults:r.getClientExtensionResults()}}function Ne(r){return r===!1?{pageViews:!1,sessionStart:!1,appOpen:!1,appBackground:!1}:r===!0||r===void 0?{pageViews:!0,sessionStart:!0,appOpen:!0,appBackground:!0}:{pageViews:r.pageViews??!0,sessionStart:r.sessionStart??!0,appOpen:r.appOpen??!0,appBackground:r.appBackground??!0}}function ce(r,e){if(typeof window>"u")return()=>{};let t=Ne(e.autoTrack),s=e.sessionIdleMs??18e5,n=[],i="sendora_session_id",o="sendora_session_last_activity";function a(){return ae(i)??""}function u(l){ue(i,l)}function d(){let l=ae(o);return l&&Number(l)||0}function I(l){ue(o,String(l))}function p(){return`sess_${Date.now().toString(36)}_${Math.random().toString(36).slice(2,10)}`}function m(l){let S=Date.now(),D=d();if(!a()||D&&S-D>s){let H=p();u(H),l&&t.sessionStart&&r.track("session.start",{sessionId:H})}I(S)}if(m(t.sessionStart),t.appOpen&&r.track("app.opened",{sessionId:a()||void 0,url:window.location.origin+window.location.pathname,referrer:document.referrer||void 0}),t.pageViews){r.page();let l=()=>{m(!0),r.page()},S=()=>l();window.addEventListener("popstate",S),n.push(()=>window.removeEventListener("popstate",S));let D=window;if(!D.__sendoraHistoryPatched){D.__sendoraHistoryPatched=!0;let H=history.pushState,le=history.replaceState;history.pushState=function(...B){let Y=H.apply(this,B);return window.dispatchEvent(new Event("sendora:locationchange")),Y},history.replaceState=function(...B){let Y=le.apply(this,B);return window.dispatchEvent(new Event("sendora:locationchange")),Y}}let j=()=>l();window.addEventListener("sendora:locationchange",j),n.push(()=>window.removeEventListener("sendora:locationchange",j))}if(t.appBackground){let l=()=>{m(!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 y=0,w=()=>{let l=Date.now();l-y<5e3||(y=l,m(!0))};return window.addEventListener("click",w,{passive:!0}),window.addEventListener("keydown",w,{passive:!0}),window.addEventListener("scroll",w,{passive:!0}),n.push(()=>{window.removeEventListener("click",w),window.removeEventListener("keydown",w),window.removeEventListener("scroll",w)}),()=>{for(let l of n)try{l()}catch{}}}function ae(r){try{return window.sessionStorage?.getItem(r)??null}catch{return null}}function ue(r,e){try{window.sessionStorage?.setItem(r,e)}catch{}}var L=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:Fe(i)}),u=a.toJSON(),d=u.endpoint,I=u.keys?.p256dh??"",p=u.keys?.auth??"",m=await He(d),y=await this.http.post("/push/tokens",{orgId:this.orgId,platform:"web",token:m,webEndpoint:d,webP256dh:I,webAuth:p,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(!y?.data?.tokenId)throw new Error("[sendoracloud] backend returned no tokenId on subscribe.");return{tokenId:y.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 Fe(r){let e="=".repeat((4-r.length%4)%4),t=(r+e).replace(/-/g,"+").replace(/_/g,"/"),s=atob(t),n=new Uint8Array(s.length);for(let i=0;i<s.length;i++)n[i]=s.charCodeAt(i);return n}async function He(r){let e=new TextEncoder().encode(r),t=await crypto.subtle.digest("SHA-256",e);return Array.from(new Uint8Array(t)).map(n=>n.toString(16).padStart(2,"0")).join("")}var M=class{constructor(e,t){this.http=e,this.orgId=t}setOrgId(e){this.orgId=e}base(){if(!this.orgId)throw new Error("Audiences API requires orgId \u2014 pass it via SendoraCloud.init({ orgId }) or call audiences.setOrgId() before use.");return`/orgs/${this.orgId}/audiences`}async list(){let e=await this.http.get(this.base());return e.success?e.data??[]:[]}async get(e){let t=await this.http.get(`${this.base()}/${e}`);return t.success?t.data??null:null}async create(e){let t=await this.http.post(this.base(),e);return t.success?t.data??null:null}async update(e,t){let s=await this.http.patch(`${this.base()}/${e}`,t);return s.success?s.data??null:null}async delete(e){return(await this.http.delete(`${this.base()}/${e}`)).success}};var Ke="2.10.0",q=class q{constructor(e){this.autoTrackCleanup=null;this.http=new K(e),this.storage=new V,this.consent=new R(e.defaultConsent===!0),this.analytics=new b(e,this.http,this.storage,this.consent),this.flags=new E(e,this.http,this.storage),this.surveys=new T(e,this.http,this.storage),this.messages=new _(e,this.http,this.storage),this.chatbot=new P(e,this.http,this.storage),this.auth=new O(e,this.http,this.storage,this.analytics,this.flags,this.chatbot),this.webPush=new L(this.http,e.orgId),this.audiences=new M(this.http,e.orgId),e.autoTrack!==!1&&(this.autoTrackCleanup=ce(this.analytics,e))}static init(e){return se(e.apiKey),new q(e)}static sanitizeHtml(e){return W(e)}static safeUrl(e){return v(e)}track(e,t){this.analytics.track(e,t)}page(e){this.analytics.page(e)}identify(e,t,s){this.analytics.identify(e,t,s),this.flags.setContext({userId:e})}reset(){this.analytics.reset(),this.flags.reset(),this.chatbot.newSession()}async flush(){await this.analytics.flush()}destroy(){this.autoTrackCleanup&&(this.autoTrackCleanup(),this.autoTrackCleanup=null),this.analytics.destroy(),this.flags.destroy(),this.messages.destroy()}};q.version=Ke;var z=q,$e=z;0&&(module.exports={Analytics,Audiences,Auth,AuthError,Chatbot,Consent,EmailAlreadyTakenError,FeatureFlags,InAppMessaging,SendoraCloud,Surveys,ValidationError,WebPush,safeUrl,sanitizeHtml});
|
|
1
|
+
"use strict";var X=Object.defineProperty;var me=Object.getOwnPropertyDescriptor;var ye=Object.getOwnPropertyNames;var we=Object.prototype.hasOwnProperty;var ve=(r,e)=>{for(var t in e)X(r,t,{get:e[t],enumerable:!0})},ke=(r,e,t,s)=>{if(e&&typeof e=="object"||typeof e=="function")for(let n of ye(e))!we.call(r,n)&&n!==t&&X(r,n,{get:()=>e[n],enumerable:!(s=me(e,n))||s.enumerable});return r};var Ae=r=>ke(X({},"__esModule",{value:!0}),r);var Ye={};ve(Ye,{Analytics:()=>E,Audiences:()=>N,Auth:()=>O,AuthError:()=>u,Chatbot:()=>P,Consent:()=>C,EmailAlreadyTakenError:()=>U,FeatureFlags:()=>T,InAppMessaging:()=>_,LinkError:()=>d,Links:()=>M,SendoraCloud:()=>q,Surveys:()=>R,ValidationError:()=>p,WebPush:()=>x,default:()=>Ge,extractShortcodeFromUrl:()=>ge,safeUrl:()=>k,sanitizeHtml:()=>z});module.exports=Ae(Ye);var te=/^[a-zA-Z][a-zA-Z0-9._:-]{0,127}$/;var Ie=new Set(["__proto__","constructor","prototype"]),p=class extends Error{constructor(e){super(`[SendoraCloud] ${e}`),this.name="SendoraValidationError"}};function se(r){if(typeof r!="string"||!te.test(r))throw new p(`Invalid event name: must match ${te}, got "${String(r)}".`)}function re(r){if(r==null)return;if(typeof r!="object"||Array.isArray(r))throw new p("Properties must be a plain object, not an array or primitive.");let e=ne(r);if(e>5)throw new p(`Properties nested too deeply (${e} levels, max 5).`);ie(r);let t;try{t=JSON.stringify(r).length}catch{throw new p("Properties contain a circular reference or unserializable value.")}if(t>32768)throw new p(`Properties too large (${t} bytes, max 32768).`)}function ne(r,e=0){if(e>6||r==null||typeof r!="object")return e;let t=e;for(let s of Object.keys(r)){let n=ne(r[s],e+1);n>t&&(t=n)}return t}function ie(r){if(!(r==null||typeof r!="object"))for(let e of Object.keys(r)){if(Ie.has(e))throw new p(`Forbidden property key "${e}" (prototype pollution guard).`);ie(r[e])}}var Se=/^pk_[A-Za-z0-9_-]{16,}$/;function oe(r){if(!r||typeof r!="string")throw new p("apiKey is required.");if(r.startsWith("sk_")||r.startsWith("sendora_secret_"))throw new p("Secret keys must NEVER be used in browser SDK. Use a publishable key (pk_...).");if(!Se.test(r))throw new p("Invalid publishable key format. Expected pk_... (17+ chars).")}function ae(r){let e=r.replace(/\/$/,"");if(!(e.startsWith("http://localhost")||e.startsWith("http://127.0.0.1"))&&!e.startsWith("https://"))throw new p("apiUrl must be https:// (only http://localhost is allowed for dev).")}var be="https://api.sendoracloud.com",Ee=1e4,V=class{constructor(e){let t=(e.apiUrl??be).replace(/\/$/,"");ae(t),this.apiKey=e.apiKey,this.baseUrl=t,this.debug=!!e.debug}get(e,t){return this.request("GET",e,void 0,t)}post(e,t,s){return this.request("POST",e,t,s)}patch(e,t,s){return this.request("PATCH",e,t,s)}delete(e,t){return this.request("DELETE",e,void 0,t)}async request(e,t,s,n){let i=`${this.baseUrl}/api/v1${t}`,o=typeof AbortController<"u"?new AbortController:null,a=o?setTimeout(()=>o.abort(),Ee):null;try{let c=await fetch(i,{method:e,headers:{"Content-Type":"application/json","X-API-Key":this.apiKey,...n??{}},body:s?JSON.stringify(s):void 0,signal:o?o.signal:void 0});a&&clearTimeout(a);let h;try{h=await c.json()}catch{h={success:!1,error:{code:"PARSE_ERROR",message:`HTTP ${c.status}`}}}if(this.debug&&console.debug(`[SendoraCloud] ${e} ${t} \u2192 ${c.status}`),!c.ok)if(t.startsWith("/auth-service/")&&!this.debug)console.warn(`[SendoraCloud] ${e} ${t} \u2192 HTTP ${c.status}`);else{let g=h?.error?.code??"HTTP_ERROR",y=h?.error?.message??`HTTP ${c.status}`,w=h?.error?.details?.fieldErrors,v=w?` (${Object.entries(w).map(([l,S])=>`${l}: ${(S??[]).join(", ")}`).join("; ")})`:"";console.warn(`[SendoraCloud] ${e} ${t} \u2192 HTTP ${c.status} \u2014 ${g}: ${y}${v}`)}return h}catch(c){a&&clearTimeout(a);let h=c instanceof Error&&c.name==="AbortError"?"timeout":"error";return console.warn(`[SendoraCloud] ${e} ${t} network ${h}`),{success:!1,error:{code:"NETWORK_ERROR",message:h}}}}};var W="sendora_",B=class{constructor(){this.memoryStore=new Map;this.useLocalStorage=this.checkLocalStorage()}get(e){let t=W+e;if(this.useLocalStorage)try{return localStorage.getItem(t)}catch{return this.memoryStore.get(t)??null}return this.memoryStore.get(t)??null}set(e,t){let s=W+e;if(this.useLocalStorage)try{localStorage.setItem(s,t);return}catch{}this.memoryStore.set(s,t)}remove(e){let t=W+e;if(this.useLocalStorage)try{localStorage.removeItem(t)}catch{}this.memoryStore.delete(t)}clearPrefix(e){let t=W+e;if(this.useLocalStorage)try{for(let s=localStorage.length-1;s>=0;s--){let n=localStorage.key(s);n&&n.startsWith(t)&&localStorage.removeItem(n)}}catch{}for(let s of Array.from(this.memoryStore.keys()))s.startsWith(t)&&this.memoryStore.delete(s)}checkLocalStorage(){try{if(typeof localStorage>"u")return!1;let e="__sendora_test__";return localStorage.setItem(e,"1"),localStorage.removeItem(e),!0}catch{return!1}}};function b(r=""){let e=new Uint8Array(16),t=typeof globalThis<"u"&&"crypto"in globalThis?globalThis.crypto:void 0;if(!t||typeof t.getRandomValues!="function")throw new Error("[SendoraCloud] crypto.getRandomValues unavailable \u2014 refusing to mint IDs without a CSPRNG");t.getRandomValues(e);let s="";for(let n of e)s+=n.toString(16).padStart(2,"0");return r+s}var ce="2.9.0",ue=500,Te=10,Re=6e4,E=class{constructor(e,t,s,n){this.queue=[];this.timer=null;this.userId=null;this.identityToken=null;this.consecutiveFailures=0;this.nextFlushAfter=0;this.http=t,this.storage=s,this.consent=n,this.projectId=e.projectId,this.flushInterval=e.flushInterval??5e3,this.maxQueueSize=e.maxQueueSize??20,this.persistUserId=!!e.persistUserId,this.stripQueryParams=e.stripQueryParams!==!1;let i=this.storage.get("anonymous_id");if(i?this.anonymousId=i:(this.anonymousId=b("anon_"),this.storage.set("anonymous_id",this.anonymousId)),this.persistUserId){let o=this.storage.get("user_id");o&&(this.userId=o)}this.timer=setInterval(()=>this.flush(),this.flushInterval),typeof window<"u"&&window.addEventListener("beforeunload",()=>this.flush()),this.consent.subscribe(o=>{o&&this.flush()})}track(e,t){se(e),t&&re(t),this.queue.length>=ue&&this.queue.shift(),this.queue.push({eventType:e,properties:t??{},userId:this.userId??void 0,anonymousId:this.anonymousId,timestamp:new Date().toISOString(),identityToken:this.identityToken??void 0}),this.queue.length>=this.maxQueueSize&&this.flush()}page(e){if(typeof window>"u")return;let t=this.stripQueryParams?window.location.origin+window.location.pathname:window.location.href;this.track("page.viewed",{url:t,path:window.location.pathname,title:document.title,referrer:document.referrer||void 0,...e})}identify(e,t,s){if(typeof e!="string"||e.length===0||e.length>256)throw new Error("[SendoraCloud] userId must be a 1-256 char string.");this.userId=e,this.identityToken=s?.identityToken??null,this.persistUserId&&this.storage.set("user_id",e),this.track("user.identified",{...t})}reset(){this.userId=null,this.identityToken=null,this.storage.remove("user_id"),this.anonymousId=b("anon_"),this.storage.set("anonymous_id",this.anonymousId),this.storage.clearPrefix("iam_shown_"),this.storage.clearPrefix("survey_shown_")}dropQueue(){this.queue=[]}async flush(){if(this.queue.length===0||!this.consent.isGranted()||Date.now()<this.nextFlushAfter)return;let e=this.queue.splice(0,100),t=e.map(o=>({projectId:this.projectId,module:"custom",eventType:o.eventType,userId:o.userId,anonymousId:o.anonymousId,identityToken:o.identityToken,timestamp:o.timestamp??new Date().toISOString(),properties:o.properties??{},context:this.getContext(),consent:["analytics"]}));if((await this.http.post("/events/batch",{events:t})).success){this.consecutiveFailures=0;return}if(this.consecutiveFailures++,this.consecutiveFailures>Te)return;let n=Math.min(Re,1e3*2**this.consecutiveFailures);this.nextFlushAfter=Date.now()+n;let i=Math.max(0,ue-this.queue.length);i>0&&this.queue.unshift(...e.slice(0,i))}destroy(){this.timer&&clearInterval(this.timer),this.timer=null,this.flush()}getContext(){return typeof window>"u"?{sdk:{name:"@sendoracloud/sdk-web",version:ce}}:{page:{url:this.stripQueryParams?window.location.origin+window.location.pathname:window.location.href,path:window.location.pathname,title:document.title},referrer:document.referrer||void 0,locale:typeof navigator<"u"?navigator.language:void 0,sdk:{name:"@sendoracloud/sdk-web",version:ce}}}};var T=class{constructor(e,t,s){this.cache=new Map;this.refreshInterval=null;this.http=t,this.storage=s;let n=this.storage.get("flags");if(n)try{let i=JSON.parse(n);if(Array.isArray(i))for(let o of i)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 _e=32*1024,R=class{constructor(e,t,s){this.activeSurvey=null;this.http=t,this.storage=s}onDisplay(e){this.onSurveyDisplay=e}async showSurvey(e){if(typeof e!="string"||!/^[A-Za-z0-9_-]{6,64}$/.test(e))return null;let t=`survey_shown_${e}`;if(this.storage.get(t))return null;let s=await this.http.get(`/surveys/${encodeURIComponent(e)}/config`);return!s.success||!s.data||!Pe(s.data)?null:(this.activeSurvey=s.data,this.storage.set(t,"1"),this.onSurveyDisplay&&this.onSurveyDisplay(s.data),s.data)}async submitResponse(e,t,s=!0){let n=0;try{n=JSON.stringify(t).length}catch{return!1}if(n>_e)return!1;let i=await this.http.post("/surveys/responses",{surveyId:e,answers:t,completed:s});return i.success&&(this.activeSurvey=null),i.success}dismiss(){this.activeSurvey=null}getActiveSurvey(){return this.activeSurvey}};function Pe(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(Ce)}function Ce(r){if(r==null||typeof r!="object")return!1;let e=r;return typeof e.id=="string"&&typeof e.text=="string"}function k(r){if(!(!r||typeof r!="string"))try{let e=typeof window<"u"?window.location.href:"https://local/",t=new URL(r,e);return t.protocol==="http:"||t.protocol==="https:"?t.href:void 0}catch{return}}var Le=new Set(["b","i","u","em","strong","a","p","br","span","ul","ol","li","h1","h2","h3","h4"]),De={a:new Set(["href","target","rel"])};function z(r){if(typeof DOMParser>"u")return"";let t=new DOMParser().parseFromString(`<div>${r}</div>`,"text/html").body.firstElementChild;return t?(le(t),t.innerHTML):""}function le(r){let e=Array.from(r.children);for(let t of e){let s=t.tagName.toLowerCase();if(!Le.has(s)){let i=document.createTextNode(t.textContent??"");t.replaceWith(i);continue}let n=De[s]??new Set;for(let i of Array.from(t.attributes)){if(!n.has(i.name.toLowerCase())){t.removeAttribute(i.name);continue}if(i.name.toLowerCase()==="href"){let o=k(i.value);o?t.setAttribute("href",o):t.removeAttribute(i.name)}}s==="a"&&(t.setAttribute("rel","noopener noreferrer"),t.setAttribute("target","_blank")),le(t)}}var _=class{constructor(e,t,s){this.messages=[];this.pollInterval=null;this.http=t,this.storage=s}onDisplay(e){this.onMessageDisplay=e}async checkMessages(){let e=await this.http.get("/in-app-messages/active");if(!e.success||!Array.isArray(e.data))return null;this.messages=e.data.map(Ue);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 Ue(r){return{...r,content:{...r.content,ctaUrl:k(r.content.ctaUrl),imageUrl:k(r.content.imageUrl)}}}var Oe=4e3,P=class{constructor(e,t,s){this.chatbotId=null;this.messages=[];this.http=t,this.storage=s;let n=this.storage.get("chat_session_id");this.sessionId=n??b("chat_"),n||this.storage.set("chat_session_id",this.sessionId)}init(e){if(typeof e!="string"||!/^[A-Za-z0-9_-]{6,64}$/.test(e))throw new Error("[SendoraCloud] Invalid chatbotId.");this.chatbotId=e}onNewMessage(e){this.onMessage=e}async sendMessage(e,t){if(!this.chatbotId||typeof e!="string"||e.length===0||e.length>Oe)return null;let s={role:"user",content:e,timestamp:new Date().toISOString()};this.messages.push(s);let n=await this.http.post("/chatbot/message",{chatbotId:this.chatbotId,sessionId:this.sessionId,message:e,userId:t});if(n.success&&n.data&&typeof n.data.reply=="string"){let i={role:"assistant",content:n.data.reply,timestamp:new Date().toISOString()};return this.messages.push(i),this.onMessage&&this.onMessage(i),i}return null}getMessages(){return[...this.messages]}newSession(){this.messages=[],this.sessionId=b("chat_"),this.storage.set("chat_session_id",this.sessionId)}getSessionId(){return this.sessionId}};var C=class{constructor(e){this.listeners=new Set;this.granted=e}isGranted(){return this.granted}grant(){this.granted||(this.granted=!0,this.emit())}revoke(){this.granted&&(this.granted=!1,this.emit())}subscribe(e){return this.listeners.add(e),()=>this.listeners.delete(e)}emit(){for(let e of this.listeners)try{e(this.granted)}catch{}}};var L="auth_access_token",D="auth_access_expires",f="auth_refresh_token",H="auth_user",xe=3e4;function m(r){if(!r||typeof r!="object")return!1;let e=r,t=e.user,s=e.tokens;return!(!t||typeof t.id!="string"||t.id.length===0||!s||typeof s.accessToken!="string"||s.accessToken.length===0||typeof s.refreshToken!="string"||s.refreshToken.length===0||typeof s.expiresIn!="number"||s.expiresIn<=0||t.isAnonymous!==void 0&&typeof t.isAnonymous!="boolean"||t.email!==void 0&&t.email!==null&&typeof t.email!="string"||t.emailVerified!==void 0&&typeof t.emailVerified!="boolean"||t.name!==void 0&&t.name!==null&&typeof t.name!="string")}var U=class extends Error{constructor(e="An account with this email already exists"){super(e),this.name="EmailAlreadyTakenError"}},u=class extends Error{constructor(e,t){super(t),this.name="AuthError",this.code=e}},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(H),c=this.storage.get(L),h=this.storage.get(f),I=this.storage.get(D);if(a&&(c||h))try{let g=JSON.parse(a);if(typeof g.id!="string"||g.id.length===0)throw new Error("invalid cache");this.user=g,c?(this.accessToken=c,this.accessExpiresAt=I?Number(I):0):(this.accessToken=null,this.accessExpiresAt=0),this.analytics.identify(this.user.id)}catch{this.storage.remove(H),this.storage.remove(L),this.storage.remove(D),this.storage.remove(f)}}getCurrentUser(){return this.user}async getAccessToken(){return this.accessToken?this.accessExpiresAt&&Date.now()<this.accessExpiresAt-xe?this.accessToken:this.refreshAccessToken():null}getAccessTokenSync(){return this.accessToken}signInAnonymously(e){return this.serialize(async()=>{let t=await this.http.post("/auth-service/anonymous",{name:e?.name,metadata:e?.metadata});if(!t.success||!m(t.data))throw new u(t.error?.code??"AUTH_ERROR",t.error?.message??"Anonymous signup failed");return this.persist(t.data),t.data.user})}signUp(e,t,s){return this.serialize(async()=>{let n=this.storage.get(f);if(n){let o=await this.http.post("/auth-service/upgrade",{refreshToken:n,email:e,password:t,name:s?.name});if(o.success&&m(o.data))return this.persist(o.data),o.data.user;let a=o.error?.code??"",c=o.error?.status;if(a==="CONFLICT"||a==="EMAIL_ALREADY_TAKEN"||c===409)throw new U(o.error?.message);if(a==="FORBIDDEN_NON_ANONYMOUS"||a==="INVALID_REFRESH"||a==="UNAUTHORIZED"||a==="REFRESH_EXPIRED")await this.analytics.flush(),this.wipeLocalIdentity();else throw new u(a||"AUTH_ERROR",o.error?.message??"Upgrade failed")}let i=await this.http.post("/auth-service/signup",{email:e,password:t,name:s?.name,metadata:s?.metadata});if(!i.success||!m(i.data)){let o=i.error?.code??"";throw o==="CONFLICT"?new U(i.error?.message):new u(o||"AUTH_ERROR",i.error?.message??"Signup failed")}return this.persist(i.data),i.data.user})}signIn(e,t){return this.serialize(async()=>{this.user!==null&&(await this.analytics.flush(),this.wipeLocalIdentity());let n=await this.http.post("/auth-service/login",{email:e,password:t});if(!n.success||!n.data)throw new u(n.error?.code??"AUTH_ERROR",n.error?.message??"Login failed");let i=n.data;if(i.mfaRequired===!0&&typeof i.mfaChallengeToken=="string"){let o=i.user;return{mfaRequired:!0,mfaChallengeToken:i.mfaChallengeToken,userId:o?.id??""}}if(!m(n.data))throw new u(n.error?.code??"AUTH_ERROR",n.error?.message??"Login failed");return this.persist(n.data),n.data.user})}loginSocial(e){return this.serialize(async()=>{this.user!==null&&(await this.analytics.flush(),this.wipeLocalIdentity());let s=await this.http.post("/auth-service/login/social",e);if(!s.success||!m(s.data))throw new u(s.error?.code??"SOCIAL_LOGIN_FAILED",s.error?.message??"Social login failed");return this.persist(s.data),s.data.user})}signInWithGoogle(e,t){return this.loginSocial({provider:"google",code:e,redirectUri:t})}signInWithGitHub(e,t){return this.loginSocial({provider:"github",code:e,redirectUri:t})}signInWithApple(e){return this.loginSocial({provider:"apple",...e})}signInWithMicrosoft(e,t){return this.loginSocial({provider:"microsoft",code:e,redirectUri:t})}signInWithLinkedIn(e,t){return this.loginSocial({provider:"linkedin",code:e,redirectUri:t})}signInWithFacebook(e,t){return this.loginSocial({provider:"facebook",code:e,redirectUri:t})}signInWithDiscord(e,t){return this.loginSocial({provider:"discord",code:e,redirectUri:t})}challengeMfa(e,t){return this.serialize(async()=>{let s=await this.http.post("/auth-service/mfa/challenge",{challengeToken:e,code:t});if(!s.success||!m(s.data))throw new u(s.error?.code??"MFA_CHALLENGE_FAILED",s.error?.message??"Invalid MFA code");return this.persist(s.data),s.data.user})}async sendMagicLink(e){let t=await this.http.post("/auth-service/magic-link/request",{email:e});if(!t.success)throw new u(t.error?.code??"MAGIC_LINK_REQUEST_FAILED",t.error?.message??"Failed to send link")}async sendEmailOtp(e){let t=await this.http.post("/auth-service/email-otp/request",{email:e});if(!t.success)throw new u(t.error?.code??"EMAIL_OTP_REQUEST_FAILED",t.error?.message??"Failed to send code")}verifyEmailOtp(e,t){return this.serialize(async()=>{this.user!==null&&(await this.analytics.flush(),this.wipeLocalIdentity());let n=await this.http.post("/auth-service/email-otp/verify",{email:e,code:t});if(!n.success||!m(n.data))throw new u(n.error?.code??"EMAIL_OTP_INVALID",n.error?.message??"Wrong or expired code");return this.persist(n.data),n.data.user})}async requestPasswordReset(e){let t=await this.http.post("/auth-service/password/forgot",{email:e});if(!t.success)throw new u(t.error?.code??"PASSWORD_RESET_REQUEST_FAILED",t.error?.message??"Failed to send reset email")}async resetPassword(e,t){let s=await this.http.post("/auth-service/password/reset",{token:e,newPassword:t});if(!s.success)throw new u(s.error?.code??"PASSWORD_RESET_FAILED",s.error?.message??"Reset failed")}async verifyEmail(e){let t=await this.http.post("/auth-service/email/verify",{token:e});if(!t.success)throw new u(t.error?.code??"EMAIL_VERIFICATION_FAILED",t.error?.message??"Verification failed")}async sendVerificationEmail(){let e=await this.getAccessToken();if(!e)throw new u("UNAUTHORIZED","Sign in before requesting a verification email");let t=await this.http.post("/auth-service/email/verify/resend",{},{Authorization:`Bearer ${e}`});if(!t.success)throw new u(t.error?.code??"VERIFICATION_RESEND_FAILED",t.error?.message??"Failed to send verification email")}verifyMagicLink(e){return this.serialize(async()=>{this.user!==null&&(await this.analytics.flush(),this.wipeLocalIdentity());let s=await this.http.post("/auth-service/magic-link/verify",{token:e});if(!s.success||!m(s.data))throw new u(s.error?.code??"MAGIC_LINK_INVALID",s.error?.message??"Invalid or expired magic link");return this.persist(s.data),s.data.user})}async enrollMfa(){let e=await this.http.post("/auth-service/mfa/enroll/start",{},this.bearerHeaders());if(!e.success||!e.data)throw new u(e.error?.code??"MFA_ENROLL_FAILED",e.error?.message??"MFA enrollment failed");return e.data}async confirmMfa(e){let t=await this.http.post("/auth-service/mfa/enroll/confirm",{code:e},this.bearerHeaders());return!!(t.success&&t.data?.confirmed)}async disableMfa(){await this.http.post("/auth-service/mfa/disable",{},this.bearerHeaders())}async registerPasskey(e){if(typeof navigator>"u"||!navigator.credentials)throw new u("PASSKEY_UNSUPPORTED","WebAuthn not supported in this environment");let t=await this.http.post("/auth-service/passkeys/register/start",{},this.bearerHeaders());if(!t.success||!t.data)throw new u(t.error?.code??"PASSKEY_START_FAILED",t.error?.message??"Passkey start failed");let s=Me(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=He(n),o=await this.http.post("/auth-service/passkeys/register/finish",{response:i,name:e},this.bearerHeaders());if(!o.success||!o.data)throw new u(o.error?.code??"PASSKEY_FINISH_FAILED",o.error?.message??"Passkey verification failed");return o.data}signInWithPasskey(e){return this.serialize(async()=>{if(typeof navigator>"u"||!navigator.credentials)throw new u("PASSKEY_UNSUPPORTED","WebAuthn not supported in this environment");this.user!==null&&(await this.analytics.flush(),this.wipeLocalIdentity());let s=await this.http.post("/auth-service/passkeys/authenticate/start",{userId:e??null});if(!s.success||!s.data)throw new u(s.error?.code??"PASSKEY_START_FAILED",s.error?.message??"Passkey start failed");let n=Fe(s.data),i;try{let c=await navigator.credentials.get({publicKey:n});if(!c)throw new Error("get returned null");i=c}catch(c){throw new u("PASSKEY_USER_CANCELLED",c instanceof Error?c.message:"User cancelled passkey signin")}let o=$e(i),a=await this.http.post("/auth-service/passkeys/authenticate/finish",{response:o,userId:e??null});if(!a.success||!m(a.data))throw new u(a.error?.code??"PASSKEY_AUTH_FAILED",a.error?.message??"Passkey signin failed");return this.persist(a.data),a.data.user})}async listPasskeys(){let e=await this.http.get("/auth-service/passkeys",this.bearerHeaders());return!e.success||!Array.isArray(e.data)?[]:e.data}async deletePasskey(e){await this.http.delete(`/auth-service/passkeys/${e}`,this.bearerHeaders())}async listMySessions(){let e=await this.http.get("/auth-service/sessions/me",this.bearerHeaders());return!e.success||!Array.isArray(e.data)?[]:e.data}async revokeSession(e){await this.http.delete(`/auth-service/sessions/me/${e}`,this.bearerHeaders())}async revokeAllSessions(){await this.http.delete("/auth-service/sessions/me",this.bearerHeaders())}async startSso(e){let t=await this.http.post("/auth-service/sso/oidc/start",{returnTo:e});if(!t.success||!t.data)throw new u(t.error?.code??"SSO_START_FAILED",t.error?.message??"SSO start failed");return t.data}async signInWithSso(e){let t=e??(typeof window<"u"?window.location.origin:"");if(!t)throw new u("SSO_NO_RETURN_TO","returnTo required outside browser context");let s=await this.startSso(t);typeof window<"u"&&(window.location.href=s.authorizationUrl)}consumeSsoTokenFromUrl(){return this.serialize(async()=>{if(typeof window>"u")return null;let e=window.location.hash.replace(/^#/,"");if(!e)return null;let t=new URLSearchParams(e),s=t.get("sendora_oidc_error");if(s)throw window.history.replaceState(null,"",window.location.pathname+window.location.search),new u("SSO_FAILED",decodeURIComponent(s));let n=t.get("sendora_oidc_token");if(!n)return null;window.history.replaceState(null,"",window.location.pathname+window.location.search),this.user!==null&&(await this.analytics.flush(),this.wipeLocalIdentity());let i=await this.http.post("/auth-service/token/refresh",{refreshToken:n});if(!i.success||!i.data)throw new u(i.error?.code??"SSO_EXCHANGE_FAILED",i.error?.message??"Could not exchange SSO token");let o=i.data;this.accessToken=o.accessToken,this.accessExpiresAt=Date.now()+o.expiresIn*1e3,this.storage.set(L,o.accessToken),this.storage.set(D,String(this.accessExpiresAt)),this.storage.set(f,o.refreshToken);let a=Ke(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(H,JSON.stringify(c)),this.analytics.identify(c.id),this.flags.setContext({userId:c.id}),c})}bearerHeaders(){if(!this.accessToken)throw new u("NOT_SIGNED_IN","Not signed in");return{Authorization:`Bearer ${this.accessToken}`}}signOut(){return this.serialize(async()=>{let e=this.storage.get(f);this.wipeLocalIdentity(),e&&this.http.post("/auth-service/token/revoke",{refreshToken:e}).catch(()=>{})})}serialize(e){let t=this.inflight.then(e,e);return this.inflight=t.catch(()=>{}),t}async refreshAccessToken(){if(this.refreshInflight)return this.refreshInflight;let e=this.storage.get(f);return e?(this.refreshInflight=(async()=>{try{let t=await this.http.post("/auth-service/token/refresh",{refreshToken:e});if(!t.success)return Ne(t.error?.code)&&this.wipeLocalIdentity(),null;if(!t.data||typeof t.data!="object"||typeof t.data.accessToken!="string"||typeof t.data.refreshToken!="string"||typeof t.data.expiresIn!="number")return null;let s=t.data;return this.accessToken=s.accessToken,this.accessExpiresAt=Date.now()+s.expiresIn*1e3,this.storage.set(L,s.accessToken),this.storage.set(D,String(this.accessExpiresAt)),this.storage.set(f,s.refreshToken),s.accessToken}catch{return null}finally{this.refreshInflight=null}})(),this.refreshInflight):null}persist(e){this.user=e.user,this.accessToken=e.tokens.accessToken,this.accessExpiresAt=Date.now()+e.tokens.expiresIn*1e3,this.storage.set(H,JSON.stringify(e.user)),this.storage.set(L,e.tokens.accessToken),this.storage.set(D,String(this.accessExpiresAt)),this.storage.set(f,e.tokens.refreshToken),this.analytics.identify(e.user.id),this.flags.setContext({userId:e.user.id})}wipeLocalIdentity(){this.user=null,this.accessToken=null,this.accessExpiresAt=0,this.storage.remove(H),this.storage.remove(L),this.storage.remove(D),this.storage.remove(f),this.analytics.reset(),this.analytics.dropQueue(),this.flags.reset(),this.chatbot.newSession()}};function Ne(r){return r?r==="INVALID_REFRESH_TOKEN"||r==="UNAUTHORIZED"||r==="HTTP_401"||r==="RATE_LIMIT_EXCEEDED"||r==="RATE_LIMIT":!1}function K(r){let e=(4-r.length%4)%4,t=(r+"===".slice(0,e)).replace(/-/g,"+").replace(/_/g,"/"),s=atob(t),n=new ArrayBuffer(s.length),i=new Uint8Array(n);for(let o=0;o<s.length;o++)i[o]=s.charCodeAt(o);return n}function A(r){let e=r instanceof Uint8Array?r:new Uint8Array(r),t="";for(let s=0;s<e.length;s++)t+=String.fromCharCode(e[s]);return btoa(t).replace(/\+/g,"-").replace(/\//g,"_").replace(/=+$/,"")}function Me(r){return{challenge:K(r.challenge),rp:r.rp,user:{id:K(r.user.id),name:r.user.name,displayName:r.user.displayName},pubKeyCredParams:r.pubKeyCredParams,timeout:r.timeout,attestation:r.attestation,excludeCredentials:r.excludeCredentials?.map(e=>({id:K(e.id),type:"public-key",transports:e.transports})),authenticatorSelection:r.authenticatorSelection}}function Fe(r){return{challenge:K(r.challenge),timeout:r.timeout,rpId:r.rpId,allowCredentials:r.allowCredentials?.map(e=>({id:K(e.id),type:"public-key",transports:e.transports})),userVerification:r.userVerification}}function He(r){let e=r.response;return{id:r.id,rawId:A(r.rawId),type:r.type,response:{clientDataJSON:A(e.clientDataJSON),attestationObject:A(e.attestationObject),transports:typeof e.getTransports=="function"?e.getTransports():void 0},clientExtensionResults:r.getClientExtensionResults()}}function Ke(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:A(r.rawId),type:r.type,response:{clientDataJSON:A(e.clientDataJSON),authenticatorData:A(e.authenticatorData),signature:A(e.signature),userHandle:e.userHandle?A(e.userHandle):null},clientExtensionResults:r.getClientExtensionResults()}}function Ve(r){return r===!1?{pageViews:!1,sessionStart:!1,appOpen:!1,appBackground:!1}:r===!0||r===void 0?{pageViews:!0,sessionStart:!0,appOpen:!0,appBackground:!0}:{pageViews:r.pageViews??!0,sessionStart:r.sessionStart??!0,appOpen:r.appOpen??!0,appBackground:r.appBackground??!0}}function pe(r,e){if(typeof window>"u")return()=>{};let t=Ve(e.autoTrack),s=e.sessionIdleMs??18e5,n=[],i="sendora_session_id",o="sendora_session_last_activity";function a(){return de(i)??""}function c(l){he(i,l)}function h(){let l=de(o);return l&&Number(l)||0}function I(l){he(o,String(l))}function g(){return`sess_${Date.now().toString(36)}_${Math.random().toString(36).slice(2,10)}`}function y(l){let S=Date.now(),F=h();if(!a()||F&&S-F>s){let $=g();c($),l&&t.sessionStart&&r.track("session.start",{sessionId:$})}I(S)}if(y(t.sessionStart),t.appOpen&&r.track("app.opened",{sessionId:a()||void 0,url:window.location.origin+window.location.pathname,referrer:document.referrer||void 0}),t.pageViews){r.page();let l=()=>{y(!0),r.page()},S=()=>l();window.addEventListener("popstate",S),n.push(()=>window.removeEventListener("popstate",S));let F=window;if(!F.__sendoraHistoryPatched){F.__sendoraHistoryPatched=!0;let $=history.pushState,fe=history.replaceState;history.pushState=function(...Y){let Q=$.apply(this,Y);return window.dispatchEvent(new Event("sendora:locationchange")),Q},history.replaceState=function(...Y){let Q=fe.apply(this,Y);return window.dispatchEvent(new Event("sendora:locationchange")),Q}}let G=()=>l();window.addEventListener("sendora:locationchange",G),n.push(()=>window.removeEventListener("sendora:locationchange",G))}if(t.appBackground){let l=()=>{y(!0),document.visibilityState==="hidden"?r.track("app.backgrounded",{sessionId:a()||void 0}):document.visibilityState==="visible"&&r.track("app.foregrounded",{sessionId:a()||void 0})};document.addEventListener("visibilitychange",l),n.push(()=>document.removeEventListener("visibilitychange",l))}let w=0,v=()=>{let l=Date.now();l-w<5e3||(w=l,y(!0))};return window.addEventListener("click",v,{passive:!0}),window.addEventListener("keydown",v,{passive:!0}),window.addEventListener("scroll",v,{passive:!0}),n.push(()=>{window.removeEventListener("click",v),window.removeEventListener("keydown",v),window.removeEventListener("scroll",v)}),()=>{for(let l of n)try{l()}catch{}}}function de(r){try{return window.sessionStorage?.getItem(r)??null}catch{return null}}function he(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:We(i)}),c=a.toJSON(),h=c.endpoint,I=c.keys?.p256dh??"",g=c.keys?.auth??"",y=await Be(h),w=await this.http.post("/push/tokens",{orgId:this.orgId,platform:"web",token:y,webEndpoint:h,webP256dh:I,webAuth:g,userAgent:typeof navigator<"u"?navigator.userAgent:void 0,locale:typeof navigator<"u"?navigator.language:void 0,timezone:Intl?.DateTimeFormat?.().resolvedOptions?.().timeZone,...e.userId?{userId:e.userId}:{}});if(!w?.data?.tokenId)throw new Error("[sendoracloud] backend returned no tokenId on subscribe.");return{tokenId:w.data.tokenId,subscription:a}}async unsubscribe(e="/"){if(!r.isSupported())return!1;let t=await navigator.serviceWorker.getRegistration(e);if(!t)return!1;let s=await t.pushManager.getSubscription();return s?await s.unsubscribe():!1}};function We(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 Be(r){let e=new TextEncoder().encode(r),t=await crypto.subtle.digest("SHA-256",e);return Array.from(new Uint8Array(t)).map(n=>n.toString(16).padStart(2,"0")).join("")}var N=class{constructor(e,t){this.http=e,this.orgId=t}setOrgId(e){this.orgId=e}base(){if(!this.orgId)throw new Error("Audiences API requires orgId \u2014 pass it via SendoraCloud.init({ orgId }) or call audiences.setOrgId() before use.");return`/orgs/${this.orgId}/audiences`}async list(){let e=await this.http.get(this.base());return e.success?e.data??[]:[]}async get(e){let t=await this.http.get(`${this.base()}/${e}`);return t.success?t.data??null:null}async create(e){let t=await this.http.post(this.base(),e);return t.success?t.data??null:null}async update(e,t){let s=await this.http.patch(`${this.base()}/${e}`,t);return s.success?s.data??null:null}async delete(e){return(await this.http.delete(`${this.base()}/${e}`)).success}};var d=class extends Error{constructor(e,t,s){super(t),this.name="LinkError",this.code=e,this.statusCode=s}};function Z(r,e){let t=(r??"").toUpperCase();return t==="NOT_FOUND"?new d("NOT_FOUND",e):t==="FORBIDDEN"||t==="UNAUTHORIZED"?new d("UNAUTHORIZED",e):t==="RATE_LIMITED"?new d("RATE_LIMITED",e):t==="ENTITLEMENT_ERROR"||/plan limit/i.test(e)?new d("PLAN_LIMIT",e):t==="VALIDATION_ERROR"?/iOS bundle|Android package/i.test(e)?new d("BUNDLE_MISMATCH",e):/2KB|10KB|linkData/i.test(e)?new d("DATA_TOO_LARGE",e):/fallbackUrl/i.test(e)&&/apps/i.test(e)?new d("FALLBACK_REQUIRED",e):new d("INVALID_INPUT",e):t==="PARSE_ERROR"||t==="HTTP_ERROR"||/HTTP \d{3}/.test(e)?new d("NETWORK",e):new d("UNKNOWN",e||`Unknown LinkError (code=${r??"none"})`)}var ee=/^[a-z0-9-]{3,20}$/;function ge(r){try{let t=new URL(r).pathname.split("/").filter(Boolean);if(t.length===0)return null;let s=t[t.length-1];return ee.test(s)?s:null}catch{return null}}var ze=5*6e4,qe=50,M=class{constructor(e){this.http=e;this.prewarmCache=new Map}cacheKey(e,t){if(t)return`k:${t}`;let s={},n=e;for(let i of Object.keys(n).sort())s[i]=n[i];return`s:${JSON.stringify(s)}`}evictExpired(){let e=Date.now();for(let[t,s]of this.prewarmCache)e-s.ts>ze&&this.prewarmCache.delete(t);for(;this.prewarmCache.size>qe;){let t=this.prewarmCache.keys().next().value;if(t===void 0)break;this.prewarmCache.delete(t)}}prewarm(e,t){this.evictExpired();let s=this.cacheKey(e,t?.key);if(this.prewarmCache.has(s))return;let n=this.doCreate(e).catch(i=>{throw this.prewarmCache.delete(s),i});this.prewarmCache.set(s,{promise:n,ts:Date.now()})}async create(e,t){if(!e.title)throw new d("INVALID_INPUT","links.create: title is required");this.evictExpired();let s=this.cacheKey(e,t?.key),n=this.prewarmCache.get(s);return n?(this.prewarmCache.delete(s),n.promise):this.doCreate(e)}async doCreate(e){let t={title:e.title};e.fallbackUrl&&(t.fallbackUrl=e.fallbackUrl),e.iosDeepLinkPath&&(t.iosDeepLinkPath=e.iosDeepLinkPath),e.androidDeepLinkPath&&(t.androidDeepLinkPath=e.androidDeepLinkPath),e.linkData&&(t.linkData=e.linkData),e.ogTitle&&(t.ogTitle=e.ogTitle),e.ogDescription&&(t.ogDescription=e.ogDescription),e.ogImageUrl&&(t.ogImageUrl=e.ogImageUrl),e.campaign&&(t.campaign=e.campaign),e.source&&(t.source=e.source),e.medium&&(t.medium=e.medium),e.channel&&(t.channel=e.channel),e.tags&&(t.tags=e.tags),e.expiresAt&&(t.expiresAt=e.expiresAt),e.iosBundleId&&(t.iosBundleId=e.iosBundleId),e.androidPackageName&&(t.androidPackageName=e.androidPackageName);let s=await this.http.post("/sdk/links",t);if(!s.success||!s.data)throw Z(s.error?.code,s.error?.message??"links.create failed");return s.data}async revoke(e){if(!ee.test(e))throw new d("INVALID_INPUT",`links.revoke: '${e}' is not a valid shortcode`);let t=await this.http.post(`/sdk/links/${encodeURIComponent(e)}/revoke`,{});if(!t.success||!t.data)throw Z(t.error?.code,t.error?.message??"links.revoke failed");return{shortcode:t.data.shortcode,revoked:!0}}async getStats(e){if(!ee.test(e))throw new d("INVALID_INPUT",`links.getStats: '${e}' is not a valid shortcode`);let t=await this.http.get(`/sdk/links/${encodeURIComponent(e)}/stats`);if(!t.success||!t.data)throw Z(t.error?.code,t.error?.message??"links.getStats failed");return t.data}};var je="2.13.0",j=class j{constructor(e){this.autoTrackCleanup=null;this.http=new V(e),this.storage=new B,this.consent=new C(e.defaultConsent===!0),this.analytics=new E(e,this.http,this.storage,this.consent),this.flags=new T(e,this.http,this.storage),this.surveys=new R(e,this.http,this.storage),this.messages=new _(e,this.http,this.storage),this.chatbot=new P(e,this.http,this.storage),this.auth=new O(e,this.http,this.storage,this.analytics,this.flags,this.chatbot),this.webPush=new x(this.http,e.orgId),this.audiences=new N(this.http,e.orgId),this.links=new M(this.http),e.autoTrack!==!1&&(this.autoTrackCleanup=pe(this.analytics,e))}static init(e){return oe(e.apiKey),new j(e)}static sanitizeHtml(e){return z(e)}static safeUrl(e){return k(e)}track(e,t){this.analytics.track(e,t)}page(e){this.analytics.page(e)}identify(e,t,s){this.analytics.identify(e,t,s),this.flags.setContext({userId:e})}reset(){this.analytics.reset(),this.flags.reset(),this.chatbot.newSession()}async flush(){await this.analytics.flush()}destroy(){this.autoTrackCleanup&&(this.autoTrackCleanup(),this.autoTrackCleanup=null),this.analytics.destroy(),this.flags.destroy(),this.messages.destroy()}};j.version=je;var q=j,Ge=q;0&&(module.exports={Analytics,Audiences,Auth,AuthError,Chatbot,Consent,EmailAlreadyTakenError,FeatureFlags,InAppMessaging,LinkError,Links,SendoraCloud,Surveys,ValidationError,WebPush,extractShortcodeFromUrl,safeUrl,sanitizeHtml});
|
package/dist/index.d.cts
CHANGED
|
@@ -734,6 +734,106 @@ declare class Audiences {
|
|
|
734
734
|
delete(audienceId: string): Promise<boolean>;
|
|
735
735
|
}
|
|
736
736
|
|
|
737
|
+
/**
|
|
738
|
+
* Sendora Deep Links surface for the Web SDK (s58.51).
|
|
739
|
+
*
|
|
740
|
+
* Web is intentionally narrower than the mobile SDKs — no Universal Links
|
|
741
|
+
* / App Links to handle, no deferred-install match. Web exposes the
|
|
742
|
+
* mint + revoke + stats moves so a browser app can:
|
|
743
|
+
* 1. Generate share URLs for paste-into-iMessage / WhatsApp / SMS flows.
|
|
744
|
+
* 2. Background-mint via `prewarm()` on row mount so a Share-button
|
|
745
|
+
* click skips the HTTPS round-trip.
|
|
746
|
+
* 3. Revoke (private-content unsend) + pull stats without dashboard scrape.
|
|
747
|
+
*
|
|
748
|
+
* Typed `linkData<T>` + `LinkError` mirror the RN / iOS / Android SDKs.
|
|
749
|
+
*/
|
|
750
|
+
|
|
751
|
+
type LinkErrorCode = "BUNDLE_MISMATCH" | "DATA_TOO_LARGE" | "EXPIRED" | "NETWORK" | "RATE_LIMITED" | "NOT_FOUND" | "UNAUTHORIZED" | "INVALID_INPUT" | "PLAN_LIMIT" | "FALLBACK_REQUIRED" | "SERVER" | "UNKNOWN";
|
|
752
|
+
declare class LinkError extends Error {
|
|
753
|
+
readonly code: LinkErrorCode;
|
|
754
|
+
readonly statusCode?: number;
|
|
755
|
+
constructor(code: LinkErrorCode, message: string, statusCode?: number);
|
|
756
|
+
}
|
|
757
|
+
type LinkData = Record<string, unknown>;
|
|
758
|
+
interface LinkCreateInput<T extends LinkData = LinkData> {
|
|
759
|
+
title: string;
|
|
760
|
+
iosDeepLinkPath?: string;
|
|
761
|
+
androidDeepLinkPath?: string;
|
|
762
|
+
/** Optional — backend defaults from your project's apps registry. */
|
|
763
|
+
fallbackUrl?: string;
|
|
764
|
+
linkData?: T;
|
|
765
|
+
ogTitle?: string;
|
|
766
|
+
ogDescription?: string;
|
|
767
|
+
ogImageUrl?: string;
|
|
768
|
+
campaign?: string;
|
|
769
|
+
source?: string;
|
|
770
|
+
medium?: string;
|
|
771
|
+
channel?: string;
|
|
772
|
+
tags?: string[];
|
|
773
|
+
expiresAt?: string;
|
|
774
|
+
/** Web rarely sets these; pass to scope the bundle-id gate when
|
|
775
|
+
* minting links destined for a specific mobile bundle. */
|
|
776
|
+
iosBundleId?: string;
|
|
777
|
+
androidPackageName?: string;
|
|
778
|
+
}
|
|
779
|
+
interface LinkCreateResult<T extends LinkData = LinkData> {
|
|
780
|
+
id: string;
|
|
781
|
+
shortcode: string;
|
|
782
|
+
url: string;
|
|
783
|
+
iosDeepLinkPath: string | null;
|
|
784
|
+
androidDeepLinkPath: string | null;
|
|
785
|
+
fallbackUrl: string;
|
|
786
|
+
linkData: T;
|
|
787
|
+
expiresAt: string | null;
|
|
788
|
+
}
|
|
789
|
+
interface LinkStats {
|
|
790
|
+
totalClicks: number;
|
|
791
|
+
uniqueClicks: number;
|
|
792
|
+
deferredMatches: number;
|
|
793
|
+
byDevice: Array<{
|
|
794
|
+
deviceType: string | null;
|
|
795
|
+
count: number;
|
|
796
|
+
}>;
|
|
797
|
+
byCountry: Array<{
|
|
798
|
+
country: string | null;
|
|
799
|
+
count: number;
|
|
800
|
+
}>;
|
|
801
|
+
byOs: Array<{
|
|
802
|
+
os: string | null;
|
|
803
|
+
count: number;
|
|
804
|
+
}>;
|
|
805
|
+
}
|
|
806
|
+
/** Extract a Sendora shortcode from a share URL. Strips an optional
|
|
807
|
+
* leading `/link/` segment. Returns `null` for non-Sendora-shaped URLs. */
|
|
808
|
+
declare function extractShortcodeFromUrl(url: string): string | null;
|
|
809
|
+
declare class Links {
|
|
810
|
+
private http;
|
|
811
|
+
private prewarmCache;
|
|
812
|
+
constructor(http: HttpClient);
|
|
813
|
+
/** Stable cache key for prewarm match. Customer can override. */
|
|
814
|
+
private cacheKey;
|
|
815
|
+
private evictExpired;
|
|
816
|
+
/**
|
|
817
|
+
* Background-mint a link + cache the promise so a subsequent `create()`
|
|
818
|
+
* with the same input (or matching `key`) returns instantly.
|
|
819
|
+
*/
|
|
820
|
+
prewarm<T extends LinkData = LinkData>(input: LinkCreateInput<T>, opts?: {
|
|
821
|
+
key?: string;
|
|
822
|
+
}): void;
|
|
823
|
+
/** Mint a new short link. Returns cached prewarm promise when keys match. */
|
|
824
|
+
create<T extends LinkData = LinkData>(input: LinkCreateInput<T>, opts?: {
|
|
825
|
+
key?: string;
|
|
826
|
+
}): Promise<LinkCreateResult<T>>;
|
|
827
|
+
private doCreate;
|
|
828
|
+
/** Soft-delete a link. Idempotent — repeated calls succeed. */
|
|
829
|
+
revoke(shortcode: string): Promise<{
|
|
830
|
+
shortcode: string;
|
|
831
|
+
revoked: true;
|
|
832
|
+
}>;
|
|
833
|
+
/** Click totals + breakdowns for a link the SDK owns. */
|
|
834
|
+
getStats(shortcode: string): Promise<LinkStats>;
|
|
835
|
+
}
|
|
836
|
+
|
|
737
837
|
/**
|
|
738
838
|
* Return the URL if it is http/https; otherwise undefined.
|
|
739
839
|
* Rejects javascript:, data:, vbscript:, and other script-bearing schemes.
|
|
@@ -787,6 +887,11 @@ declare class SendoraCloud {
|
|
|
787
887
|
readonly auth: Auth;
|
|
788
888
|
readonly webPush: WebPush;
|
|
789
889
|
readonly audiences: Audiences;
|
|
890
|
+
/** Deep links surface (Branch / Firebase parity — web subset).
|
|
891
|
+
* Mint share URLs, prewarm, revoke, getStats. No
|
|
892
|
+
* `handleUniversalLink` / `matchDeferred` — those are mobile-only.
|
|
893
|
+
* Added 2.13.0. */
|
|
894
|
+
readonly links: Links;
|
|
790
895
|
private http;
|
|
791
896
|
private storage;
|
|
792
897
|
private autoTrackCleanup;
|
|
@@ -798,7 +903,7 @@ declare class SendoraCloud {
|
|
|
798
903
|
/** Return the URL if it's http(s); undefined otherwise. */
|
|
799
904
|
static safeUrl(u?: string): string | undefined;
|
|
800
905
|
/** SDK version. */
|
|
801
|
-
static readonly version = "2.
|
|
906
|
+
static readonly version = "2.13.0";
|
|
802
907
|
track(eventType: string, properties?: Record<string, unknown>): void;
|
|
803
908
|
page(properties?: Record<string, unknown>): void;
|
|
804
909
|
identify(userId: string, traits?: UserTraits, opts?: IdentifyOptions): void;
|
|
@@ -807,4 +912,4 @@ declare class SendoraCloud {
|
|
|
807
912
|
destroy(): void;
|
|
808
913
|
}
|
|
809
914
|
|
|
810
|
-
export { Analytics, type Audience, type AudienceRule, type AudienceRuleGroup, Audiences, Auth, AuthError, type AuthTokens, type AuthUser, Chatbot, Consent, type CreateAudienceInput, EmailAlreadyTakenError, FeatureFlags, type FlagValue, type IdentifyOptions, type InAppMessage, InAppMessaging, SendoraCloud, type SendoraConfig, type SurveyConfig, type SurveyQuestion, Surveys, type TrackEvent, type UpdateAudienceInput, type UserTraits, ValidationError, WebPush, SendoraCloud as default, safeUrl, sanitizeHtml };
|
|
915
|
+
export { Analytics, type Audience, type AudienceRule, type AudienceRuleGroup, Audiences, Auth, AuthError, type AuthTokens, type AuthUser, Chatbot, Consent, type CreateAudienceInput, EmailAlreadyTakenError, FeatureFlags, type FlagValue, type IdentifyOptions, type InAppMessage, InAppMessaging, type LinkCreateInput, type LinkCreateResult, type LinkData, LinkError, type LinkErrorCode, type LinkStats, Links, SendoraCloud, type SendoraConfig, type SurveyConfig, type SurveyQuestion, Surveys, type TrackEvent, type UpdateAudienceInput, type UserTraits, ValidationError, WebPush, SendoraCloud as default, extractShortcodeFromUrl, safeUrl, sanitizeHtml };
|
package/dist/index.d.ts
CHANGED
|
@@ -734,6 +734,106 @@ declare class Audiences {
|
|
|
734
734
|
delete(audienceId: string): Promise<boolean>;
|
|
735
735
|
}
|
|
736
736
|
|
|
737
|
+
/**
|
|
738
|
+
* Sendora Deep Links surface for the Web SDK (s58.51).
|
|
739
|
+
*
|
|
740
|
+
* Web is intentionally narrower than the mobile SDKs — no Universal Links
|
|
741
|
+
* / App Links to handle, no deferred-install match. Web exposes the
|
|
742
|
+
* mint + revoke + stats moves so a browser app can:
|
|
743
|
+
* 1. Generate share URLs for paste-into-iMessage / WhatsApp / SMS flows.
|
|
744
|
+
* 2. Background-mint via `prewarm()` on row mount so a Share-button
|
|
745
|
+
* click skips the HTTPS round-trip.
|
|
746
|
+
* 3. Revoke (private-content unsend) + pull stats without dashboard scrape.
|
|
747
|
+
*
|
|
748
|
+
* Typed `linkData<T>` + `LinkError` mirror the RN / iOS / Android SDKs.
|
|
749
|
+
*/
|
|
750
|
+
|
|
751
|
+
type LinkErrorCode = "BUNDLE_MISMATCH" | "DATA_TOO_LARGE" | "EXPIRED" | "NETWORK" | "RATE_LIMITED" | "NOT_FOUND" | "UNAUTHORIZED" | "INVALID_INPUT" | "PLAN_LIMIT" | "FALLBACK_REQUIRED" | "SERVER" | "UNKNOWN";
|
|
752
|
+
declare class LinkError extends Error {
|
|
753
|
+
readonly code: LinkErrorCode;
|
|
754
|
+
readonly statusCode?: number;
|
|
755
|
+
constructor(code: LinkErrorCode, message: string, statusCode?: number);
|
|
756
|
+
}
|
|
757
|
+
type LinkData = Record<string, unknown>;
|
|
758
|
+
interface LinkCreateInput<T extends LinkData = LinkData> {
|
|
759
|
+
title: string;
|
|
760
|
+
iosDeepLinkPath?: string;
|
|
761
|
+
androidDeepLinkPath?: string;
|
|
762
|
+
/** Optional — backend defaults from your project's apps registry. */
|
|
763
|
+
fallbackUrl?: string;
|
|
764
|
+
linkData?: T;
|
|
765
|
+
ogTitle?: string;
|
|
766
|
+
ogDescription?: string;
|
|
767
|
+
ogImageUrl?: string;
|
|
768
|
+
campaign?: string;
|
|
769
|
+
source?: string;
|
|
770
|
+
medium?: string;
|
|
771
|
+
channel?: string;
|
|
772
|
+
tags?: string[];
|
|
773
|
+
expiresAt?: string;
|
|
774
|
+
/** Web rarely sets these; pass to scope the bundle-id gate when
|
|
775
|
+
* minting links destined for a specific mobile bundle. */
|
|
776
|
+
iosBundleId?: string;
|
|
777
|
+
androidPackageName?: string;
|
|
778
|
+
}
|
|
779
|
+
interface LinkCreateResult<T extends LinkData = LinkData> {
|
|
780
|
+
id: string;
|
|
781
|
+
shortcode: string;
|
|
782
|
+
url: string;
|
|
783
|
+
iosDeepLinkPath: string | null;
|
|
784
|
+
androidDeepLinkPath: string | null;
|
|
785
|
+
fallbackUrl: string;
|
|
786
|
+
linkData: T;
|
|
787
|
+
expiresAt: string | null;
|
|
788
|
+
}
|
|
789
|
+
interface LinkStats {
|
|
790
|
+
totalClicks: number;
|
|
791
|
+
uniqueClicks: number;
|
|
792
|
+
deferredMatches: number;
|
|
793
|
+
byDevice: Array<{
|
|
794
|
+
deviceType: string | null;
|
|
795
|
+
count: number;
|
|
796
|
+
}>;
|
|
797
|
+
byCountry: Array<{
|
|
798
|
+
country: string | null;
|
|
799
|
+
count: number;
|
|
800
|
+
}>;
|
|
801
|
+
byOs: Array<{
|
|
802
|
+
os: string | null;
|
|
803
|
+
count: number;
|
|
804
|
+
}>;
|
|
805
|
+
}
|
|
806
|
+
/** Extract a Sendora shortcode from a share URL. Strips an optional
|
|
807
|
+
* leading `/link/` segment. Returns `null` for non-Sendora-shaped URLs. */
|
|
808
|
+
declare function extractShortcodeFromUrl(url: string): string | null;
|
|
809
|
+
declare class Links {
|
|
810
|
+
private http;
|
|
811
|
+
private prewarmCache;
|
|
812
|
+
constructor(http: HttpClient);
|
|
813
|
+
/** Stable cache key for prewarm match. Customer can override. */
|
|
814
|
+
private cacheKey;
|
|
815
|
+
private evictExpired;
|
|
816
|
+
/**
|
|
817
|
+
* Background-mint a link + cache the promise so a subsequent `create()`
|
|
818
|
+
* with the same input (or matching `key`) returns instantly.
|
|
819
|
+
*/
|
|
820
|
+
prewarm<T extends LinkData = LinkData>(input: LinkCreateInput<T>, opts?: {
|
|
821
|
+
key?: string;
|
|
822
|
+
}): void;
|
|
823
|
+
/** Mint a new short link. Returns cached prewarm promise when keys match. */
|
|
824
|
+
create<T extends LinkData = LinkData>(input: LinkCreateInput<T>, opts?: {
|
|
825
|
+
key?: string;
|
|
826
|
+
}): Promise<LinkCreateResult<T>>;
|
|
827
|
+
private doCreate;
|
|
828
|
+
/** Soft-delete a link. Idempotent — repeated calls succeed. */
|
|
829
|
+
revoke(shortcode: string): Promise<{
|
|
830
|
+
shortcode: string;
|
|
831
|
+
revoked: true;
|
|
832
|
+
}>;
|
|
833
|
+
/** Click totals + breakdowns for a link the SDK owns. */
|
|
834
|
+
getStats(shortcode: string): Promise<LinkStats>;
|
|
835
|
+
}
|
|
836
|
+
|
|
737
837
|
/**
|
|
738
838
|
* Return the URL if it is http/https; otherwise undefined.
|
|
739
839
|
* Rejects javascript:, data:, vbscript:, and other script-bearing schemes.
|
|
@@ -787,6 +887,11 @@ declare class SendoraCloud {
|
|
|
787
887
|
readonly auth: Auth;
|
|
788
888
|
readonly webPush: WebPush;
|
|
789
889
|
readonly audiences: Audiences;
|
|
890
|
+
/** Deep links surface (Branch / Firebase parity — web subset).
|
|
891
|
+
* Mint share URLs, prewarm, revoke, getStats. No
|
|
892
|
+
* `handleUniversalLink` / `matchDeferred` — those are mobile-only.
|
|
893
|
+
* Added 2.13.0. */
|
|
894
|
+
readonly links: Links;
|
|
790
895
|
private http;
|
|
791
896
|
private storage;
|
|
792
897
|
private autoTrackCleanup;
|
|
@@ -798,7 +903,7 @@ declare class SendoraCloud {
|
|
|
798
903
|
/** Return the URL if it's http(s); undefined otherwise. */
|
|
799
904
|
static safeUrl(u?: string): string | undefined;
|
|
800
905
|
/** SDK version. */
|
|
801
|
-
static readonly version = "2.
|
|
906
|
+
static readonly version = "2.13.0";
|
|
802
907
|
track(eventType: string, properties?: Record<string, unknown>): void;
|
|
803
908
|
page(properties?: Record<string, unknown>): void;
|
|
804
909
|
identify(userId: string, traits?: UserTraits, opts?: IdentifyOptions): void;
|
|
@@ -807,4 +912,4 @@ declare class SendoraCloud {
|
|
|
807
912
|
destroy(): void;
|
|
808
913
|
}
|
|
809
914
|
|
|
810
|
-
export { Analytics, type Audience, type AudienceRule, type AudienceRuleGroup, Audiences, Auth, AuthError, type AuthTokens, type AuthUser, Chatbot, Consent, type CreateAudienceInput, EmailAlreadyTakenError, FeatureFlags, type FlagValue, type IdentifyOptions, type InAppMessage, InAppMessaging, SendoraCloud, type SendoraConfig, type SurveyConfig, type SurveyQuestion, Surveys, type TrackEvent, type UpdateAudienceInput, type UserTraits, ValidationError, WebPush, SendoraCloud as default, safeUrl, sanitizeHtml };
|
|
915
|
+
export { Analytics, type Audience, type AudienceRule, type AudienceRuleGroup, Audiences, Auth, AuthError, type AuthTokens, type AuthUser, Chatbot, Consent, type CreateAudienceInput, EmailAlreadyTakenError, FeatureFlags, type FlagValue, type IdentifyOptions, type InAppMessage, InAppMessaging, type LinkCreateInput, type LinkCreateResult, type LinkData, LinkError, type LinkErrorCode, type LinkStats, Links, SendoraCloud, type SendoraConfig, type SurveyConfig, type SurveyQuestion, Surveys, type TrackEvent, type UpdateAudienceInput, type UserTraits, ValidationError, WebPush, SendoraCloud as default, extractShortcodeFromUrl, safeUrl, sanitizeHtml };
|
package/dist/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
var Q=/^[a-zA-Z][a-zA-Z0-9._:-]{0,127}$/;var le=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=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(le.has(e))throw new h(`Forbidden property key "${e}" (prototype pollution guard).`);ee(r[e])}}var de=/^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(!de.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 he="https://api.sendoracloud.com",pe=1e4,K=class{constructor(e){let t=(e.apiUrl??he).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)}patch(e,t,s){return this.request("PATCH",e,t,s)}delete(e,t){return this.request("DELETE",e,void 0,t)}async request(e,t,s,n){let i=`${this.baseUrl}/api/v1${t}`,o=typeof AbortController<"u"?new AbortController:null,a=o?setTimeout(()=>o.abort(),pe):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 p=d?.error?.code??"HTTP_ERROR",m=d?.error?.message??`HTTP ${u.status}`,y=d?.error?.details?.fieldErrors,w=y?` (${Object.entries(y).map(([l,I])=>`${l}: ${(I??[]).join(", ")}`).join("; ")})`:"";console.warn(`[SendoraCloud] ${e} ${t} \u2192 HTTP ${u.status} \u2014 ${p}: ${m}${w}`)}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 $="sendora_",V=class{constructor(){this.memoryStore=new Map;this.useLocalStorage=this.checkLocalStorage()}get(e){let t=$+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=$+e;if(this.useLocalStorage)try{localStorage.setItem(s,t);return}catch{}this.memoryStore.set(s,t)}remove(e){let t=$+e;if(this.useLocalStorage)try{localStorage.removeItem(t)}catch{}this.memoryStore.delete(t)}clearPrefix(e){let t=$+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 k(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,ge=10,fe=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=k("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>=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=k("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>ge)return;let n=Math.min(fe,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 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)B(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&&B(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)B(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 B(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 me=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||!ye(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>me)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 ye(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(we)}function we(r){if(r==null||typeof r!="object")return!1;let e=r;return typeof e.id=="string"&&typeof e.text=="string"}function S(r){if(!(!r||typeof r!="string"))try{let e=typeof window<"u"?window.location.href:"https://local/",t=new URL(r,e);return t.protocol==="http:"||t.protocol==="https:"?t.href:void 0}catch{return}}var ve=new Set(["b","i","u","em","strong","a","p","br","span","ul","ol","li","h1","h2","h3","h4"]),Ae={a:new Set(["href","target","rel"])};function Y(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(!ve.has(s)){let i=document.createTextNode(t.textContent??"");t.replaceWith(i);continue}let n=Ae[s]??new Set;for(let i of Array.from(t.attributes)){if(!n.has(i.name.toLowerCase())){t.removeAttribute(i.name);continue}if(i.name.toLowerCase()==="href"){let o=S(i.value);o?t.setAttribute("href",o):t.removeAttribute(i.name)}}s==="a"&&(t.setAttribute("rel","noopener noreferrer"),t.setAttribute("target","_blank")),ie(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(Ie);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 Ie(r){return{...r,content:{...r.content,ctaUrl:S(r.content.ctaUrl),imageUrl:S(r.content.imageUrl)}}}var Se=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??k("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 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=k("chat_"),this.storage.set("chat_session_id",this.sessionId)}getSessionId(){return this.sessionId}};var x=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 b="auth_access_token",E="auth_access_expires",g="auth_refresh_token",O="auth_user",ke=3e4;function f(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"}},c=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(O),u=this.storage.get(b),d=this.storage.get(g),A=this.storage.get(E);if(a&&(u||d))try{let p=JSON.parse(a);if(typeof p.id!="string"||p.id.length===0)throw new Error("invalid cache");this.user=p,u?(this.accessToken=u,this.accessExpiresAt=A?Number(A):0):(this.accessToken=null,this.accessExpiresAt=0),this.analytics.identify(this.user.id)}catch{this.storage.remove(O),this.storage.remove(b),this.storage.remove(E),this.storage.remove(g)}}getCurrentUser(){return this.user}async getAccessToken(){return this.accessToken?this.accessExpiresAt&&Date.now()<this.accessExpiresAt-ke?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||!f(t.data))throw new c(t.error?.code??"AUTH_ERROR",t.error?.message??"Anonymous signup failed");return this.persist(t.data),t.data.user})}signUp(e,t,s){return this.serialize(async()=>{let n=this.storage.get(g);if(n){let o=await this.http.post("/auth-service/upgrade",{refreshToken:n,email:e,password:t,name:s?.name});if(o.success&&f(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 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 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||!f(i.data)){let o=i.error?.code??"";throw o==="CONFLICT"?new M(i.error?.message):new c(o||"AUTH_ERROR",i.error?.message??"Signup failed")}return this.persist(i.data),i.data.user})}signIn(e,t){return this.serialize(async()=>{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(!f(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||!f(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||!f(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||!f(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||!f(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=Ee(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=_e(n),o=await this.http.post("/auth-service/passkeys/register/finish",{response:i,name:e},this.bearerHeaders());if(!o.success||!o.data)throw new 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=Te(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=Re(i),a=await this.http.post("/auth-service/passkeys/authenticate/finish",{response:o,userId:e??null});if(!a.success||!f(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(b,o.accessToken),this.storage.set(E,String(this.accessExpiresAt)),this.storage.set(g,o.refreshToken);let a=Pe(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(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(g);this.wipeLocalIdentity(),e&&this.http.post("/auth-service/token/revoke",{refreshToken:e}).catch(()=>{})})}serialize(e){let t=this.inflight.then(e,e);return this.inflight=t.catch(()=>{}),t}async refreshAccessToken(){if(this.refreshInflight)return this.refreshInflight;let e=this.storage.get(g);return e?(this.refreshInflight=(async()=>{try{let t=await this.http.post("/auth-service/token/refresh",{refreshToken:e});if(!t.success)return be(t.error?.code)&&this.wipeLocalIdentity(),null;if(!t.data||typeof t.data!="object"||typeof t.data.accessToken!="string"||typeof t.data.refreshToken!="string"||typeof t.data.expiresIn!="number")return null;let s=t.data;return this.accessToken=s.accessToken,this.accessExpiresAt=Date.now()+s.expiresIn*1e3,this.storage.set(b,s.accessToken),this.storage.set(E,String(this.accessExpiresAt)),this.storage.set(g,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(b,e.tokens.accessToken),this.storage.set(E,String(this.accessExpiresAt)),this.storage.set(g,e.tokens.refreshToken),this.analytics.identify(e.user.id),this.flags.setContext({userId:e.user.id})}wipeLocalIdentity(){this.user=null,this.accessToken=null,this.accessExpiresAt=0,this.storage.remove(O),this.storage.remove(b),this.storage.remove(E),this.storage.remove(g),this.analytics.reset(),this.analytics.dropQueue(),this.flags.reset(),this.chatbot.newSession()}};function be(r){return r?r==="INVALID_REFRESH_TOKEN"||r==="UNAUTHORIZED"||r==="HTTP_401"||r==="RATE_LIMIT_EXCEEDED"||r==="RATE_LIMIT":!1}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 Ee(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 Te(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 _e(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 Pe(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 Re(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 Ce(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 ue(r,e){if(typeof window>"u")return()=>{};let t=Ce(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 A(l){ae(o,String(l))}function p(){return`sess_${Date.now().toString(36)}_${Math.random().toString(36).slice(2,10)}`}function m(l){let I=Date.now(),T=d();if(!a()||T&&I-T>s){let H=p();u(H),l&&t.sessionStart&&r.track("session.start",{sessionId:H})}A(I)}if(m(t.sessionStart),t.appOpen&&r.track("app.opened",{sessionId:a()||void 0,url:window.location.origin+window.location.pathname,referrer:document.referrer||void 0}),t.pageViews){r.page();let l=()=>{m(!0),r.page()},I=()=>l();window.addEventListener("popstate",I),n.push(()=>window.removeEventListener("popstate",I));let T=window;if(!T.__sendoraHistoryPatched){T.__sendoraHistoryPatched=!0;let H=history.pushState,ce=history.replaceState;history.pushState=function(...q){let j=H.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 z=()=>l();window.addEventListener("sendora:locationchange",z),n.push(()=>window.removeEventListener("sendora:locationchange",z))}if(t.appBackground){let l=()=>{m(!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 y=0,w=()=>{let l=Date.now();l-y<5e3||(y=l,m(!0))};return window.addEventListener("click",w,{passive:!0}),window.addEventListener("keydown",w,{passive:!0}),window.addEventListener("scroll",w,{passive:!0}),n.push(()=>{window.removeEventListener("click",w),window.removeEventListener("keydown",w),window.removeEventListener("scroll",w)}),()=>{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 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:Ue(i)}),u=a.toJSON(),d=u.endpoint,A=u.keys?.p256dh??"",p=u.keys?.auth??"",m=await xe(d),y=await this.http.post("/push/tokens",{orgId:this.orgId,platform:"web",token:m,webEndpoint:d,webP256dh:A,webAuth:p,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(!y?.data?.tokenId)throw new Error("[sendoracloud] backend returned no tokenId on subscribe.");return{tokenId:y.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 Ue(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 xe(r){let e=new TextEncoder().encode(r),t=await crypto.subtle.digest("SHA-256",e);return Array.from(new Uint8Array(t)).map(n=>n.toString(16).padStart(2,"0")).join("")}var F=class{constructor(e,t){this.http=e,this.orgId=t}setOrgId(e){this.orgId=e}base(){if(!this.orgId)throw new Error("Audiences API requires orgId \u2014 pass it via SendoraCloud.init({ orgId }) or call audiences.setOrgId() before use.");return`/orgs/${this.orgId}/audiences`}async list(){let e=await this.http.get(this.base());return e.success?e.data??[]:[]}async get(e){let t=await this.http.get(`${this.base()}/${e}`);return t.success?t.data??null:null}async create(e){let t=await this.http.post(this.base(),e);return t.success?t.data??null:null}async update(e,t){let s=await this.http.patch(`${this.base()}/${e}`,t);return s.success?s.data??null:null}async delete(e){return(await this.http.delete(`${this.base()}/${e}`)).success}};var Oe="2.10.0",W=class W{constructor(e){this.autoTrackCleanup=null;this.http=new K(e),this.storage=new V,this.consent=new x(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),this.audiences=new F(this.http,e.orgId),e.autoTrack!==!1&&(this.autoTrackCleanup=ue(this.analytics,e))}static init(e){return te(e.apiKey),new W(e)}static sanitizeHtml(e){return Y(e)}static safeUrl(e){return S(e)}track(e,t){this.analytics.track(e,t)}page(e){this.analytics.page(e)}identify(e,t,s){this.analytics.identify(e,t,s),this.flags.setContext({userId:e})}reset(){this.analytics.reset(),this.flags.reset(),this.chatbot.newSession()}async flush(){await this.analytics.flush()}destroy(){this.autoTrackCleanup&&(this.autoTrackCleanup(),this.autoTrackCleanup=null),this.analytics.destroy(),this.flags.destroy(),this.messages.destroy()}};W.version=Oe;var G=W,gt=G;export{_ as Analytics,F as Audiences,D as Auth,c as AuthError,U as Chatbot,x as Consent,M as EmailAlreadyTakenError,P as FeatureFlags,C as InAppMessaging,G as SendoraCloud,R as Surveys,h as ValidationError,N as WebPush,gt as default,S as safeUrl,Y as sanitizeHtml};
|
|
1
|
+
var ee=/^[a-zA-Z][a-zA-Z0-9._:-]{0,127}$/;var ge=new Set(["__proto__","constructor","prototype"]),p=class extends Error{constructor(e){super(`[SendoraCloud] ${e}`),this.name="SendoraValidationError"}};function te(r){if(typeof r!="string"||!ee.test(r))throw new p(`Invalid event name: must match ${ee}, got "${String(r)}".`)}function se(r){if(r==null)return;if(typeof r!="object"||Array.isArray(r))throw new p("Properties must be a plain object, not an array or primitive.");let e=re(r);if(e>5)throw new p(`Properties nested too deeply (${e} levels, max 5).`);ne(r);let t;try{t=JSON.stringify(r).length}catch{throw new p("Properties contain a circular reference or unserializable value.")}if(t>32768)throw new p(`Properties too large (${t} bytes, max 32768).`)}function re(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=re(r[s],e+1);n>t&&(t=n)}return t}function ne(r){if(!(r==null||typeof r!="object"))for(let e of Object.keys(r)){if(ge.has(e))throw new p(`Forbidden property key "${e}" (prototype pollution guard).`);ne(r[e])}}var fe=/^pk_[A-Za-z0-9_-]{16,}$/;function ie(r){if(!r||typeof r!="string")throw new p("apiKey is required.");if(r.startsWith("sk_")||r.startsWith("sendora_secret_"))throw new p("Secret keys must NEVER be used in browser SDK. Use a publishable key (pk_...).");if(!fe.test(r))throw new p("Invalid publishable key format. Expected pk_... (17+ chars).")}function oe(r){let e=r.replace(/\/$/,"");if(!(e.startsWith("http://localhost")||e.startsWith("http://127.0.0.1"))&&!e.startsWith("https://"))throw new p("apiUrl must be https:// (only http://localhost is allowed for dev).")}var me="https://api.sendoracloud.com",ye=1e4,V=class{constructor(e){let t=(e.apiUrl??me).replace(/\/$/,"");oe(t),this.apiKey=e.apiKey,this.baseUrl=t,this.debug=!!e.debug}get(e,t){return this.request("GET",e,void 0,t)}post(e,t,s){return this.request("POST",e,t,s)}patch(e,t,s){return this.request("PATCH",e,t,s)}delete(e,t){return this.request("DELETE",e,void 0,t)}async request(e,t,s,n){let i=`${this.baseUrl}/api/v1${t}`,o=typeof AbortController<"u"?new AbortController:null,a=o?setTimeout(()=>o.abort(),ye):null;try{let c=await fetch(i,{method:e,headers:{"Content-Type":"application/json","X-API-Key":this.apiKey,...n??{}},body:s?JSON.stringify(s):void 0,signal:o?o.signal:void 0});a&&clearTimeout(a);let d;try{d=await c.json()}catch{d={success:!1,error:{code:"PARSE_ERROR",message:`HTTP ${c.status}`}}}if(this.debug&&console.debug(`[SendoraCloud] ${e} ${t} \u2192 ${c.status}`),!c.ok)if(t.startsWith("/auth-service/")&&!this.debug)console.warn(`[SendoraCloud] ${e} ${t} \u2192 HTTP ${c.status}`);else{let g=d?.error?.code??"HTTP_ERROR",y=d?.error?.message??`HTTP ${c.status}`,w=d?.error?.details?.fieldErrors,v=w?` (${Object.entries(w).map(([l,I])=>`${l}: ${(I??[]).join(", ")}`).join("; ")})`:"";console.warn(`[SendoraCloud] ${e} ${t} \u2192 HTTP ${c.status} \u2014 ${g}: ${y}${v}`)}return d}catch(c){a&&clearTimeout(a);let d=c instanceof Error&&c.name==="AbortError"?"timeout":"error";return console.warn(`[SendoraCloud] ${e} ${t} network ${d}`),{success:!1,error:{code:"NETWORK_ERROR",message:d}}}}};var W="sendora_",B=class{constructor(){this.memoryStore=new Map;this.useLocalStorage=this.checkLocalStorage()}get(e){let t=W+e;if(this.useLocalStorage)try{return localStorage.getItem(t)}catch{return this.memoryStore.get(t)??null}return this.memoryStore.get(t)??null}set(e,t){let s=W+e;if(this.useLocalStorage)try{localStorage.setItem(s,t);return}catch{}this.memoryStore.set(s,t)}remove(e){let t=W+e;if(this.useLocalStorage)try{localStorage.removeItem(t)}catch{}this.memoryStore.delete(t)}clearPrefix(e){let t=W+e;if(this.useLocalStorage)try{for(let s=localStorage.length-1;s>=0;s--){let n=localStorage.key(s);n&&n.startsWith(t)&&localStorage.removeItem(n)}}catch{}for(let s of Array.from(this.memoryStore.keys()))s.startsWith(t)&&this.memoryStore.delete(s)}checkLocalStorage(){try{if(typeof localStorage>"u")return!1;let e="__sendora_test__";return localStorage.setItem(e,"1"),localStorage.removeItem(e),!0}catch{return!1}}};function b(r=""){let e=new Uint8Array(16),t=typeof globalThis<"u"&&"crypto"in globalThis?globalThis.crypto:void 0;if(!t||typeof t.getRandomValues!="function")throw new Error("[SendoraCloud] crypto.getRandomValues unavailable \u2014 refusing to mint IDs without a CSPRNG");t.getRandomValues(e);let s="";for(let n of e)s+=n.toString(16).padStart(2,"0");return r+s}var ae="2.9.0",ce=500,we=10,ve=6e4,_=class{constructor(e,t,s,n){this.queue=[];this.timer=null;this.userId=null;this.identityToken=null;this.consecutiveFailures=0;this.nextFlushAfter=0;this.http=t,this.storage=s,this.consent=n,this.projectId=e.projectId,this.flushInterval=e.flushInterval??5e3,this.maxQueueSize=e.maxQueueSize??20,this.persistUserId=!!e.persistUserId,this.stripQueryParams=e.stripQueryParams!==!1;let i=this.storage.get("anonymous_id");if(i?this.anonymousId=i:(this.anonymousId=b("anon_"),this.storage.set("anonymous_id",this.anonymousId)),this.persistUserId){let o=this.storage.get("user_id");o&&(this.userId=o)}this.timer=setInterval(()=>this.flush(),this.flushInterval),typeof window<"u"&&window.addEventListener("beforeunload",()=>this.flush()),this.consent.subscribe(o=>{o&&this.flush()})}track(e,t){te(e),t&&se(t),this.queue.length>=ce&&this.queue.shift(),this.queue.push({eventType:e,properties:t??{},userId:this.userId??void 0,anonymousId:this.anonymousId,timestamp:new Date().toISOString(),identityToken:this.identityToken??void 0}),this.queue.length>=this.maxQueueSize&&this.flush()}page(e){if(typeof window>"u")return;let t=this.stripQueryParams?window.location.origin+window.location.pathname:window.location.href;this.track("page.viewed",{url:t,path:window.location.pathname,title:document.title,referrer:document.referrer||void 0,...e})}identify(e,t,s){if(typeof e!="string"||e.length===0||e.length>256)throw new Error("[SendoraCloud] userId must be a 1-256 char string.");this.userId=e,this.identityToken=s?.identityToken??null,this.persistUserId&&this.storage.set("user_id",e),this.track("user.identified",{...t})}reset(){this.userId=null,this.identityToken=null,this.storage.remove("user_id"),this.anonymousId=b("anon_"),this.storage.set("anonymous_id",this.anonymousId),this.storage.clearPrefix("iam_shown_"),this.storage.clearPrefix("survey_shown_")}dropQueue(){this.queue=[]}async flush(){if(this.queue.length===0||!this.consent.isGranted()||Date.now()<this.nextFlushAfter)return;let e=this.queue.splice(0,100),t=e.map(o=>({projectId:this.projectId,module:"custom",eventType:o.eventType,userId:o.userId,anonymousId:o.anonymousId,identityToken:o.identityToken,timestamp:o.timestamp??new Date().toISOString(),properties:o.properties??{},context:this.getContext(),consent:["analytics"]}));if((await this.http.post("/events/batch",{events:t})).success){this.consecutiveFailures=0;return}if(this.consecutiveFailures++,this.consecutiveFailures>we)return;let n=Math.min(ve,1e3*2**this.consecutiveFailures);this.nextFlushAfter=Date.now()+n;let i=Math.max(0,ce-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:ae}}:{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:ae}}}};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)Y(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&&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 ke=32*1024,C=class{constructor(e,t,s){this.activeSurvey=null;this.http=t,this.storage=s}onDisplay(e){this.onSurveyDisplay=e}async showSurvey(e){if(typeof e!="string"||!/^[A-Za-z0-9_-]{6,64}$/.test(e))return null;let t=`survey_shown_${e}`;if(this.storage.get(t))return null;let s=await this.http.get(`/surveys/${encodeURIComponent(e)}/config`);return!s.success||!s.data||!Ae(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>ke)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 Ae(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(Ie)}function Ie(r){if(r==null||typeof r!="object")return!1;let e=r;return typeof e.id=="string"&&typeof e.text=="string"}function S(r){if(!(!r||typeof r!="string"))try{let e=typeof window<"u"?window.location.href:"https://local/",t=new URL(r,e);return t.protocol==="http:"||t.protocol==="https:"?t.href:void 0}catch{return}}var Se=new Set(["b","i","u","em","strong","a","p","br","span","ul","ol","li","h1","h2","h3","h4"]),be={a:new Set(["href","target","rel"])};function Q(r){if(typeof DOMParser>"u")return"";let t=new DOMParser().parseFromString(`<div>${r}</div>`,"text/html").body.firstElementChild;return t?(ue(t),t.innerHTML):""}function ue(r){let e=Array.from(r.children);for(let t of e){let s=t.tagName.toLowerCase();if(!Se.has(s)){let i=document.createTextNode(t.textContent??"");t.replaceWith(i);continue}let n=be[s]??new Set;for(let i of Array.from(t.attributes)){if(!n.has(i.name.toLowerCase())){t.removeAttribute(i.name);continue}if(i.name.toLowerCase()==="href"){let o=S(i.value);o?t.setAttribute("href",o):t.removeAttribute(i.name)}}s==="a"&&(t.setAttribute("rel","noopener noreferrer"),t.setAttribute("target","_blank")),ue(t)}}var L=class{constructor(e,t,s){this.messages=[];this.pollInterval=null;this.http=t,this.storage=s}onDisplay(e){this.onMessageDisplay=e}async checkMessages(){let e=await this.http.get("/in-app-messages/active");if(!e.success||!Array.isArray(e.data))return null;this.messages=e.data.map(Ee);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 Ee(r){return{...r,content:{...r.content,ctaUrl:S(r.content.ctaUrl),imageUrl:S(r.content.imageUrl)}}}var Te=4e3,D=class{constructor(e,t,s){this.chatbotId=null;this.messages=[];this.http=t,this.storage=s;let n=this.storage.get("chat_session_id");this.sessionId=n??b("chat_"),n||this.storage.set("chat_session_id",this.sessionId)}init(e){if(typeof e!="string"||!/^[A-Za-z0-9_-]{6,64}$/.test(e))throw new Error("[SendoraCloud] Invalid chatbotId.");this.chatbotId=e}onNewMessage(e){this.onMessage=e}async sendMessage(e,t){if(!this.chatbotId||typeof e!="string"||e.length===0||e.length>Te)return null;let s={role:"user",content:e,timestamp:new Date().toISOString()};this.messages.push(s);let n=await this.http.post("/chatbot/message",{chatbotId:this.chatbotId,sessionId:this.sessionId,message:e,userId:t});if(n.success&&n.data&&typeof n.data.reply=="string"){let i={role:"assistant",content:n.data.reply,timestamp:new Date().toISOString()};return this.messages.push(i),this.onMessage&&this.onMessage(i),i}return null}getMessages(){return[...this.messages]}newSession(){this.messages=[],this.sessionId=b("chat_"),this.storage.set("chat_session_id",this.sessionId)}getSessionId(){return this.sessionId}};var U=class{constructor(e){this.listeners=new Set;this.granted=e}isGranted(){return this.granted}grant(){this.granted||(this.granted=!0,this.emit())}revoke(){this.granted&&(this.granted=!1,this.emit())}subscribe(e){return this.listeners.add(e),()=>this.listeners.delete(e)}emit(){for(let e of this.listeners)try{e(this.granted)}catch{}}};var E="auth_access_token",T="auth_access_expires",f="auth_refresh_token",O="auth_user",Re=3e4;function m(r){if(!r||typeof r!="object")return!1;let e=r,t=e.user,s=e.tokens;return!(!t||typeof t.id!="string"||t.id.length===0||!s||typeof s.accessToken!="string"||s.accessToken.length===0||typeof s.refreshToken!="string"||s.refreshToken.length===0||typeof s.expiresIn!="number"||s.expiresIn<=0||t.isAnonymous!==void 0&&typeof t.isAnonymous!="boolean"||t.email!==void 0&&t.email!==null&&typeof t.email!="string"||t.emailVerified!==void 0&&typeof t.emailVerified!="boolean"||t.name!==void 0&&t.name!==null&&typeof t.name!="string")}var N=class extends Error{constructor(e="An account with this email already exists"){super(e),this.name="EmailAlreadyTakenError"}},u=class extends Error{constructor(e,t){super(t),this.name="AuthError",this.code=e}},M=class{constructor(e,t,s,n,i,o){this.user=null;this.accessToken=null;this.accessExpiresAt=0;this.inflight=Promise.resolve();this.refreshInflight=null;this.http=t,this.storage=s,this.analytics=n,this.flags=i,this.chatbot=o;let a=this.storage.get(O),c=this.storage.get(E),d=this.storage.get(f),A=this.storage.get(T);if(a&&(c||d))try{let g=JSON.parse(a);if(typeof g.id!="string"||g.id.length===0)throw new Error("invalid cache");this.user=g,c?(this.accessToken=c,this.accessExpiresAt=A?Number(A):0):(this.accessToken=null,this.accessExpiresAt=0),this.analytics.identify(this.user.id)}catch{this.storage.remove(O),this.storage.remove(E),this.storage.remove(T),this.storage.remove(f)}}getCurrentUser(){return this.user}async getAccessToken(){return this.accessToken?this.accessExpiresAt&&Date.now()<this.accessExpiresAt-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||!m(t.data))throw new u(t.error?.code??"AUTH_ERROR",t.error?.message??"Anonymous signup failed");return this.persist(t.data),t.data.user})}signUp(e,t,s){return this.serialize(async()=>{let n=this.storage.get(f);if(n){let o=await this.http.post("/auth-service/upgrade",{refreshToken:n,email:e,password:t,name:s?.name});if(o.success&&m(o.data))return this.persist(o.data),o.data.user;let a=o.error?.code??"",c=o.error?.status;if(a==="CONFLICT"||a==="EMAIL_ALREADY_TAKEN"||c===409)throw new N(o.error?.message);if(a==="FORBIDDEN_NON_ANONYMOUS"||a==="INVALID_REFRESH"||a==="UNAUTHORIZED"||a==="REFRESH_EXPIRED")await this.analytics.flush(),this.wipeLocalIdentity();else throw new u(a||"AUTH_ERROR",o.error?.message??"Upgrade failed")}let i=await this.http.post("/auth-service/signup",{email:e,password:t,name:s?.name,metadata:s?.metadata});if(!i.success||!m(i.data)){let o=i.error?.code??"";throw o==="CONFLICT"?new N(i.error?.message):new u(o||"AUTH_ERROR",i.error?.message??"Signup failed")}return this.persist(i.data),i.data.user})}signIn(e,t){return this.serialize(async()=>{this.user!==null&&(await this.analytics.flush(),this.wipeLocalIdentity());let n=await this.http.post("/auth-service/login",{email:e,password:t});if(!n.success||!n.data)throw new u(n.error?.code??"AUTH_ERROR",n.error?.message??"Login failed");let i=n.data;if(i.mfaRequired===!0&&typeof i.mfaChallengeToken=="string"){let o=i.user;return{mfaRequired:!0,mfaChallengeToken:i.mfaChallengeToken,userId:o?.id??""}}if(!m(n.data))throw new u(n.error?.code??"AUTH_ERROR",n.error?.message??"Login failed");return this.persist(n.data),n.data.user})}loginSocial(e){return this.serialize(async()=>{this.user!==null&&(await this.analytics.flush(),this.wipeLocalIdentity());let s=await this.http.post("/auth-service/login/social",e);if(!s.success||!m(s.data))throw new u(s.error?.code??"SOCIAL_LOGIN_FAILED",s.error?.message??"Social login failed");return this.persist(s.data),s.data.user})}signInWithGoogle(e,t){return this.loginSocial({provider:"google",code:e,redirectUri:t})}signInWithGitHub(e,t){return this.loginSocial({provider:"github",code:e,redirectUri:t})}signInWithApple(e){return this.loginSocial({provider:"apple",...e})}signInWithMicrosoft(e,t){return this.loginSocial({provider:"microsoft",code:e,redirectUri:t})}signInWithLinkedIn(e,t){return this.loginSocial({provider:"linkedin",code:e,redirectUri:t})}signInWithFacebook(e,t){return this.loginSocial({provider:"facebook",code:e,redirectUri:t})}signInWithDiscord(e,t){return this.loginSocial({provider:"discord",code:e,redirectUri:t})}challengeMfa(e,t){return this.serialize(async()=>{let s=await this.http.post("/auth-service/mfa/challenge",{challengeToken:e,code:t});if(!s.success||!m(s.data))throw new u(s.error?.code??"MFA_CHALLENGE_FAILED",s.error?.message??"Invalid MFA code");return this.persist(s.data),s.data.user})}async sendMagicLink(e){let t=await this.http.post("/auth-service/magic-link/request",{email:e});if(!t.success)throw new u(t.error?.code??"MAGIC_LINK_REQUEST_FAILED",t.error?.message??"Failed to send link")}async sendEmailOtp(e){let t=await this.http.post("/auth-service/email-otp/request",{email:e});if(!t.success)throw new u(t.error?.code??"EMAIL_OTP_REQUEST_FAILED",t.error?.message??"Failed to send code")}verifyEmailOtp(e,t){return this.serialize(async()=>{this.user!==null&&(await this.analytics.flush(),this.wipeLocalIdentity());let n=await this.http.post("/auth-service/email-otp/verify",{email:e,code:t});if(!n.success||!m(n.data))throw new u(n.error?.code??"EMAIL_OTP_INVALID",n.error?.message??"Wrong or expired code");return this.persist(n.data),n.data.user})}async requestPasswordReset(e){let t=await this.http.post("/auth-service/password/forgot",{email:e});if(!t.success)throw new u(t.error?.code??"PASSWORD_RESET_REQUEST_FAILED",t.error?.message??"Failed to send reset email")}async resetPassword(e,t){let s=await this.http.post("/auth-service/password/reset",{token:e,newPassword:t});if(!s.success)throw new u(s.error?.code??"PASSWORD_RESET_FAILED",s.error?.message??"Reset failed")}async verifyEmail(e){let t=await this.http.post("/auth-service/email/verify",{token:e});if(!t.success)throw new u(t.error?.code??"EMAIL_VERIFICATION_FAILED",t.error?.message??"Verification failed")}async sendVerificationEmail(){let e=await this.getAccessToken();if(!e)throw new u("UNAUTHORIZED","Sign in before requesting a verification email");let t=await this.http.post("/auth-service/email/verify/resend",{},{Authorization:`Bearer ${e}`});if(!t.success)throw new u(t.error?.code??"VERIFICATION_RESEND_FAILED",t.error?.message??"Failed to send verification email")}verifyMagicLink(e){return this.serialize(async()=>{this.user!==null&&(await this.analytics.flush(),this.wipeLocalIdentity());let s=await this.http.post("/auth-service/magic-link/verify",{token:e});if(!s.success||!m(s.data))throw new u(s.error?.code??"MAGIC_LINK_INVALID",s.error?.message??"Invalid or expired magic link");return this.persist(s.data),s.data.user})}async enrollMfa(){let e=await this.http.post("/auth-service/mfa/enroll/start",{},this.bearerHeaders());if(!e.success||!e.data)throw new u(e.error?.code??"MFA_ENROLL_FAILED",e.error?.message??"MFA enrollment failed");return e.data}async confirmMfa(e){let t=await this.http.post("/auth-service/mfa/enroll/confirm",{code:e},this.bearerHeaders());return!!(t.success&&t.data?.confirmed)}async disableMfa(){await this.http.post("/auth-service/mfa/disable",{},this.bearerHeaders())}async registerPasskey(e){if(typeof navigator>"u"||!navigator.credentials)throw new u("PASSKEY_UNSUPPORTED","WebAuthn not supported in this environment");let t=await this.http.post("/auth-service/passkeys/register/start",{},this.bearerHeaders());if(!t.success||!t.data)throw new u(t.error?.code??"PASSKEY_START_FAILED",t.error?.message??"Passkey start failed");let s=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 u("PASSKEY_USER_CANCELLED",a instanceof Error?a.message:"User cancelled passkey registration")}let i=Le(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=Ce(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=Ue(i),a=await this.http.post("/auth-service/passkeys/authenticate/finish",{response:o,userId:e??null});if(!a.success||!m(a.data))throw new u(a.error?.code??"PASSKEY_AUTH_FAILED",a.error?.message??"Passkey signin failed");return this.persist(a.data),a.data.user})}async listPasskeys(){let e=await this.http.get("/auth-service/passkeys",this.bearerHeaders());return!e.success||!Array.isArray(e.data)?[]:e.data}async deletePasskey(e){await this.http.delete(`/auth-service/passkeys/${e}`,this.bearerHeaders())}async listMySessions(){let e=await this.http.get("/auth-service/sessions/me",this.bearerHeaders());return!e.success||!Array.isArray(e.data)?[]:e.data}async revokeSession(e){await this.http.delete(`/auth-service/sessions/me/${e}`,this.bearerHeaders())}async revokeAllSessions(){await this.http.delete("/auth-service/sessions/me",this.bearerHeaders())}async startSso(e){let t=await this.http.post("/auth-service/sso/oidc/start",{returnTo:e});if(!t.success||!t.data)throw new u(t.error?.code??"SSO_START_FAILED",t.error?.message??"SSO start failed");return t.data}async signInWithSso(e){let t=e??(typeof window<"u"?window.location.origin:"");if(!t)throw new u("SSO_NO_RETURN_TO","returnTo required outside browser context");let s=await this.startSso(t);typeof window<"u"&&(window.location.href=s.authorizationUrl)}consumeSsoTokenFromUrl(){return this.serialize(async()=>{if(typeof window>"u")return null;let e=window.location.hash.replace(/^#/,"");if(!e)return null;let t=new URLSearchParams(e),s=t.get("sendora_oidc_error");if(s)throw window.history.replaceState(null,"",window.location.pathname+window.location.search),new u("SSO_FAILED",decodeURIComponent(s));let n=t.get("sendora_oidc_token");if(!n)return null;window.history.replaceState(null,"",window.location.pathname+window.location.search),this.user!==null&&(await this.analytics.flush(),this.wipeLocalIdentity());let i=await this.http.post("/auth-service/token/refresh",{refreshToken:n});if(!i.success||!i.data)throw new u(i.error?.code??"SSO_EXCHANGE_FAILED",i.error?.message??"Could not exchange SSO token");let o=i.data;this.accessToken=o.accessToken,this.accessExpiresAt=Date.now()+o.expiresIn*1e3,this.storage.set(E,o.accessToken),this.storage.set(T,String(this.accessExpiresAt)),this.storage.set(f,o.refreshToken);let a=De(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(O,JSON.stringify(c)),this.analytics.identify(c.id),this.flags.setContext({userId:c.id}),c})}bearerHeaders(){if(!this.accessToken)throw new u("NOT_SIGNED_IN","Not signed in");return{Authorization:`Bearer ${this.accessToken}`}}signOut(){return this.serialize(async()=>{let e=this.storage.get(f);this.wipeLocalIdentity(),e&&this.http.post("/auth-service/token/revoke",{refreshToken:e}).catch(()=>{})})}serialize(e){let t=this.inflight.then(e,e);return this.inflight=t.catch(()=>{}),t}async refreshAccessToken(){if(this.refreshInflight)return this.refreshInflight;let e=this.storage.get(f);return e?(this.refreshInflight=(async()=>{try{let t=await this.http.post("/auth-service/token/refresh",{refreshToken:e});if(!t.success)return _e(t.error?.code)&&this.wipeLocalIdentity(),null;if(!t.data||typeof t.data!="object"||typeof t.data.accessToken!="string"||typeof t.data.refreshToken!="string"||typeof t.data.expiresIn!="number")return null;let s=t.data;return this.accessToken=s.accessToken,this.accessExpiresAt=Date.now()+s.expiresIn*1e3,this.storage.set(E,s.accessToken),this.storage.set(T,String(this.accessExpiresAt)),this.storage.set(f,s.refreshToken),s.accessToken}catch{return null}finally{this.refreshInflight=null}})(),this.refreshInflight):null}persist(e){this.user=e.user,this.accessToken=e.tokens.accessToken,this.accessExpiresAt=Date.now()+e.tokens.expiresIn*1e3,this.storage.set(O,JSON.stringify(e.user)),this.storage.set(E,e.tokens.accessToken),this.storage.set(T,String(this.accessExpiresAt)),this.storage.set(f,e.tokens.refreshToken),this.analytics.identify(e.user.id),this.flags.setContext({userId:e.user.id})}wipeLocalIdentity(){this.user=null,this.accessToken=null,this.accessExpiresAt=0,this.storage.remove(O),this.storage.remove(E),this.storage.remove(T),this.storage.remove(f),this.analytics.reset(),this.analytics.dropQueue(),this.flags.reset(),this.chatbot.newSession()}};function _e(r){return r?r==="INVALID_REFRESH_TOKEN"||r==="UNAUTHORIZED"||r==="HTTP_401"||r==="RATE_LIMIT_EXCEEDED"||r==="RATE_LIMIT":!1}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),i=new Uint8Array(n);for(let o=0;o<s.length;o++)i[o]=s.charCodeAt(o);return n}function k(r){let e=r instanceof Uint8Array?r:new Uint8Array(r),t="";for(let s=0;s<e.length;s++)t+=String.fromCharCode(e[s]);return btoa(t).replace(/\+/g,"-").replace(/\//g,"_").replace(/=+$/,"")}function Pe(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 Ce(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 Le(r){let e=r.response;return{id:r.id,rawId:k(r.rawId),type:r.type,response:{clientDataJSON:k(e.clientDataJSON),attestationObject:k(e.attestationObject),transports:typeof e.getTransports=="function"?e.getTransports():void 0},clientExtensionResults:r.getClientExtensionResults()}}function De(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 Ue(r){let e=r.response;return{id:r.id,rawId:k(r.rawId),type:r.type,response:{clientDataJSON:k(e.clientDataJSON),authenticatorData:k(e.authenticatorData),signature:k(e.signature),userHandle:e.userHandle?k(e.userHandle):null},clientExtensionResults:r.getClientExtensionResults()}}function Oe(r){return r===!1?{pageViews:!1,sessionStart:!1,appOpen:!1,appBackground:!1}:r===!0||r===void 0?{pageViews:!0,sessionStart:!0,appOpen:!0,appBackground:!0}:{pageViews:r.pageViews??!0,sessionStart:r.sessionStart??!0,appOpen:r.appOpen??!0,appBackground:r.appBackground??!0}}function he(r,e){if(typeof window>"u")return()=>{};let t=Oe(e.autoTrack),s=e.sessionIdleMs??18e5,n=[],i="sendora_session_id",o="sendora_session_last_activity";function a(){return le(i)??""}function c(l){de(i,l)}function d(){let l=le(o);return l&&Number(l)||0}function A(l){de(o,String(l))}function g(){return`sess_${Date.now().toString(36)}_${Math.random().toString(36).slice(2,10)}`}function y(l){let I=Date.now(),R=d();if(!a()||R&&I-R>s){let $=g();c($),l&&t.sessionStart&&r.track("session.start",{sessionId:$})}A(I)}if(y(t.sessionStart),t.appOpen&&r.track("app.opened",{sessionId:a()||void 0,url:window.location.origin+window.location.pathname,referrer:document.referrer||void 0}),t.pageViews){r.page();let l=()=>{y(!0),r.page()},I=()=>l();window.addEventListener("popstate",I),n.push(()=>window.removeEventListener("popstate",I));let R=window;if(!R.__sendoraHistoryPatched){R.__sendoraHistoryPatched=!0;let $=history.pushState,pe=history.replaceState;history.pushState=function(...j){let G=$.apply(this,j);return window.dispatchEvent(new Event("sendora:locationchange")),G},history.replaceState=function(...j){let G=pe.apply(this,j);return window.dispatchEvent(new Event("sendora:locationchange")),G}}let q=()=>l();window.addEventListener("sendora:locationchange",q),n.push(()=>window.removeEventListener("sendora:locationchange",q))}if(t.appBackground){let l=()=>{y(!0),document.visibilityState==="hidden"?r.track("app.backgrounded",{sessionId:a()||void 0}):document.visibilityState==="visible"&&r.track("app.foregrounded",{sessionId:a()||void 0})};document.addEventListener("visibilitychange",l),n.push(()=>document.removeEventListener("visibilitychange",l))}let w=0,v=()=>{let l=Date.now();l-w<5e3||(w=l,y(!0))};return window.addEventListener("click",v,{passive:!0}),window.addEventListener("keydown",v,{passive:!0}),window.addEventListener("scroll",v,{passive:!0}),n.push(()=>{window.removeEventListener("click",v),window.removeEventListener("keydown",v),window.removeEventListener("scroll",v)}),()=>{for(let l of n)try{l()}catch{}}}function le(r){try{return window.sessionStorage?.getItem(r)??null}catch{return null}}function de(r,e){try{window.sessionStorage?.setItem(r,e)}catch{}}var F=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:xe(i)}),c=a.toJSON(),d=c.endpoint,A=c.keys?.p256dh??"",g=c.keys?.auth??"",y=await Ne(d),w=await this.http.post("/push/tokens",{orgId:this.orgId,platform:"web",token:y,webEndpoint:d,webP256dh:A,webAuth:g,userAgent:typeof navigator<"u"?navigator.userAgent:void 0,locale:typeof navigator<"u"?navigator.language:void 0,timezone:Intl?.DateTimeFormat?.().resolvedOptions?.().timeZone,...e.userId?{userId:e.userId}:{}});if(!w?.data?.tokenId)throw new Error("[sendoracloud] backend returned no tokenId on subscribe.");return{tokenId:w.data.tokenId,subscription:a}}async unsubscribe(e="/"){if(!r.isSupported())return!1;let t=await navigator.serviceWorker.getRegistration(e);if(!t)return!1;let s=await t.pushManager.getSubscription();return s?await s.unsubscribe():!1}};function xe(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 H=class{constructor(e,t){this.http=e,this.orgId=t}setOrgId(e){this.orgId=e}base(){if(!this.orgId)throw new Error("Audiences API requires orgId \u2014 pass it via SendoraCloud.init({ orgId }) or call audiences.setOrgId() before use.");return`/orgs/${this.orgId}/audiences`}async list(){let e=await this.http.get(this.base());return e.success?e.data??[]:[]}async get(e){let t=await this.http.get(`${this.base()}/${e}`);return t.success?t.data??null:null}async create(e){let t=await this.http.post(this.base(),e);return t.success?t.data??null:null}async update(e,t){let s=await this.http.patch(`${this.base()}/${e}`,t);return s.success?s.data??null:null}async delete(e){return(await this.http.delete(`${this.base()}/${e}`)).success}};var h=class extends Error{constructor(e,t,s){super(t),this.name="LinkError",this.code=e,this.statusCode=s}};function X(r,e){let t=(r??"").toUpperCase();return t==="NOT_FOUND"?new h("NOT_FOUND",e):t==="FORBIDDEN"||t==="UNAUTHORIZED"?new h("UNAUTHORIZED",e):t==="RATE_LIMITED"?new h("RATE_LIMITED",e):t==="ENTITLEMENT_ERROR"||/plan limit/i.test(e)?new h("PLAN_LIMIT",e):t==="VALIDATION_ERROR"?/iOS bundle|Android package/i.test(e)?new h("BUNDLE_MISMATCH",e):/2KB|10KB|linkData/i.test(e)?new h("DATA_TOO_LARGE",e):/fallbackUrl/i.test(e)&&/apps/i.test(e)?new h("FALLBACK_REQUIRED",e):new h("INVALID_INPUT",e):t==="PARSE_ERROR"||t==="HTTP_ERROR"||/HTTP \d{3}/.test(e)?new h("NETWORK",e):new h("UNKNOWN",e||`Unknown LinkError (code=${r??"none"})`)}var J=/^[a-z0-9-]{3,20}$/;function Me(r){try{let t=new URL(r).pathname.split("/").filter(Boolean);if(t.length===0)return null;let s=t[t.length-1];return J.test(s)?s:null}catch{return null}}var Fe=5*6e4,He=50,K=class{constructor(e){this.http=e;this.prewarmCache=new Map}cacheKey(e,t){if(t)return`k:${t}`;let s={},n=e;for(let i of Object.keys(n).sort())s[i]=n[i];return`s:${JSON.stringify(s)}`}evictExpired(){let e=Date.now();for(let[t,s]of this.prewarmCache)e-s.ts>Fe&&this.prewarmCache.delete(t);for(;this.prewarmCache.size>He;){let t=this.prewarmCache.keys().next().value;if(t===void 0)break;this.prewarmCache.delete(t)}}prewarm(e,t){this.evictExpired();let s=this.cacheKey(e,t?.key);if(this.prewarmCache.has(s))return;let n=this.doCreate(e).catch(i=>{throw this.prewarmCache.delete(s),i});this.prewarmCache.set(s,{promise:n,ts:Date.now()})}async create(e,t){if(!e.title)throw new h("INVALID_INPUT","links.create: title is required");this.evictExpired();let s=this.cacheKey(e,t?.key),n=this.prewarmCache.get(s);return n?(this.prewarmCache.delete(s),n.promise):this.doCreate(e)}async doCreate(e){let t={title:e.title};e.fallbackUrl&&(t.fallbackUrl=e.fallbackUrl),e.iosDeepLinkPath&&(t.iosDeepLinkPath=e.iosDeepLinkPath),e.androidDeepLinkPath&&(t.androidDeepLinkPath=e.androidDeepLinkPath),e.linkData&&(t.linkData=e.linkData),e.ogTitle&&(t.ogTitle=e.ogTitle),e.ogDescription&&(t.ogDescription=e.ogDescription),e.ogImageUrl&&(t.ogImageUrl=e.ogImageUrl),e.campaign&&(t.campaign=e.campaign),e.source&&(t.source=e.source),e.medium&&(t.medium=e.medium),e.channel&&(t.channel=e.channel),e.tags&&(t.tags=e.tags),e.expiresAt&&(t.expiresAt=e.expiresAt),e.iosBundleId&&(t.iosBundleId=e.iosBundleId),e.androidPackageName&&(t.androidPackageName=e.androidPackageName);let s=await this.http.post("/sdk/links",t);if(!s.success||!s.data)throw X(s.error?.code,s.error?.message??"links.create failed");return s.data}async revoke(e){if(!J.test(e))throw new h("INVALID_INPUT",`links.revoke: '${e}' is not a valid shortcode`);let t=await this.http.post(`/sdk/links/${encodeURIComponent(e)}/revoke`,{});if(!t.success||!t.data)throw X(t.error?.code,t.error?.message??"links.revoke failed");return{shortcode:t.data.shortcode,revoked:!0}}async getStats(e){if(!J.test(e))throw new h("INVALID_INPUT",`links.getStats: '${e}' is not a valid shortcode`);let t=await this.http.get(`/sdk/links/${encodeURIComponent(e)}/stats`);if(!t.success||!t.data)throw X(t.error?.code,t.error?.message??"links.getStats failed");return t.data}};var Ke="2.13.0",z=class z{constructor(e){this.autoTrackCleanup=null;this.http=new V(e),this.storage=new B,this.consent=new U(e.defaultConsent===!0),this.analytics=new _(e,this.http,this.storage,this.consent),this.flags=new P(e,this.http,this.storage),this.surveys=new C(e,this.http,this.storage),this.messages=new L(e,this.http,this.storage),this.chatbot=new D(e,this.http,this.storage),this.auth=new M(e,this.http,this.storage,this.analytics,this.flags,this.chatbot),this.webPush=new F(this.http,e.orgId),this.audiences=new H(this.http,e.orgId),this.links=new K(this.http),e.autoTrack!==!1&&(this.autoTrackCleanup=he(this.analytics,e))}static init(e){return ie(e.apiKey),new z(e)}static sanitizeHtml(e){return Q(e)}static safeUrl(e){return S(e)}track(e,t){this.analytics.track(e,t)}page(e){this.analytics.page(e)}identify(e,t,s){this.analytics.identify(e,t,s),this.flags.setContext({userId:e})}reset(){this.analytics.reset(),this.flags.reset(),this.chatbot.newSession()}async flush(){await this.analytics.flush()}destroy(){this.autoTrackCleanup&&(this.autoTrackCleanup(),this.autoTrackCleanup=null),this.analytics.destroy(),this.flags.destroy(),this.messages.destroy()}};z.version=Ke;var Z=z,St=Z;export{_ as Analytics,H as Audiences,M as Auth,u as AuthError,D as Chatbot,U as Consent,N as EmailAlreadyTakenError,P as FeatureFlags,L as InAppMessaging,h as LinkError,K as Links,Z as SendoraCloud,C as Surveys,p as ValidationError,F as WebPush,St as default,Me as extractShortcodeFromUrl,S as safeUrl,Q as sanitizeHtml};
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sendoracloud/sdk-web",
|
|
3
|
-
"version": "2.
|
|
4
|
-
"description": "Sendora Cloud Web SDK — analytics, feature flags, surveys, in-app messaging, chatbot, auth. Zero dependencies, TypeScript, SSR-safe.",
|
|
3
|
+
"version": "2.13.0",
|
|
4
|
+
"description": "Sendora Cloud Web SDK — analytics, feature flags, surveys, in-app messaging, chatbot, auth, deep links (Branch / Firebase parity). Zero dependencies, TypeScript, SSR-safe.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.cjs",
|
|
7
7
|
"module": "./dist/index.js",
|