@saas-support/react 0.7.3 → 0.7.6

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),u=n.code||i.status;throw new d(u,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 u=this.inferDomain(e),h=a.code||n.status;throw new d(h,a.message||"Request failed",u)}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 R{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,v=5*60*1e3;class b{constructor(t,e,s,r){this.cachedUser=null,this.cachedSettings=null,this.loaded=!1,this.transport=t,this.tokenManager=e,this.emitter=s,this.baseUrl=r}async load(){var t,e;if(!this.loaded){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.performRefresh()}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((u,h)=>{let c=!1;const g=async l=>{var k;if(((k=l.data)==null?void 0:k.type)==="saas-support:oauth-callback"&&!c){if(c=!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),u(p)}catch(p){h(p)}}};window.addEventListener("message",g);const m=setTimeout(()=>{c||(c=!0,window.removeEventListener("message",g),clearInterval(f),a==null||a.close(),h(new Error("OAuth popup timed out")))},v),f=setInterval(()=>{a!=null&&a.closed&&!c&&(c=!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 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,!t.publishableKey&&!t.apiKey)throw new Error("SaaSSupport: either publishableKey or apiKey is required");const e=t.baseUrl??M;this.emitter=new R;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(){this.loaded||(await this.auth.load(),this.loaded=!0)}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(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;
package/dist/index.d.ts CHANGED
@@ -21,8 +21,10 @@ export declare class AuthClient {
21
21
  private cachedUser;
22
22
  private cachedSettings;
23
23
  private loaded;
24
+ private loadPromise;
24
25
  constructor(transport: Transport, tokenManager: TokenManager | null, emitter: EventEmitter<SaaSEvents>, baseUrl: string);
25
26
  load(): Promise<void>;
27
+ private doLoad;
26
28
  signIn(email: string, password: string): Promise<AuthResult>;
27
29
  signUp(email: string, password: string): Promise<SignUpResult>;
28
30
  signOut(): Promise<void>;
@@ -72,6 +74,12 @@ export declare class AuthClient {
72
74
  }>;
73
75
  listInvites(orgId: string): Promise<PendingInvite[]>;
74
76
  revokeInvite(orgId: string, inviteId: string): Promise<void>;
77
+ listMyInvites(): Promise<MyPendingInvite[]>;
78
+ acceptInviteById(inviteId: string): Promise<{
79
+ orgId: string;
80
+ role: string;
81
+ }>;
82
+ declineInvite(inviteId: string): Promise<void>;
75
83
  deleteAccount(): Promise<void>;
76
84
  /* Excluded from this release type: handleExternalLogout */
77
85
  /* Excluded from this release type: performRefresh */
@@ -268,6 +276,15 @@ export declare interface MfaVerifyResult {
268
276
  backupCodes: string[];
269
277
  }
270
278
 
279
+ export declare interface MyPendingInvite {
280
+ id: string;
281
+ orgId: string;
282
+ orgName: string;
283
+ role: string;
284
+ expiresAt: string;
285
+ createdAt: string;
286
+ }
287
+
271
288
  export declare type OAuthProvider = 'google' | 'github';
272
289
 
273
290
  export declare interface OffsetPage<T> {
@@ -415,6 +432,7 @@ export declare class SaaSSupport {
415
432
  private tokenManager;
416
433
  private emitter;
417
434
  private loaded;
435
+ private loadPromise;
418
436
  constructor(options: SaaSOptions);
419
437
  load(): Promise<void>;
420
438
  isLoaded(): boolean;
package/dist/index.js CHANGED
@@ -236,27 +236,31 @@ class U {
236
236
  const w = 500, T = 600, S = 5 * 60 * 1e3;
237
237
  class $ {
238
238
  constructor(t, e, s, r) {
239
- this.cachedUser = null, this.cachedSettings = null, this.loaded = !1, this.transport = t, this.tokenManager = e, this.emitter = s, this.baseUrl = r;
239
+ this.cachedUser = null, this.cachedSettings = null, this.loaded = !1, this.loadPromise = null, this.transport = t, this.tokenManager = e, this.emitter = s, this.baseUrl = r;
240
240
  }
241
241
  // ---------------------------------------------------------------------------
242
242
  // Lifecycle
243
243
  // ---------------------------------------------------------------------------
244
244
  async load() {
245
+ if (!this.loaded)
246
+ return this.loadPromise ? this.loadPromise : (this.loadPromise = this.doLoad().finally(() => {
247
+ this.loadPromise = null;
248
+ }), this.loadPromise);
249
+ }
250
+ async doLoad() {
245
251
  var t, e;
246
- if (!this.loaded) {
252
+ try {
253
+ this.cachedSettings = await this.transport.get("/auth/settings");
254
+ } catch (s) {
255
+ console.warn("[SaaS Support] Failed to load project settings:", s);
256
+ }
257
+ if ((t = this.tokenManager) != null && t.hasRefreshToken())
247
258
  try {
248
- this.cachedSettings = await this.transport.get("/auth/settings");
249
- } catch (s) {
250
- console.warn("[SaaS Support] Failed to load project settings:", s);
259
+ await this.tokenManager.refreshOnce();
260
+ } catch {
261
+ (e = this.tokenManager) == null || e.clearTokens();
251
262
  }
252
- if ((t = this.tokenManager) != null && t.hasRefreshToken())
253
- try {
254
- await this.performRefresh();
255
- } catch {
256
- (e = this.tokenManager) == null || e.clearTokens();
257
- }
258
- this.loaded = !0;
259
- }
263
+ this.loaded = !0;
260
264
  }
261
265
  // ---------------------------------------------------------------------------
262
266
  // Core auth operations
@@ -457,6 +461,19 @@ class $ {
457
461
  async revokeInvite(t, e) {
458
462
  await this.transport.del(`/auth/orgs/${t}/invites/${e}`, this.authHeaders());
459
463
  }
464
+ async listMyInvites() {
465
+ return this.transport.get("/auth/invites/pending", this.authHeaders());
466
+ }
467
+ async acceptInviteById(t) {
468
+ return this.transport.post(
469
+ `/auth/invites/${t}/accept-by-id`,
470
+ void 0,
471
+ this.authHeaders()
472
+ );
473
+ }
474
+ async declineInvite(t) {
475
+ await this.transport.del(`/auth/invites/${t}/decline`, this.authHeaders());
476
+ }
460
477
  async deleteAccount() {
461
478
  await this.transport.del("/auth/account", this.authHeaders()), this.clearSession();
462
479
  }
@@ -496,7 +513,7 @@ class $ {
496
513
  return t ? { Authorization: `Bearer ${t}` } : {};
497
514
  }
498
515
  }
499
- class R {
516
+ class v {
500
517
  constructor(t) {
501
518
  this.transport = t;
502
519
  }
@@ -540,7 +557,7 @@ class R {
540
557
  return this.transport.post(`/billing/customers/${t}/coupon`, { code: e });
541
558
  }
542
559
  }
543
- class v {
560
+ class R {
544
561
  constructor(t) {
545
562
  this.transport = t;
546
563
  }
@@ -594,12 +611,12 @@ class v {
594
611
  return e.length === 0 ? "" : "?" + e.map(([s, r]) => `${s}=${encodeURIComponent(String(r))}`).join("&");
595
612
  }
596
613
  }
597
- const M = "https://api.saas-support.com/v1";
598
- class H {
614
+ const H = "https://api.saas-support.com/v1";
615
+ class M {
599
616
  constructor(t) {
600
- if (this.tokenManager = null, this.loaded = !1, !t.publishableKey && !t.apiKey)
617
+ if (this.tokenManager = null, this.loaded = !1, this.loadPromise = null, !t.publishableKey && !t.apiKey)
601
618
  throw new Error("SaaSSupport: either publishableKey or apiKey is required");
602
- const e = t.baseUrl ?? M;
619
+ const e = t.baseUrl ?? H;
603
620
  this.emitter = new U();
604
621
  const s = t.publishableKey ? new k(e, { type: "publishableKey", key: t.publishableKey }) : null, r = t.apiKey ? new k(e, { type: "apiKey", key: t.apiKey }) : null;
605
622
  t.publishableKey && (this.tokenManager = new b(t.publishableKey)), this.auth = new $(
@@ -607,7 +624,7 @@ class H {
607
624
  this.tokenManager,
608
625
  this.emitter,
609
626
  e
610
- ), this.billing = new R(r ?? s), this.report = new v(r ?? s), this.tokenManager && (this.tokenManager.setRefreshCallback(() => this.auth.performRefresh()), this.tokenManager.setTokensChangedCallback(() => {
627
+ ), this.billing = new v(r ?? s), this.report = new R(r ?? s), this.tokenManager && (this.tokenManager.setRefreshCallback(() => this.auth.performRefresh()), this.tokenManager.setTokensChangedCallback(() => {
611
628
  this.tokenManager.hasRefreshToken() || this.auth.handleExternalLogout();
612
629
  })), this.tokenManager && s && s.setUnauthorizedHandler(async () => {
613
630
  try {
@@ -618,7 +635,12 @@ class H {
618
635
  });
619
636
  }
620
637
  async load() {
621
- this.loaded || (await this.auth.load(), this.loaded = !0);
638
+ if (!this.loaded)
639
+ return this.loadPromise ? this.loadPromise : (this.loadPromise = this.auth.load().then(() => {
640
+ this.loaded = !0;
641
+ }).finally(() => {
642
+ this.loadPromise = null;
643
+ }), this.loadPromise);
622
644
  }
623
645
  isLoaded() {
624
646
  return this.loaded;
@@ -636,10 +658,10 @@ function E(o) {
636
658
  }
637
659
  export {
638
660
  $ as AuthClient,
639
- R as BillingClient,
640
- v as ReportClient,
661
+ v as BillingClient,
662
+ R as ReportClient,
641
663
  p as SaaSError,
642
- H as SaaSSupport,
664
+ M as SaaSSupport,
643
665
  k as Transport,
644
666
  E as isMfaRequired
645
667
  };