@saas-support/react 0.7.6 → 0.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});class d extends Error{constructor(t,e,s="unknown"){super(e),this.name="SaaSError",this.code=t,this.domain=s}get isNotFound(){return this.code===404}get isUnauthorized(){return this.code===401}get isForbidden(){return this.code===403}get isConflict(){return this.code===409}get isRateLimited(){return this.code===429}}class y{constructor(t,e){this.onUnauthorized=null,this.baseUrl=t,this.authMode=e}setUnauthorizedHandler(t){this.onUnauthorized=t}async request(t,e,s,r){try{return await this.doRequest(t,e,s,r)}catch(i){if(i instanceof d&&i.isUnauthorized&&this.onUnauthorized&&(r!=null&&r.Authorization)){const n=await this.onUnauthorized();if(n)return this.doRequest(t,e,s,{...r,Authorization:`Bearer ${n}`})}throw i}}async get(t,e){return this.request("GET",t,void 0,e)}async post(t,e,s){return this.request("POST",t,e,s)}async patch(t,e,s){return this.request("PATCH",t,e,s)}async del(t,e){return this.request("DELETE",t,void 0,e)}async uploadBinary(t,e,s){try{return await this.doUploadBinary(t,e,s)}catch(r){if(r instanceof d&&r.isUnauthorized&&this.onUnauthorized&&(s!=null&&s.Authorization)){const i=await this.onUnauthorized();if(i)return this.doUploadBinary(t,e,{...s,Authorization:`Bearer ${i}`})}throw r}}async doUploadBinary(t,e,s){const r={"Content-Type":"application/octet-stream",...this.getAuthHeaders(),...s},i=await fetch(`${this.baseUrl}${t}`,{method:"POST",headers:r,body:e}),n=await i.json();if(!i.ok||n.isOk===!1||n.code&&n.code>=400){const a=this.inferDomain(t),c=n.code||i.status;throw new d(c,n.message||"Upload failed",a)}return n.data}async doRequest(t,e,s,r){const i={"Content-Type":"application/json",...this.getAuthHeaders(),...r},n=await fetch(`${this.baseUrl}${e}`,{method:t,headers:i,body:s?JSON.stringify(s):void 0}),a=await n.json();if(!n.ok||a.isOk===!1||a.code&&a.code>=400){const c=this.inferDomain(e),h=a.code||n.status;throw new d(h,a.message||"Request failed",c)}return a.data}getAuthHeaders(){switch(this.authMode.type){case"publishableKey":case"apiKey":return{"X-API-Key":this.authMode.key};case"portalToken":case"embedToken":return{Authorization:`Bearer ${this.authMode.token}`}}}inferDomain(t){return t.startsWith("/auth")?"auth":t.startsWith("/billing")?"billing":t.startsWith("/report")?"report":"unknown"}}class ${constructor(t){this.accessToken=null,this.refreshToken=null,this.refreshTimer=null,this.refreshInFlight=null,this.onRefreshNeeded=null,this.onTokensChanged=null,this.boundHandleStorage=null,this.storageKey=`ss_rt_${t.slice(0,12)}`,this.refreshToken=this.loadRefreshToken(),typeof window<"u"&&(this.boundHandleStorage=this.handleStorageEvent.bind(this),window.addEventListener("storage",this.boundHandleStorage))}setRefreshCallback(t){this.onRefreshNeeded=t}setTokensChangedCallback(t){this.onTokensChanged=t}getAccessToken(){return this.accessToken}getRefreshToken(){return this.refreshToken}hasRefreshToken(){return this.refreshToken!==null}setTokens(t,e){this.accessToken=t,this.refreshToken=e,this.saveRefreshToken(e),this.scheduleRefresh(t)}clearTokens(){this.accessToken=null,this.refreshToken=null,this.removeRefreshToken(),this.refreshTimer&&(clearTimeout(this.refreshTimer),this.refreshTimer=null)}async refreshOnce(){return this.refreshInFlight?this.refreshInFlight:(this.refreshInFlight=this.executeRefresh().finally(()=>{this.refreshInFlight=null}),this.refreshInFlight)}destroy(){this.refreshTimer&&(clearTimeout(this.refreshTimer),this.refreshTimer=null),typeof window<"u"&&this.boundHandleStorage&&(window.removeEventListener("storage",this.boundHandleStorage),this.boundHandleStorage=null)}async executeRefresh(){if(!this.onRefreshNeeded)throw new Error("No refresh callback configured");typeof navigator<"u"&&"locks"in navigator?await navigator.locks.request(`ss_refresh_lock_${this.storageKey}`,async()=>{const t=this.loadRefreshToken();t&&t!==this.refreshToken&&(this.refreshToken=t),await this.onRefreshNeeded()}):await this.onRefreshNeeded()}scheduleRefresh(t){this.refreshTimer&&clearTimeout(this.refreshTimer);const e=this.getTokenExpiry(t);if(!e)return;const s=e*1e3-Date.now()-6e4;if(s<=0){this.refreshOnce().catch(()=>{});return}this.refreshTimer=setTimeout(()=>{this.refreshOnce().catch(()=>{})},s)}handleStorageEvent(t){var e;if(t.key===this.storageKey){if(t.newValue===null){this.accessToken=null,this.refreshToken=null,this.refreshTimer&&(clearTimeout(this.refreshTimer),this.refreshTimer=null),(e=this.onTokensChanged)==null||e.call(this);return}t.newValue!==this.refreshToken&&(this.refreshToken=t.newValue,this.accessToken=null,this.refreshTimer&&(clearTimeout(this.refreshTimer),this.refreshTimer=null))}}getTokenExpiry(t){try{const e=t.split(".")[1];return JSON.parse(atob(e)).exp??null}catch{return null}}loadRefreshToken(){try{return localStorage.getItem(this.storageKey)}catch{return null}}saveRefreshToken(t){try{localStorage.setItem(this.storageKey,t)}catch{}}removeRefreshToken(){try{localStorage.removeItem(this.storageKey)}catch{}}}class v{constructor(){this.listeners=new Map}on(t,e){return this.listeners.has(t)||this.listeners.set(t,new Set),this.listeners.get(t).add(e),()=>{var s;(s=this.listeners.get(t))==null||s.delete(e)}}emit(t,e){var s;(s=this.listeners.get(t))==null||s.forEach(r=>r(e))}removeAll(){this.listeners.clear()}}const w=500,T=600,R=5*60*1e3;class b{constructor(t,e,s,r){this.cachedUser=null,this.cachedSettings=null,this.loaded=!1,this.loadPromise=null,this.transport=t,this.tokenManager=e,this.emitter=s,this.baseUrl=r}async load(){if(!this.loaded)return this.loadPromise?this.loadPromise:(this.loadPromise=this.doLoad().finally(()=>{this.loadPromise=null}),this.loadPromise)}async doLoad(){var t,e;try{this.cachedSettings=await this.transport.get("/auth/settings")}catch(s){console.warn("[SaaS Support] Failed to load project settings:",s)}if((t=this.tokenManager)!=null&&t.hasRefreshToken())try{await this.tokenManager.refreshOnce()}catch{(e=this.tokenManager)==null||e.clearTokens()}this.loaded=!0}async signIn(t,e){const s=await this.transport.post("/auth/login",{email:t,password:e});if("mfaRequired"in s&&s.mfaRequired)return s;const r=s;return this.setSession(r),r}async signUp(t,e){const s=await this.transport.post("/auth/register",{email:t,password:e});return this.setSession(s),s}async signOut(){var e;const t=(e=this.tokenManager)==null?void 0:e.getRefreshToken();if(t)try{await this.transport.post("/auth/logout",{refreshToken:t})}catch{}this.clearSession()}async signInWithOAuth(t){const e=`${this.baseUrl}/auth/oauth/${t}/popup-callback`,{authUrl:s,state:r}=await this.transport.get(`/auth/oauth/${t}?redirect_uri=${encodeURIComponent(e)}`),i=window.screenX+(window.innerWidth-w)/2,n=window.screenY+(window.innerHeight-T)/2,a=window.open(s,"saas-support-oauth",`width=${w},height=${T},left=${i},top=${n},toolbar=no,menubar=no`);return new Promise((c,h)=>{let u=!1;const g=async l=>{var k;if(((k=l.data)==null?void 0:k.type)==="saas-support:oauth-callback"&&!u){if(u=!0,window.removeEventListener("message",g),clearTimeout(m),clearInterval(f),a==null||a.close(),l.data.error){h(new Error(`OAuth error: ${l.data.error}`));return}try{const p=await this.transport.post(`/auth/oauth/${t}/callback`,{code:l.data.code,state:l.data.state||r});this.setSession(p),c(p)}catch(p){h(p)}}};window.addEventListener("message",g);const m=setTimeout(()=>{u||(u=!0,window.removeEventListener("message",g),clearInterval(f),a==null||a.close(),h(new Error("OAuth popup timed out")))},R),f=setInterval(()=>{a!=null&&a.closed&&!u&&(u=!0,clearInterval(f),clearTimeout(m),window.removeEventListener("message",g),h(new Error("OAuth popup was closed")))},500)})}async submitMfaCode(t,e){const s=await this.transport.post("/auth/login/mfa",{mfaToken:t,code:e});return this.setSession(s),s}async sendMagicLink(t,e){await this.transport.post("/auth/magic-link/send",{email:t,redirectUrl:e})}async verifyMagicLink(t){const e=await this.transport.post("/auth/magic-link/verify",{token:t});return this.setSession(e),e}async sendPasswordReset(t,e){await this.transport.post("/auth/password-reset/send",{email:t,redirectUrl:e})}async resetPassword(t,e){await this.transport.post("/auth/password-reset/verify",{token:t,newPassword:e})}async setupMfa(){return this.transport.post("/auth/mfa/setup",void 0,this.authHeaders())}async verifyMfa(t){return this.transport.post("/auth/mfa/verify",{code:t},this.authHeaders())}async disableMfa(t){await this.transport.post("/auth/mfa/disable",{code:t},this.authHeaders())}async getToken(){var e,s,r;const t=((e=this.tokenManager)==null?void 0:e.getAccessToken())??null;if(t)return t;if((s=this.tokenManager)!=null&&s.hasRefreshToken())try{return await this.tokenManager.refreshOnce(),((r=this.tokenManager)==null?void 0:r.getAccessToken())??null}catch{return this.clearSession(),null}return null}async getUser(){if(this.cachedUser)return this.cachedUser;const t=await this.getToken();if(!t)return null;try{return this.cachedUser=await this.transport.get("/auth/me",{Authorization:`Bearer ${t}`}),this.cachedUser}catch{return null}}async refreshUser(){const t=await this.getToken();if(!t)return this.cachedUser;try{return this.cachedUser=await this.transport.get("/auth/me",{Authorization:`Bearer ${t}`}),this.emitter.emit("authStateChange",this.cachedUser),this.cachedUser}catch{return this.cachedUser}}getUserSync(){return this.cachedUser}isLoaded(){return this.loaded}async getSettings(){if(this.cachedSettings)return this.cachedSettings;try{return this.cachedSettings=await this.transport.get("/auth/settings"),this.cachedSettings}catch{return null}}onAuthStateChange(t){return this.emitter.on("authStateChange",t)}async updateProfile(t){const e=await this.transport.patch("/auth/me",t,this.authHeaders());return this.cachedUser=e,this.emitter.emit("authStateChange",e),e}async uploadAvatar(t){const e=await this.transport.uploadBinary("/auth/avatar",t,this.authHeaders());return this.cachedUser&&(this.cachedUser={...this.cachedUser,avatarUrl:e.avatarUrl},this.emitter.emit("authStateChange",this.cachedUser)),e}async changePassword(t,e){await this.transport.post("/auth/change-password",{currentPassword:t,newPassword:e},this.authHeaders())}async listOrgs(){return this.transport.get("/auth/orgs",this.authHeaders())}async createOrg(t,e){return this.transport.post("/auth/orgs",{name:t,slug:e},this.authHeaders())}async getOrg(t){return this.transport.get(`/auth/orgs/${t}`,this.authHeaders())}async updateOrg(t,e){return this.transport.patch(`/auth/orgs/${t}`,e,this.authHeaders())}async deleteOrg(t){await this.transport.del(`/auth/orgs/${t}`,this.authHeaders())}async listMembers(t){return this.transport.get(`/auth/orgs/${t}/members`,this.authHeaders())}async sendInvite(t,e,s){return this.transport.post(`/auth/orgs/${t}/invites`,{email:e,role:s},this.authHeaders())}async updateMemberRole(t,e,s){await this.transport.patch(`/auth/orgs/${t}/members/${e}`,{role:s},this.authHeaders())}async removeMember(t,e){await this.transport.del(`/auth/orgs/${t}/members/${e}`,this.authHeaders())}async acceptInvite(t){return this.transport.post(`/auth/invites/${t}/accept`,void 0,this.authHeaders())}async listInvites(t){return this.transport.get(`/auth/orgs/${t}/invites`,this.authHeaders())}async revokeInvite(t,e){await this.transport.del(`/auth/orgs/${t}/invites/${e}`,this.authHeaders())}async listMyInvites(){return this.transport.get("/auth/invites/pending",this.authHeaders())}async acceptInviteById(t){return this.transport.post(`/auth/invites/${t}/accept-by-id`,void 0,this.authHeaders())}async declineInvite(t){await this.transport.del(`/auth/invites/${t}/decline`,this.authHeaders())}async deleteAccount(){await this.transport.del("/auth/account",this.authHeaders()),this.clearSession()}handleExternalLogout(){this.cachedUser=null,this.emitter.emit("authStateChange",null)}async performRefresh(){var s;const t=(s=this.tokenManager)==null?void 0:s.getRefreshToken();if(!t)throw new Error("No refresh token");const e=await this.transport.post("/auth/refresh",{refreshToken:t});if(this.tokenManager.setTokens(e.accessToken,e.refreshToken),!this.cachedUser)try{this.cachedUser=await this.transport.get("/auth/me",{Authorization:`Bearer ${e.accessToken}`}),this.emitter.emit("authStateChange",this.cachedUser)}catch{}}setSession(t){var e;(e=this.tokenManager)==null||e.setTokens(t.accessToken,t.refreshToken),this.cachedUser=t.user,this.emitter.emit("authStateChange",t.user),this.refreshUser()}clearSession(){var t;(t=this.tokenManager)==null||t.clearTokens(),this.cachedUser=null,this.emitter.emit("authStateChange",null)}authHeaders(){var e;const t=(e=this.tokenManager)==null?void 0:e.getAccessToken();return t?{Authorization:`Bearer ${t}`}:{}}}class S{constructor(t){this.transport=t}async createCustomer(t){return this.transport.post("/billing/customers",t)}async getCustomer(t){return this.transport.get(`/billing/customers/${t}`)}async updateCustomer(t,e){return this.transport.patch(`/billing/customers/${t}`,e)}async subscribe(t,e){return this.transport.post(`/billing/customers/${t}/subscribe`,{planId:e})}async changePlan(t,e){return this.transport.patch(`/billing/customers/${t}/subscription`,{planId:e})}async cancelSubscription(t){return this.transport.del(`/billing/customers/${t}/subscription`)}async getInvoices(t){return this.transport.get(`/billing/customers/${t}/invoices`)}async ingestUsageEvent(t){return this.transport.post("/billing/events",t)}async getCurrentUsage(t){return this.transport.get(`/billing/customers/${t}/usage`)}async createPortalToken(t,e){return this.transport.post("/billing/portal-tokens",{customerId:t,expiresIn:e})}async applyCoupon(t,e){return this.transport.post(`/billing/customers/${t}/coupon`,{code:e})}}class U{constructor(t){this.transport=t}async executeQuery(t){return this.transport.post("/report/query",t)}async listQueries(t){const e=t?this.toQueryString(t):"";return this.transport.get(`/report/queries${e}`)}async saveQuery(t){return this.transport.post("/report/queries",t)}async updateQuery(t,e){return this.transport.patch(`/report/queries/${t}`,e)}async deleteQuery(t){await this.transport.del(`/report/queries/${t}`)}async listDashboards(t){const e=t?this.toQueryString(t):"";return this.transport.get(`/report/dashboards${e}`)}async createDashboard(t){return this.transport.post("/report/dashboards",t)}async getDashboard(t){return this.transport.get(`/report/dashboards/${t}`)}async updateDashboard(t,e){return this.transport.patch(`/report/dashboards/${t}`,e)}async deleteDashboard(t){await this.transport.del(`/report/dashboards/${t}`)}async createEmbedToken(t){return this.transport.post("/report/embed-tokens",t)}async listEmbedTokens(){return this.transport.get("/report/embed-tokens")}async revokeEmbedToken(t){await this.transport.del(`/report/embed-tokens/${t}`)}toQueryString(t){const e=Object.entries(t).filter(([,s])=>s!=null&&s!=="");return e.length===0?"":"?"+e.map(([s,r])=>`${s}=${encodeURIComponent(String(r))}`).join("&")}}const M="https://api.saas-support.com/v1";class H{constructor(t){if(this.tokenManager=null,this.loaded=!1,this.loadPromise=null,!t.publishableKey&&!t.apiKey)throw new Error("SaaSSupport: either publishableKey or apiKey is required");const e=t.baseUrl??M;this.emitter=new v;const s=t.publishableKey?new y(e,{type:"publishableKey",key:t.publishableKey}):null,r=t.apiKey?new y(e,{type:"apiKey",key:t.apiKey}):null;t.publishableKey&&(this.tokenManager=new $(t.publishableKey)),this.auth=new b(s??r,this.tokenManager,this.emitter,e),this.billing=new S(r??s),this.report=new U(r??s),this.tokenManager&&(this.tokenManager.setRefreshCallback(()=>this.auth.performRefresh()),this.tokenManager.setTokensChangedCallback(()=>{this.tokenManager.hasRefreshToken()||this.auth.handleExternalLogout()})),this.tokenManager&&s&&s.setUnauthorizedHandler(async()=>{try{return await this.tokenManager.refreshOnce(),this.tokenManager.getAccessToken()}catch{return null}})}async load(){if(!this.loaded)return this.loadPromise?this.loadPromise:(this.loadPromise=this.auth.load().then(()=>{this.loaded=!0}).finally(()=>{this.loadPromise=null}),this.loadPromise)}isLoaded(){return this.loaded}onError(t){return this.emitter.on("error",t)}destroy(){var t;(t=this.tokenManager)==null||t.destroy(),this.emitter.removeAll()}}function C(o){return"mfaRequired"in o&&o.mfaRequired===!0}exports.AuthClient=b;exports.BillingClient=S;exports.ReportClient=U;exports.SaaSError=d;exports.SaaSSupport=H;exports.Transport=y;exports.isMfaRequired=C;
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});class d extends Error{constructor(e,t,s="unknown"){super(t),this.name="SaaSError",this.code=e,this.domain=s}get isNotFound(){return this.code===404}get isUnauthorized(){return this.code===401}get isForbidden(){return this.code===403}get isConflict(){return this.code===409}get isRateLimited(){return this.code===429}}class y{constructor(e,t){this.onUnauthorized=null,this.baseUrl=e,this.authMode=t}setUnauthorizedHandler(e){this.onUnauthorized=e}async request(e,t,s,r){try{return await this.doRequest(e,t,s,r)}catch(i){if(i instanceof d&&i.isUnauthorized&&this.onUnauthorized&&(r!=null&&r.Authorization)){const n=await this.onUnauthorized();if(n)return this.doRequest(e,t,s,{...r,Authorization:`Bearer ${n}`})}throw i}}async get(e,t){return this.request("GET",e,void 0,t)}async post(e,t,s){return this.request("POST",e,t,s)}async patch(e,t,s){return this.request("PATCH",e,t,s)}async del(e,t){return this.request("DELETE",e,void 0,t)}async uploadBinary(e,t,s){try{return await this.doUploadBinary(e,t,s)}catch(r){if(r instanceof d&&r.isUnauthorized&&this.onUnauthorized&&(s!=null&&s.Authorization)){const i=await this.onUnauthorized();if(i)return this.doUploadBinary(e,t,{...s,Authorization:`Bearer ${i}`})}throw r}}async doUploadBinary(e,t,s){const r={"Content-Type":"application/octet-stream",...this.getAuthHeaders(),...s},i=await fetch(`${this.baseUrl}${e}`,{method:"POST",headers:r,body:t}),n=await i.json();if(!i.ok||n.isOk===!1||n.code&&n.code>=400){const a=this.inferDomain(e),c=n.code||i.status;throw new d(c,n.message||"Upload failed",a)}return n.data}async doRequest(e,t,s,r){const i={"Content-Type":"application/json",...this.getAuthHeaders(),...r},n=await fetch(`${this.baseUrl}${t}`,{method:e,headers:i,body:s?JSON.stringify(s):void 0}),a=await n.json();if(!n.ok||a.isOk===!1||a.code&&a.code>=400){const c=this.inferDomain(t),o=a.code||n.status;throw new d(o,a.message||"Request failed",c)}return a.data}getAuthHeaders(){switch(this.authMode.type){case"publishableKey":case"apiKey":return{"X-API-Key":this.authMode.key};case"portalToken":case"embedToken":return{Authorization:`Bearer ${this.authMode.token}`}}}inferDomain(e){return e.startsWith("/auth")?"auth":e.startsWith("/billing")?"billing":e.startsWith("/report")?"report":"unknown"}}class U{constructor(e){this.accessToken=null,this.refreshToken=null,this.refreshTimer=null,this.refreshInFlight=null,this.onRefreshNeeded=null,this.onTokensChanged=null,this.boundHandleStorage=null,this.storageKey=`ss_rt_${e.slice(0,12)}`,this.refreshToken=this.loadRefreshToken(),typeof window<"u"&&(this.boundHandleStorage=this.handleStorageEvent.bind(this),window.addEventListener("storage",this.boundHandleStorage))}setRefreshCallback(e){this.onRefreshNeeded=e}setTokensChangedCallback(e){this.onTokensChanged=e}getAccessToken(){return this.accessToken}getRefreshToken(){return this.refreshToken}hasRefreshToken(){return this.refreshToken!==null}setTokens(e,t){this.accessToken=e,this.refreshToken=t,this.saveRefreshToken(t),this.scheduleRefresh(e)}clearTokens(){this.accessToken=null,this.refreshToken=null,this.removeRefreshToken(),this.refreshTimer&&(clearTimeout(this.refreshTimer),this.refreshTimer=null)}async refreshOnce(){return this.refreshInFlight?this.refreshInFlight:(this.refreshInFlight=this.executeRefresh().finally(()=>{this.refreshInFlight=null}),this.refreshInFlight)}destroy(){this.refreshTimer&&(clearTimeout(this.refreshTimer),this.refreshTimer=null),typeof window<"u"&&this.boundHandleStorage&&(window.removeEventListener("storage",this.boundHandleStorage),this.boundHandleStorage=null)}async executeRefresh(){if(!this.onRefreshNeeded)throw new Error("No refresh callback configured");typeof navigator<"u"&&"locks"in navigator?await navigator.locks.request(`ss_refresh_lock_${this.storageKey}`,async()=>{const e=this.loadRefreshToken();e&&e!==this.refreshToken&&(this.refreshToken=e),await this.onRefreshNeeded()}):await this.onRefreshNeeded()}scheduleRefresh(e){this.refreshTimer&&clearTimeout(this.refreshTimer);const t=this.getTokenExpiry(e);if(!t)return;const s=t*1e3-Date.now()-6e4;if(s<=0){this.refreshOnce().catch(()=>{});return}this.refreshTimer=setTimeout(()=>{this.refreshOnce().catch(()=>{})},s)}handleStorageEvent(e){var t;if(e.key===this.storageKey){if(e.newValue===null){this.accessToken=null,this.refreshToken=null,this.refreshTimer&&(clearTimeout(this.refreshTimer),this.refreshTimer=null),(t=this.onTokensChanged)==null||t.call(this);return}e.newValue!==this.refreshToken&&(this.refreshToken=e.newValue,this.accessToken=null,this.refreshTimer&&(clearTimeout(this.refreshTimer),this.refreshTimer=null))}}getTokenExpiry(e){try{const t=e.split(".")[1];return JSON.parse(atob(t)).exp??null}catch{return null}}loadRefreshToken(){try{return localStorage.getItem(this.storageKey)}catch{return null}}saveRefreshToken(e){try{localStorage.setItem(this.storageKey,e)}catch{}}removeRefreshToken(){try{localStorage.removeItem(this.storageKey)}catch{}}}class b{constructor(){this.listeners=new Map}on(e,t){return this.listeners.has(e)||this.listeners.set(e,new Set),this.listeners.get(e).add(t),()=>{var s;(s=this.listeners.get(e))==null||s.delete(t)}}emit(e,t){var s;(s=this.listeners.get(e))==null||s.forEach(r=>r(t))}removeAll(){this.listeners.clear()}}const w=500,T=600,v=5*60*1e3;class S{constructor(e,t,s,r){this.cachedUser=null,this.cachedSettings=null,this.loaded=!1,this.loadPromise=null,this.transport=e,this.tokenManager=t,this.emitter=s,this.baseUrl=r}async load(){if(!this.loaded)return this.loadPromise?this.loadPromise:(this.loadPromise=this.doLoad().finally(()=>{this.loadPromise=null}),this.loadPromise)}async doLoad(){var e,t;try{this.cachedSettings=await this.transport.get("/auth/settings")}catch(s){console.warn("[SaaS Support] Failed to load project settings:",s)}if((e=this.tokenManager)!=null&&e.hasRefreshToken())try{await this.tokenManager.refreshOnce()}catch{(t=this.tokenManager)==null||t.clearTokens()}this.loaded=!0}async signIn(e,t){const s=await this.transport.post("/auth/login",{email:e,password:t});if("mfaRequired"in s&&s.mfaRequired)return s;const r=s;return this.setSession(r),r}async signUp(e,t){const s=await this.transport.post("/auth/register",{email:e,password:t});return this.setSession(s),s}async signOut(){var t;const e=(t=this.tokenManager)==null?void 0:t.getRefreshToken();if(e)try{await this.transport.post("/auth/logout",{refreshToken:e})}catch{}this.clearSession()}async signInWithOAuth(e){const t=`${this.baseUrl}/auth/oauth/${e}/popup-callback`,{authUrl:s,state:r}=await this.transport.get(`/auth/oauth/${e}?redirect_uri=${encodeURIComponent(t)}`),i=window.screenX+(window.innerWidth-w)/2,n=window.screenY+(window.innerHeight-T)/2,a=window.open(s,"saas-support-oauth",`width=${w},height=${T},left=${i},top=${n},toolbar=no,menubar=no`);return new Promise((c,o)=>{let u=!1;const f=async l=>{var k;if(((k=l.data)==null?void 0:k.type)==="saas-support:oauth-callback"&&!u){if(u=!0,window.removeEventListener("message",f),clearTimeout(m),clearInterval(p),a==null||a.close(),l.data.error){o(new Error(`OAuth error: ${l.data.error}`));return}try{const g=await this.transport.post(`/auth/oauth/${e}/callback`,{code:l.data.code,state:l.data.state||r});this.setSession(g),c(g)}catch(g){o(g)}}};window.addEventListener("message",f);const m=setTimeout(()=>{u||(u=!0,window.removeEventListener("message",f),clearInterval(p),a==null||a.close(),o(new Error("OAuth popup timed out")))},v),p=setInterval(()=>{a!=null&&a.closed&&!u&&(u=!0,clearInterval(p),clearTimeout(m),window.removeEventListener("message",f),o(new Error("OAuth popup was closed")))},500)})}async submitMfaCode(e,t){const s=await this.transport.post("/auth/login/mfa",{mfaToken:e,code:t});return this.setSession(s),s}async sendMagicLink(e,t){await this.transport.post("/auth/magic-link/send",{email:e,redirectUrl:t})}async verifyMagicLink(e){const t=await this.transport.post("/auth/magic-link/verify",{token:e});return this.setSession(t),t}async sendPasswordReset(e,t){await this.transport.post("/auth/password-reset/send",{email:e,redirectUrl:t})}async resetPassword(e,t){await this.transport.post("/auth/password-reset/verify",{token:e,newPassword:t})}async setupMfa(){return this.transport.post("/auth/mfa/setup",void 0,this.authHeaders())}async verifyMfa(e){return this.transport.post("/auth/mfa/verify",{code:e},this.authHeaders())}async disableMfa(e){await this.transport.post("/auth/mfa/disable",{code:e},this.authHeaders())}async getToken(){var t,s,r;const e=((t=this.tokenManager)==null?void 0:t.getAccessToken())??null;if(e)return e;if((s=this.tokenManager)!=null&&s.hasRefreshToken())try{return await this.tokenManager.refreshOnce(),((r=this.tokenManager)==null?void 0:r.getAccessToken())??null}catch{return this.clearSession(),null}return null}async getUser(){if(this.cachedUser)return this.cachedUser;const e=await this.getToken();if(!e)return null;try{return this.cachedUser=await this.transport.get("/auth/me",{Authorization:`Bearer ${e}`}),this.cachedUser}catch{return null}}async refreshUser(){const e=await this.getToken();if(!e)return this.cachedUser;try{return this.cachedUser=await this.transport.get("/auth/me",{Authorization:`Bearer ${e}`}),this.emitter.emit("authStateChange",this.cachedUser),this.cachedUser}catch{return this.cachedUser}}getUserSync(){return this.cachedUser}isLoaded(){return this.loaded}async getSettings(){if(this.cachedSettings)return this.cachedSettings;try{return this.cachedSettings=await this.transport.get("/auth/settings"),this.cachedSettings}catch{return null}}onAuthStateChange(e){return this.emitter.on("authStateChange",e)}async updateProfile(e){const t=await this.transport.patch("/auth/me",e,this.authHeaders());return this.cachedUser=t,this.emitter.emit("authStateChange",t),t}async uploadAvatar(e){const t=await this.transport.uploadBinary("/auth/avatar",e,this.authHeaders());return this.cachedUser&&(this.cachedUser={...this.cachedUser,avatarUrl:t.avatarUrl},this.emitter.emit("authStateChange",this.cachedUser)),t}async changePassword(e,t){await this.transport.post("/auth/change-password",{currentPassword:e,newPassword:t},this.authHeaders())}async listOrgs(){return this.transport.get("/auth/orgs",this.authHeaders())}async createOrg(e,t){return this.transport.post("/auth/orgs",{name:e,slug:t},this.authHeaders())}async getOrg(e){return this.transport.get(`/auth/orgs/${e}`,this.authHeaders())}async updateOrg(e,t){return this.transport.patch(`/auth/orgs/${e}`,t,this.authHeaders())}async deleteOrg(e){await this.transport.del(`/auth/orgs/${e}`,this.authHeaders())}async uploadOrgAvatar(e,t){return this.transport.uploadBinary(`/auth/orgs/${e}/avatar`,t,this.authHeaders())}async listMembers(e){return this.transport.get(`/auth/orgs/${e}/members`,this.authHeaders())}async sendInvite(e,t,s){return this.transport.post(`/auth/orgs/${e}/invites`,{email:t,role:s},this.authHeaders())}async updateMemberRole(e,t,s){await this.transport.patch(`/auth/orgs/${e}/members/${t}`,{role:s},this.authHeaders())}async removeMember(e,t){await this.transport.del(`/auth/orgs/${e}/members/${t}`,this.authHeaders())}async acceptInvite(e){return this.transport.post(`/auth/invites/${e}/accept`,void 0,this.authHeaders())}async listInvites(e){return this.transport.get(`/auth/orgs/${e}/invites`,this.authHeaders())}async revokeInvite(e,t){await this.transport.del(`/auth/orgs/${e}/invites/${t}`,this.authHeaders())}async listMyInvites(){return this.transport.get("/auth/invites/pending",this.authHeaders())}async acceptInviteById(e){return this.transport.post(`/auth/invites/${e}/accept-by-id`,void 0,this.authHeaders())}async declineInvite(e){await this.transport.del(`/auth/invites/${e}/decline`,this.authHeaders())}async deleteAccount(){await this.transport.del("/auth/account",this.authHeaders()),this.clearSession()}handleExternalLogout(){this.cachedUser=null,this.emitter.emit("authStateChange",null)}async performRefresh(){var s;const e=(s=this.tokenManager)==null?void 0:s.getRefreshToken();if(!e)throw new Error("No refresh token");const t=await this.transport.post("/auth/refresh",{refreshToken:e});if(this.tokenManager.setTokens(t.accessToken,t.refreshToken),!this.cachedUser)try{this.cachedUser=await this.transport.get("/auth/me",{Authorization:`Bearer ${t.accessToken}`}),this.emitter.emit("authStateChange",this.cachedUser)}catch{}}setSession(e){var t;(t=this.tokenManager)==null||t.setTokens(e.accessToken,e.refreshToken),this.cachedUser=e.user,this.emitter.emit("authStateChange",e.user),this.refreshUser()}clearSession(){var e;(e=this.tokenManager)==null||e.clearTokens(),this.cachedUser=null,this.emitter.emit("authStateChange",null)}authHeaders(){var t;const e=(t=this.tokenManager)==null?void 0:t.getAccessToken();return e?{Authorization:`Bearer ${e}`}:{}}}const M="https://api.saas-support.com/v1";class H{constructor(e){if(this.tokenManager=null,this.loaded=!1,this.loadPromise=null,!e.publishableKey&&!e.apiKey)throw new Error("SaaSSupport: either publishableKey or apiKey is required");const t=e.baseUrl??M;this.emitter=new b;const s=e.publishableKey?new y(t,{type:"publishableKey",key:e.publishableKey}):null,r=e.apiKey?new y(t,{type:"apiKey",key:e.apiKey}):null;e.publishableKey&&(this.tokenManager=new U(e.publishableKey)),this.auth=new S(s??r,this.tokenManager,this.emitter,t),this.tokenManager&&(this.tokenManager.setRefreshCallback(()=>this.auth.performRefresh()),this.tokenManager.setTokensChangedCallback(()=>{this.tokenManager.hasRefreshToken()||this.auth.handleExternalLogout()})),this.tokenManager&&s&&s.setUnauthorizedHandler(async()=>{try{return await this.tokenManager.refreshOnce(),this.tokenManager.getAccessToken()}catch{return null}})}async load(){if(!this.loaded)return this.loadPromise?this.loadPromise:(this.loadPromise=this.auth.load().then(()=>{this.loaded=!0}).finally(()=>{this.loadPromise=null}),this.loadPromise)}isLoaded(){return this.loaded}onError(e){return this.emitter.on("error",e)}destroy(){var e;(e=this.tokenManager)==null||e.destroy(),this.emitter.removeAll()}}function R(h){return"mfaRequired"in h&&h.mfaRequired===!0}exports.AuthClient=S;exports.SaaSError=d;exports.SaaSSupport=H;exports.Transport=y;exports.isMfaRequired=R;
package/dist/index.d.ts CHANGED
@@ -6,13 +6,6 @@ export declare interface Appearance {
6
6
  fontUrl?: string | null;
7
7
  }
