@saas-support/react 0.3.2 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});class f 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 f&&i.isUnauthorized&&this.onUnauthorized&&(r!=null&&r.Authorization)){const h=await this.onUnauthorized();if(h)return this.doRequest(e,t,s,{...r,Authorization:`Bearer ${h}`})}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 doRequest(e,t,s,r){const i={"Content-Type":"application/json",...this.getAuthHeaders(),...r},n=await(await fetch(`${this.baseUrl}${t}`,{method:e,headers:i,body:s?JSON.stringify(s):void 0})).json();if(n.code&&n.code>=400){const g=this.inferDomain(t);throw new f(n.code,n.message||"Request failed",g)}return n.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 ${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 U{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 T=500,w=600,M=5*60*1e3;class b{constructor(e,t,s,r){this.cachedUser=null,this.cachedSettings=null,this.loaded=!1,this.transport=e,this.tokenManager=t,this.emitter=s,this.baseUrl=r}async load(){var e,t;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((e=this.tokenManager)!=null&&e.hasRefreshToken())try{await this.performRefresh()}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-T)/2,h=window.screenY+(window.innerHeight-w)/2,n=window.open(s,"saas-support-oauth",`width=${T},height=${w},left=${i},top=${h},toolbar=no,menubar=no`);return new Promise((g,c)=>{let o=!1;const l=async u=>{var m;if(((m=u.data)==null?void 0:m.type)==="saas-support:oauth-callback"&&!o){if(o=!0,window.removeEventListener("message",l),clearTimeout(k),clearInterval(p),n==null||n.close(),u.data.error){c(new Error(`OAuth error: ${u.data.error}`));return}try{const d=await this.transport.post(`/auth/oauth/${e}/callback`,{code:u.data.code,state:u.data.state||r});this.setSession(d),g(d)}catch(d){c(d)}}};window.addEventListener("message",l);const k=setTimeout(()=>{o||(o=!0,window.removeEventListener("message",l),clearInterval(p),n==null||n.close(),c(new Error("OAuth popup timed out")))},M),p=setInterval(()=>{n!=null&&n.closed&&!o&&(o=!0,clearInterval(p),clearTimeout(k),window.removeEventListener("message",l),c(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}}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 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 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())}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)}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}`}:{}}}class S{constructor(e){this.transport=e}async createCustomer(e){return this.transport.post("/billing/customers",e)}async getCustomer(e){return this.transport.get(`/billing/customers/${e}`)}async updateCustomer(e,t){return this.transport.patch(`/billing/customers/${e}`,t)}async subscribe(e,t){return this.transport.post(`/billing/customers/${e}/subscribe`,{planId:t})}async changePlan(e,t){return this.transport.patch(`/billing/customers/${e}/subscription`,{planId:t})}async cancelSubscription(e){return this.transport.del(`/billing/customers/${e}/subscription`)}async getInvoices(e){return this.transport.get(`/billing/customers/${e}/invoices`)}async ingestUsageEvent(e){return this.transport.post("/billing/events",e)}async getCurrentUsage(e){return this.transport.get(`/billing/customers/${e}/usage`)}async createPortalToken(e,t){return this.transport.post("/billing/portal-tokens",{customerId:e,expiresIn:t})}async applyCoupon(e,t){return this.transport.post(`/billing/customers/${e}/coupon`,{code:t})}}class R{constructor(e){this.transport=e}async executeQuery(e){return this.transport.post("/report/query",e)}async listQueries(e){const t=e?this.toQueryString(e):"";return this.transport.get(`/report/queries${t}`)}async saveQuery(e){return this.transport.post("/report/queries",e)}async updateQuery(e,t){return this.transport.patch(`/report/queries/${e}`,t)}async deleteQuery(e){await this.transport.del(`/report/queries/${e}`)}async listDashboards(e){const t=e?this.toQueryString(e):"";return this.transport.get(`/report/dashboards${t}`)}async createDashboard(e){return this.transport.post("/report/dashboards",e)}async getDashboard(e){return this.transport.get(`/report/dashboards/${e}`)}async updateDashboard(e,t){return this.transport.patch(`/report/dashboards/${e}`,t)}async deleteDashboard(e){await this.transport.del(`/report/dashboards/${e}`)}async createEmbedToken(e){return this.transport.post("/report/embed-tokens",e)}async listEmbedTokens(){return this.transport.get("/report/embed-tokens")}async revokeEmbedToken(e){await this.transport.del(`/report/embed-tokens/${e}`)}toQueryString(e){const t=Object.entries(e).filter(([,s])=>s!=null&&s!=="");return t.length===0?"":"?"+t.map(([s,r])=>`${s}=${encodeURIComponent(String(r))}`).join("&")}}const E="https://api.saas-support.com/v1";class v{constructor(e){if(this.tokenManager=null,this.loaded=!1,!e.publishableKey&&!e.apiKey)throw new Error("SaaSSupport: either publishableKey or apiKey is required");const t=e.baseUrl??E;this.emitter=new U;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 $(e.publishableKey)),this.auth=new b(s??r,this.tokenManager,this.emitter,t),this.billing=new S(r??s),this.report=new R(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(e){return this.emitter.on("error",e)}destroy(){var e;(e=this.tokenManager)==null||e.destroy(),this.emitter.removeAll()}}function C(a){return"mfaRequired"in a&&a.mfaRequired===!0}exports.AuthClient=b;exports.BillingClient=S;exports.ReportClient=R;exports.SaaSError=f;exports.SaaSSupport=v;exports.Transport=y;exports.isMfaRequired=C;
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});class c 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(n){if(n instanceof c&&n.isUnauthorized&&this.onUnauthorized&&(r!=null&&r.Authorization)){const i=await this.onUnauthorized();if(i)return this.doRequest(e,t,s,{...r,Authorization:`Bearer ${i}`})}throw n}}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 c&&r.isUnauthorized&&this.onUnauthorized&&(s!=null&&s.Authorization)){const n=await this.onUnauthorized();if(n)return this.doUploadBinary(e,t,{...s,Authorization:`Bearer ${n}`})}throw r}}async doUploadBinary(e,t,s){const r={"Content-Type":"application/octet-stream",...this.getAuthHeaders(),...s},i=await(await fetch(`${this.baseUrl}${e}`,{method:"POST",headers:r,body:t})).json();if(i.code&&i.code>=400){const a=this.inferDomain(e);throw new c(i.code,i.message||"Upload failed",a)}return i.data}async doRequest(e,t,s,r){const n={"Content-Type":"application/json",...this.getAuthHeaders(),...r},a=await(await fetch(`${this.baseUrl}${t}`,{method:e,headers:n,body:s?JSON.stringify(s):void 0})).json();if(a.code&&a.code>=400){const p=this.inferDomain(t);throw new c(a.code,a.message||"Request failed",p)}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 ${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 R{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 T=500,w=600,M=5*60*1e3;class b{constructor(e,t,s,r){this.cachedUser=null,this.cachedSettings=null,this.loaded=!1,this.transport=e,this.tokenManager=t,this.emitter=s,this.baseUrl=r}async load(){var e,t;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((e=this.tokenManager)!=null&&e.hasRefreshToken())try{await this.performRefresh()}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)}`),n=window.screenX+(window.innerWidth-T)/2,i=window.screenY+(window.innerHeight-w)/2,a=window.open(s,"saas-support-oauth",`width=${T},height=${w},left=${n},top=${i},toolbar=no,menubar=no`);return new Promise((p,l)=>{let h=!1;const d=async u=>{var k;if(((k=u.data)==null?void 0:k.type)==="saas-support:oauth-callback"&&!h){if(h=!0,window.removeEventListener("message",d),clearTimeout(m),clearInterval(f),a==null||a.close(),u.data.error){l(new Error(`OAuth error: ${u.data.error}`));return}try{const g=await this.transport.post(`/auth/oauth/${e}/callback`,{code:u.data.code,state:u.data.state||r});this.setSession(g),p(g)}catch(g){l(g)}}};window.addEventListener("message",d);const m=setTimeout(()=>{h||(h=!0,window.removeEventListener("message",d),clearInterval(f),a==null||a.close(),l(new Error("OAuth popup timed out")))},M),f=setInterval(()=>{a!=null&&a.closed&&!h&&(h=!0,clearInterval(f),clearTimeout(m),window.removeEventListener("message",d),l(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}}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 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())}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)}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}`}:{}}}class S{constructor(e){this.transport=e}async createCustomer(e){return this.transport.post("/billing/customers",e)}async getCustomer(e){return this.transport.get(`/billing/customers/${e}`)}async updateCustomer(e,t){return this.transport.patch(`/billing/customers/${e}`,t)}async subscribe(e,t){return this.transport.post(`/billing/customers/${e}/subscribe`,{planId:t})}async changePlan(e,t){return this.transport.patch(`/billing/customers/${e}/subscription`,{planId:t})}async cancelSubscription(e){return this.transport.del(`/billing/customers/${e}/subscription`)}async getInvoices(e){return this.transport.get(`/billing/customers/${e}/invoices`)}async ingestUsageEvent(e){return this.transport.post("/billing/events",e)}async getCurrentUsage(e){return this.transport.get(`/billing/customers/${e}/usage`)}async createPortalToken(e,t){return this.transport.post("/billing/portal-tokens",{customerId:e,expiresIn:t})}async applyCoupon(e,t){return this.transport.post(`/billing/customers/${e}/coupon`,{code:t})}}class U{constructor(e){this.transport=e}async executeQuery(e){return this.transport.post("/report/query",e)}async listQueries(e){const t=e?this.toQueryString(e):"";return this.transport.get(`/report/queries${t}`)}async saveQuery(e){return this.transport.post("/report/queries",e)}async updateQuery(e,t){return this.transport.patch(`/report/queries/${e}`,t)}async deleteQuery(e){await this.transport.del(`/report/queries/${e}`)}async listDashboards(e){const t=e?this.toQueryString(e):"";return this.transport.get(`/report/dashboards${t}`)}async createDashboard(e){return this.transport.post("/report/dashboards",e)}async getDashboard(e){return this.transport.get(`/report/dashboards/${e}`)}async updateDashboard(e,t){return this.transport.patch(`/report/dashboards/${e}`,t)}async deleteDashboard(e){await this.transport.del(`/report/dashboards/${e}`)}async createEmbedToken(e){return this.transport.post("/report/embed-tokens",e)}async listEmbedTokens(){return this.transport.get("/report/embed-tokens")}async revokeEmbedToken(e){await this.transport.del(`/report/embed-tokens/${e}`)}toQueryString(e){const t=Object.entries(e).filter(([,s])=>s!=null&&s!=="");return t.length===0?"":"?"+t.map(([s,r])=>`${s}=${encodeURIComponent(String(r))}`).join("&")}}const v="https://api.saas-support.com/v1";class E{constructor(e){if(this.tokenManager=null,this.loaded=!1,!e.publishableKey&&!e.apiKey)throw new Error("SaaSSupport: either publishableKey or apiKey is required");const t=e.baseUrl??v;this.emitter=new R;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 $(e.publishableKey)),this.auth=new b(s??r,this.tokenManager,this.emitter,t),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(e){return this.emitter.on("error",e)}destroy(){var e;(e=this.tokenManager)==null||e.destroy(),this.emitter.removeAll()}}function H(o){return"mfaRequired"in o&&o.mfaRequired===!0}exports.AuthClient=b;exports.BillingClient=S;exports.ReportClient=U;exports.SaaSError=c;exports.SaaSSupport=E;exports.Transport=y;exports.isMfaRequired=H;
package/dist/index.d.ts CHANGED
@@ -44,6 +44,12 @@ export declare class AuthClient {
44
44
  avatarUrl?: string;
45
45
  metadata?: Record<string, unknown>;
46
46
  }): Promise<User>;
47
+ uploadAvatar(imageBlob: Blob): Promise<{
48
+ avatarUrl: string;
49
+ small: string;
50
+ medium: string;
51
+ original: string;
52
+ }>;
47
53
  changePassword(currentPassword: string, newPassword: string): Promise<void>;
48
54
  listOrgs(): Promise<Org[]>;
49
55
  createOrg(name: string, slug: string): Promise<Org>;
@@ -490,6 +496,8 @@ export declare class Transport {
490
496
  post<T>(path: string, body?: unknown, headers?: Record<string, string>): Promise<T>;
491
497
  patch<T>(path: string, body?: unknown, headers?: Record<string, string>): Promise<T>;
492
498
  del<T>(path: string, headers?: Record<string, string>): Promise<T>;
499
+ uploadBinary<T>(path: string, data: Blob, headers?: Record<string, string>): Promise<T>;
500
+ private doUploadBinary;
493
501
  private doRequest;
494
502
  private getAuthHeaders;
495
503
  private inferDomain;
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- class k extends Error {
1
+ class g extends Error {
2
2
  constructor(e, t, s = "unknown") {
3
3
  super(t), this.name = "SaaSError", this.code = e, this.domain = s;
4
4
  }
@@ -18,7 +18,7 @@ class k extends Error {
18
18
  return this.code === 429;
19
19
  }
20
20
  }
21
- class m {
21
+ class k {
22
22
  constructor(e, t) {
23
23
  this.onUnauthorized = null, this.baseUrl = e, this.authMode = t;
24
24
  }
@@ -29,16 +29,16 @@ class m {
29
29
  async request(e, t, s, r) {
30
30
  try {
31
31
  return await this.doRequest(e, t, s, r);
32
- } catch (i) {
33
- if (i instanceof k && i.isUnauthorized && this.onUnauthorized && (r != null && r.Authorization)) {
34
- const h = await this.onUnauthorized();
35
- if (h)
32
+ } catch (n) {
33
+ if (n instanceof g && n.isUnauthorized && this.onUnauthorized && (r != null && r.Authorization)) {
34
+ const i = await this.onUnauthorized();
35
+ if (i)
36
36
  return this.doRequest(e, t, s, {
37
37
  ...r,
38
- Authorization: `Bearer ${h}`
38
+ Authorization: `Bearer ${i}`
39
39
  });
40
40
  }
41
- throw i;
41
+ throw n;
42
42
  }
43
43
  }
44
44
  async get(e, t) {
@@ -53,21 +53,52 @@ class m {
53
53
  async del(e, t) {
54
54
  return this.request("DELETE", e, void 0, t);
55
55
  }
56
+ async uploadBinary(e, t, s) {
57
+ try {
58
+ return await this.doUploadBinary(e, t, s);
59
+ } catch (r) {
60
+ if (r instanceof g && r.isUnauthorized && this.onUnauthorized && (s != null && s.Authorization)) {
61
+ const n = await this.onUnauthorized();
62
+ if (n)
63
+ return this.doUploadBinary(e, t, {
64
+ ...s,
65
+ Authorization: `Bearer ${n}`
66
+ });
67
+ }
68
+ throw r;
69
+ }
70
+ }
71
+ async doUploadBinary(e, t, s) {
72
+ const r = {
73
+ "Content-Type": "application/octet-stream",
74
+ ...this.getAuthHeaders(),
75
+ ...s
76
+ }, i = await (await fetch(`${this.baseUrl}${e}`, {
77
+ method: "POST",
78
+ headers: r,
79
+ body: t
80
+ })).json();
81
+ if (i.code && i.code >= 400) {
82
+ const a = this.inferDomain(e);
83
+ throw new g(i.code, i.message || "Upload failed", a);
84
+ }
85
+ return i.data;
86
+ }
56
87
  async doRequest(e, t, s, r) {
57
- const i = {
88
+ const n = {
58
89
  "Content-Type": "application/json",
59
90
  ...this.getAuthHeaders(),
60
91
  ...r
61
- }, n = await (await fetch(`${this.baseUrl}${t}`, {
92
+ }, a = await (await fetch(`${this.baseUrl}${t}`, {
62
93
  method: e,
63
- headers: i,
94
+ headers: n,
64
95
  body: s ? JSON.stringify(s) : void 0
65
96
  })).json();
66
- if (n.code && n.code >= 400) {
67
- const g = this.inferDomain(t);
68
- throw new k(n.code, n.message || "Request failed", g);
97
+ if (a.code && a.code >= 400) {
98
+ const p = this.inferDomain(t);
99
+ throw new g(a.code, a.message || "Request failed", p);
69
100
  }
70
- return n.data;
101
+ return a.data;
71
102
  }
72
103
  getAuthHeaders() {
73
104
  switch (this.authMode.type) {
@@ -202,8 +233,8 @@ class S {
202
233
  this.listeners.clear();
203
234
  }
204
235
  }
205
- const T = 500, w = 600, $ = 5 * 60 * 1e3;
206
- class R {
236
+ const w = 500, T = 600, U = 5 * 60 * 1e3;
237
+ class $ {
207
238
  constructor(e, t, s, r) {
208
239
  this.cachedUser = null, this.cachedSettings = null, this.loaded = !1, this.transport = e, this.tokenManager = t, this.emitter = s, this.baseUrl = r;
209
240
  }
@@ -254,17 +285,17 @@ class R {
254
285
  async signInWithOAuth(e) {
255
286
  const t = `${this.baseUrl}/auth/oauth/${e}/popup-callback`, { authUrl: s, state: r } = await this.transport.get(
256
287
  `/auth/oauth/${e}?redirect_uri=${encodeURIComponent(t)}`
257
- ), i = window.screenX + (window.innerWidth - T) / 2, h = window.screenY + (window.innerHeight - w) / 2, n = window.open(
288
+ ), n = window.screenX + (window.innerWidth - w) / 2, i = window.screenY + (window.innerHeight - T) / 2, a = window.open(
258
289
  s,
259
290
  "saas-support-oauth",
260
- `width=${T},height=${w},left=${i},top=${h},toolbar=no,menubar=no`
291
+ `width=${w},height=${T},left=${n},top=${i},toolbar=no,menubar=no`
261
292
  );
262
- return new Promise((g, c) => {
263
- let o = !1;
293
+ return new Promise((p, c) => {
294
+ let h = !1;
264
295
  const l = async (u) => {
265
- var y;
266
- if (((y = u.data) == null ? void 0 : y.type) === "saas-support:oauth-callback" && !o) {
267
- if (o = !0, window.removeEventListener("message", l), clearTimeout(f), clearInterval(p), n == null || n.close(), u.data.error) {
296
+ var m;
297
+ if (((m = u.data) == null ? void 0 : m.type) === "saas-support:oauth-callback" && !h) {
298
+ if (h = !0, window.removeEventListener("message", l), clearTimeout(y), clearInterval(f), a == null || a.close(), u.data.error) {
268
299
  c(new Error(`OAuth error: ${u.data.error}`));
269
300
  return;
270
301
  }
@@ -273,17 +304,17 @@ class R {
273
304
  `/auth/oauth/${e}/callback`,
274
305
  { code: u.data.code, state: u.data.state || r }
275
306
  );
276
- this.setSession(d), g(d);
307
+ this.setSession(d), p(d);
277
308
  } catch (d) {
278
309
  c(d);
279
310
  }
280
311
  }
281
312
  };
282
313
  window.addEventListener("message", l);
283
- const f = setTimeout(() => {
284
- o || (o = !0, window.removeEventListener("message", l), clearInterval(p), n == null || n.close(), c(new Error("OAuth popup timed out")));
285
- }, $), p = setInterval(() => {
286
- n != null && n.closed && !o && (o = !0, clearInterval(p), clearTimeout(f), window.removeEventListener("message", l), c(new Error("OAuth popup was closed")));
314
+ const y = setTimeout(() => {
315
+ h || (h = !0, window.removeEventListener("message", l), clearInterval(f), a == null || a.close(), c(new Error("OAuth popup timed out")));
316
+ }, U), f = setInterval(() => {
317
+ a != null && a.closed && !h && (h = !0, clearInterval(f), clearTimeout(y), window.removeEventListener("message", l), c(new Error("OAuth popup was closed")));
287
318
  }, 500);
288
319
  });
289
320
  }
@@ -368,6 +399,10 @@ class R {
368
399
  const t = await this.transport.patch("/auth/me", e, this.authHeaders());
369
400
  return this.cachedUser = t, this.emitter.emit("authStateChange", t), t;
370
401
  }
402
+ async uploadAvatar(e) {
403
+ const t = await this.transport.uploadBinary("/auth/avatar", e, this.authHeaders());
404
+ return this.cachedUser && (this.cachedUser = { ...this.cachedUser, avatarUrl: t.avatarUrl }, this.emitter.emit("authStateChange", this.cachedUser)), t;
405
+ }
371
406
  async changePassword(e, t) {
372
407
  await this.transport.post("/auth/change-password", { currentPassword: e, newPassword: t }, this.authHeaders());
373
408
  }
@@ -443,7 +478,7 @@ class R {
443
478
  return e ? { Authorization: `Bearer ${e}` } : {};
444
479
  }
445
480
  }
446
- class U {
481
+ class R {
447
482
  constructor(e) {
448
483
  this.transport = e;
449
484
  }
@@ -541,20 +576,20 @@ class M {
541
576
  return t.length === 0 ? "" : "?" + t.map(([s, r]) => `${s}=${encodeURIComponent(String(r))}`).join("&");
542
577
  }
543
578
  }
544
- const E = "https://api.saas-support.com/v1";
545
- class v {
579
+ const v = "https://api.saas-support.com/v1";
580
+ class H {
546
581
  constructor(e) {
547
582
  if (this.tokenManager = null, this.loaded = !1, !e.publishableKey && !e.apiKey)
548
583
  throw new Error("SaaSSupport: either publishableKey or apiKey is required");
549
- const t = e.baseUrl ?? E;
584
+ const t = e.baseUrl ?? v;
550
585
  this.emitter = new S();
551
- const s = e.publishableKey ? new m(t, { type: "publishableKey", key: e.publishableKey }) : null, r = e.apiKey ? new m(t, { type: "apiKey", key: e.apiKey }) : null;
552
- e.publishableKey && (this.tokenManager = new b(e.publishableKey)), this.auth = new R(
586
+ const s = e.publishableKey ? new k(t, { type: "publishableKey", key: e.publishableKey }) : null, r = e.apiKey ? new k(t, { type: "apiKey", key: e.apiKey }) : null;
587
+ e.publishableKey && (this.tokenManager = new b(e.publishableKey)), this.auth = new $(
553
588
  s ?? r,
554
589
  this.tokenManager,
555
590
  this.emitter,
556
591
  t
557
- ), this.billing = new U(r ?? s), this.report = new M(r ?? s), this.tokenManager && (this.tokenManager.setRefreshCallback(() => this.auth.performRefresh()), this.tokenManager.setTokensChangedCallback(() => {
592
+ ), this.billing = new R(r ?? s), this.report = new M(r ?? s), this.tokenManager && (this.tokenManager.setRefreshCallback(() => this.auth.performRefresh()), this.tokenManager.setTokensChangedCallback(() => {
558
593
  this.tokenManager.hasRefreshToken() || this.auth.handleExternalLogout();
559
594
  })), this.tokenManager && s && s.setUnauthorizedHandler(async () => {
560
595
  try {
@@ -578,15 +613,15 @@ class v {
578
613
  (e = this.tokenManager) == null || e.destroy(), this.emitter.removeAll();
579
614
  }
580
615
  }
581
- function H(a) {
582
- return "mfaRequired" in a && a.mfaRequired === !0;
616
+ function E(o) {
617
+ return "mfaRequired" in o && o.mfaRequired === !0;
583
618
  }
584
619
  export {
585
- R as AuthClient,
586
- U as BillingClient,
620
+ $ as AuthClient,
621
+ R as BillingClient,
587
622
  M as ReportClient,
588
- k as SaaSError,
589
- v as SaaSSupport,
590
- m as Transport,
591
- H as isMfaRequired
623
+ g as SaaSError,
624
+ H as SaaSSupport,
625
+ k as Transport,
626
+ E as isMfaRequired
592
627
  };