8
8
 
9
- export declare interface ApplyCouponResult {
10
- applied: boolean;
11
- discountType: string;
12
- amount: number;
13
- duration: string;
14
- }
15
-
16
9
  export declare class AuthClient {
17
10
  private transport;
18
11
  private tokenManager;
@@ -64,6 +57,12 @@ export declare class AuthClient {
64
57
  avatarUrl?: string;
65
58
  }): Promise<Org>;
66
59
  deleteOrg(orgId: string): Promise<void>;
60
+ uploadOrgAvatar(orgId: string, imageBlob: Blob): Promise<{
61
+ avatarUrl: string;
62
+ small: string;
63
+ medium: string;
64
+ original: string;
65
+ }>;
67
66
  listMembers(orgId: string): Promise<Member[]>;
68
67
  sendInvite(orgId: string, email: string, role: string): Promise<Invite>;
69
68
  updateMemberRole(orgId: string, userId: string, role: string): Promise<void>;
@@ -106,66 +105,6 @@ export declare type AuthResult = SignInResult | MfaRequiredResult;
106
105
 
107
106
  export declare type AuthStateCallback = (user: User | null) => void;
108
107
 
109
- export declare class BillingClient {
110
- private transport;
111
- constructor(transport: Transport);
112
- createCustomer(params: {
113
- email: string;
114
- name?: string;
115
- metadata?: string;
116
- }): Promise<Customer>;
117
- getCustomer(customerId: string): Promise<Customer>;
118
- updateCustomer(customerId: string, params: {
119
- email?: string;
120
- name?: string;
121
- metadata?: string;
122
- }): Promise<Customer>;
123
- subscribe(customerId: string, planId: string): Promise<Subscription>;
124
- changePlan(customerId: string, planId: string): Promise<Subscription>;
125
- cancelSubscription(customerId: string): Promise<{
126
- canceledAtPeriodEnd: boolean;
127
- }>;
128
- getInvoices(customerId: string): Promise<Invoice[]>;
129
- ingestUsageEvent(params: {
130
- customerId: string;
131
- metric: string;
132
- quantity: number;
133
- timestamp?: string;
134
- idempotencyKey?: string;
135
- }): Promise<{
136
- id: string;
137
- ingested: boolean;
138
- }>;
139
- getCurrentUsage(customerId: string): Promise<UsageSummary[]>;
140
- createPortalToken(customerId: string, expiresIn?: number): Promise<PortalTokenResult>;
141
- applyCoupon(customerId: string, code: string): Promise<ApplyCouponResult>;
142
- }
143
-
144
- export declare interface Customer {
145
- id: string;
146
- projectId: string;
147
- email: string;
148
- name?: string;
149
- stripeCustomerId?: string;
150
- balanceCents: number;
151
- metadata?: string;
152
- taxExempt: boolean;
153
- taxId?: string;
154
- createdAt: string;
155
- updatedAt: string;
156
- }
157
-
158
- export declare interface Dashboard {
159
- id: string;
160
- projectId: string;
161
- name: string;
162
- layoutJson: string;
163
- isPublic: boolean;
164
- refreshIntervalSeconds: number;
165
- createdAt: string;
166
- updatedAt: string;
167
- }
168
-
169
108
  export declare interface ElementOverrides {
170
109
  card?: React.CSSProperties;
171
110
  headerTitle?: React.CSSProperties;
@@ -192,22 +131,6 @@ export declare interface ElementOverrides {
192
131
  profileHeader?: React.CSSProperties;
193
132
  }
194
133
 
195
- export declare interface EmbedToken {
196
- id: string;
197
- projectId: string;
198
- dashboardId?: string;
199
- customerId?: string;
200
- filterRules?: FilterRule[];
201
- expiresAt: string;
202
- createdAt: string;
203
- revokedAt?: string;
204
- }
205
-
206
- export declare interface EmbedTokenResult {
207
- embedToken: string;
208
- expiresAt: string;
209
- }
210
-
211
134
  declare class EventEmitter<Events extends Record<string, any>> {
212
135
  private listeners;
213
136
  on<K extends keyof Events>(event: K, listener: Listener<Events[K]>): () => void;
@@ -215,13 +138,6 @@ declare class EventEmitter<Events extends Record<string, any>> {
215
138
  removeAll(): void;
216
139
  }
217
140
 
218
- export declare interface FilterRule {
219
- table?: string;
220
- column: string;
221
- op: 'eq' | 'neq' | 'gt' | 'gte' | 'lt' | 'lte' | 'in' | 'between';
222
- value: string;
223
- }
224
-
225
141
  export declare interface Invite {
226
142
  inviteId: string;
227
143
  email: string;
@@ -230,32 +146,10 @@ export declare interface Invite {
230
146
  expiresAt: string;
231
147
  }
232
148
 
233
- export declare interface Invoice {
234
- id: string;
235
- projectId: string;
236
- customerId: string;
237
- subscriptionId?: string;
238
- amountCents: number;
239
- status: 'draft' | 'open' | 'paid' | 'void' | 'uncollectible';
240
- stripeInvoiceId?: string;
241
- pdfUrl?: string;
242
- dueDate?: string;
243
- paidAt?: string;
244
- createdAt: string;
245
- }
246
-
247
149
  export declare function isMfaRequired(result: AuthResult): result is MfaRequiredResult;
248
150
 
249
151
  declare type Listener<T> = (data: T) => void;
250
152
 
251
- export declare interface ListParams {
252
- page?: number;
253
- perPage?: number;
254
- sort?: string;
255
- order?: string;
256
- search?: string;
257
- }
258
-
259
153
  export declare interface Member {
260
154
  userId: string;
261
155
  email: string;
@@ -287,16 +181,6 @@ export declare interface MyPendingInvite {
287
181
 
288
182
  export declare type OAuthProvider = 'google' | 'github';
289
183
 
290
- export declare interface OffsetPage<T> {
291
- data: T[];
292
- meta: {
293
- page: number;
294
- perPage: number;
295
- total: number;
296
- totalPages: number;
297
- };
298
- }
299
-
300
184
  export declare interface Org {
301
185
  id: string;
302
186
  projectId: string;
@@ -315,24 +199,6 @@ export declare interface PendingInvite {
315
199
  createdAt: string;
316
200
  }
317
201
 
318
- export declare interface Plan {
319
- id: string;
320
- name: string;
321
- description?: string;
322
- amountCents: number;
323
- interval: 'month' | 'year';
324
- currency: string;
325
- trialDays: number;
326
- isFree: boolean;
327
- features: string[];
328
- isActive: boolean;
329
- }
330
-
331
- export declare interface PortalTokenResult {
332
- portalToken: string;
333
- expiresAt: string;
334
- }
335
-
336
202
  export declare interface ProjectSettings {
337
203
  googleEnabled: boolean;
338
204
  githubEnabled: boolean;
@@ -340,64 +206,8 @@ export declare interface ProjectSettings {
340
206
  mfaEnforced: boolean;
341
207
  passwordMinLength: number;
342
208
  emailVerification: boolean;
343
- }
344
-
345
- export declare interface QueryParams {
346
- naturalLanguage?: string;
347
- sql?: string;
348
- filterRules?: FilterRule[];
349
- }
350
-
351
- export declare interface QueryResult {
352
- sql: string;
353
- columns: string[];
354
- rows: Record<string, unknown>[];
355
- rowCount: number;
356
- executionMs: number;
357
- chartType: string;
358
- cached: boolean;
359
- }
360
-
361
- export declare class ReportClient {
362
- private transport;
363
- constructor(transport: Transport);
364
- executeQuery(params: QueryParams): Promise<QueryResult>;
365
- listQueries(params?: ListParams): Promise<OffsetPage<SavedQuery>>;
366
- saveQuery(params: {
367
- name: string;
368
- naturalLanguage?: string;
369
- generatedSql?: string;
370
- chartType?: string;
371
- }): Promise<SavedQuery>;
372
- updateQuery(queryId: string, params: {
373
- name?: string;
374
- chartType?: string;
375
- }): Promise<SavedQuery>;
376
- deleteQuery(queryId: string): Promise<void>;
377
- listDashboards(params?: ListParams): Promise<OffsetPage<Dashboard>>;
378
- createDashboard(params: {
379
- name: string;
380
- layoutJson?: string;
381
- isPublic?: boolean;
382
- refreshIntervalSeconds?: number;
383
- }): Promise<Dashboard>;
384
- getDashboard(dashboardId: string): Promise<Dashboard>;
385
- updateDashboard(dashboardId: string, params: {
386
- name?: string;
387
- layoutJson?: string;
388
- isPublic?: boolean;
389
- refreshIntervalSeconds?: number;
390
- }): Promise<Dashboard>;
391
- deleteDashboard(dashboardId: string): Promise<void>;
392
- createEmbedToken(params: {
393
- dashboardId?: string;
394
- customerId?: string;
395
- expiresIn?: number;
396
- filterRules?: FilterRule[];
397
- }): Promise<EmbedTokenResult>;
398
- listEmbedTokens(): Promise<EmbedToken[]>;
399
- revokeEmbedToken(tokenId: string): Promise<void>;
400
- private toQueryString;
209
+ privacyPolicyUrl?: string;
210
+ termsOfServiceUrl?: string;
401
211
  }
402
212
 
403
213
  export declare class SaaSError extends Error {
@@ -427,8 +237,6 @@ export declare interface SaaSOptions {
427
237
 
428
238
  export declare class SaaSSupport {
429
239
  readonly auth: AuthClient;
430
- readonly billing: BillingClient;
431
- readonly report: ReportClient;
432
240
  private tokenManager;
433
241
  private emitter;
434
242
  private loaded;
@@ -440,17 +248,6 @@ export declare class SaaSSupport {
440
248
  destroy(): void;
441
249
  }
442
250
 
443
- export declare interface SavedQuery {
444
- id: string;
445
- projectId: string;
446
- name: string;
447
- naturalLanguage?: string;
448
- generatedSql?: string;
449
- chartType: string;
450
- createdAt: string;
451
- updatedAt: string;
452
- }
453
-
454
251
  export declare interface SignInResult {
455
252
  user: User;
456
253
  accessToken: string;
@@ -463,21 +260,6 @@ export declare interface SignUpResult {
463
260
  refreshToken: string;
464
261
  }
465
262
 
466
- export declare interface Subscription {
467
- id: string;
468
- customerId: string;
469
- planId: string;
470
- projectId: string;
471
- status: 'trialing' | 'active' | 'past_due' | 'paused' | 'canceled';
472
- stripeSubscriptionId?: string;
473
- cancelAtPeriodEnd: boolean;
474
- trialEnd?: string;
475
- currentPeriodStart: string;
476
- currentPeriodEnd: string;
477
- canceledAt?: string;
478
- createdAt: string;
479
- }
480
-
481
263
  export declare interface ThemeVariables {
482
264
  colorPrimary?: string;
483
265
  colorBackground?: string;
@@ -548,11 +330,6 @@ export declare class Transport {
548
330
  private inferDomain;
549
331
  }
550
332
 
551
- export declare interface UsageSummary {
552
- metric: string;
553
- total: number;
554
- }
555
-
556
333
  export declare interface User {
557
334
  id: string;
558
335
  email: string;