@gh-platform/auth-sdk 1.0.0 → 1.0.2

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.
@@ -1,3 +1,10 @@
1
+ let _refreshPromise = null;
2
+ function getRefreshPromise() {
3
+ return _refreshPromise;
4
+ }
5
+ function setRefreshPromise(promise) {
6
+ _refreshPromise = promise;
7
+ }
1
8
  class AuthClient {
2
9
  /**
3
10
  * options:
@@ -61,28 +68,39 @@ class AuthClient {
61
68
  return json;
62
69
  }
63
70
  async refresh(refreshToken) {
64
- const res = await fetch(this.refreshUrl, {
65
- method: "POST",
66
- headers: this.headers,
67
- body: JSON.stringify({ refresh_token: refreshToken })
68
- });
69
- if (!res.ok) {
70
- const text = await res.text().catch(() => res.statusText);
71
- throw new Error(`Refresh failed: ${res.status} ${text}`);
71
+ if (getRefreshPromise()) {
72
+ return getRefreshPromise();
72
73
  }
73
- let json;
74
+ const promise = (async () => {
75
+ const res = await fetch(this.refreshUrl, {
76
+ method: "POST",
77
+ headers: this.headers,
78
+ body: JSON.stringify({ refresh_token: refreshToken })
79
+ });
80
+ if (!res.ok) {
81
+ const text = await res.text().catch(() => res.statusText);
82
+ throw new Error(`Refresh failed: ${res.status} ${text}`);
83
+ }
84
+ let json;
85
+ try {
86
+ json = await res.json();
87
+ } catch (e) {
88
+ console.error("❌ JSON parse error:", e);
89
+ throw new Error("Invalid JSON response from server");
90
+ }
91
+ const data = json.data || json;
92
+ if (this.storage) {
93
+ if (data.access_token) this.storage.accessToken = data.access_token;
94
+ if (data.refresh_token) this.storage.refreshToken = data.refresh_token;
95
+ }
96
+ return json;
97
+ })();
98
+ setRefreshPromise(promise);
74
99
  try {
75
- json = await res.json();
76
- } catch (e) {
77
- console.error("❌ JSON parse error:", e);
78
- throw new Error("Invalid JSON response from server");
100
+ return await promise;
101
+ } finally {
102
+ setRefreshPromise(null);
79
103
  }
80
- const data = json.data || json;
81
- if (this.storage) {
82
- if (data.access_token) this.storage.accessToken = data.access_token;
83
- if (data.refresh_token) this.storage.refreshToken = data.refresh_token;
84
- }
85
- return json;
86
104
  }
87
105
  async introspect(token = null) {
88
106
  let finalToken = token;
@@ -1 +1 @@
1
- !function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self).AuthSDK={})}(this,function(e){"use strict";class t{constructor({baseUrl:e,tenant:t=null,loginPath:s=null,refreshPath:r=null,introspectPath:o=null,headers:a={},storage:n=null}){if(!e)throw new Error("baseUrl is required");this.baseUrl=e.replace(/\/$/,"");const h=t?`/api/v1/${t}/auth`:"/api/v1/auth";this.loginUrl=this.baseUrl+(s||`${h}/login`),this.refreshUrl=this.baseUrl+(r||`${h}/refresh`),this.introspectUrl=this.baseUrl+(o||`${h}/me`),this.tenant=t,this.headers={"Content-Type":"application/json",...a},this.storage=n}async login(e,t,s=null,r={}){const o={identifier:e,password:t,...r};s&&(o.totp=s);const a=await fetch(this.loginUrl,{method:"POST",headers:this.headers,body:JSON.stringify(o)});if(!a.ok){const e=await a.text().catch(()=>a.statusText);throw new Error(`Login failed: ${a.status} ${e}`)}let n;try{n=await a.json()}catch(e){throw console.error("❌ JSON parse error:",e),new Error("Invalid JSON response from server")}const h=n.data||n;return this.storage&&(h.access_token&&(this.storage.accessToken=h.access_token),h.refresh_token&&(this.storage.refreshToken=h.refresh_token)),n}async refresh(e){const t=await fetch(this.refreshUrl,{method:"POST",headers:this.headers,body:JSON.stringify({refresh_token:e})});if(!t.ok){const e=await t.text().catch(()=>t.statusText);throw new Error(`Refresh failed: ${t.status} ${e}`)}let s;try{s=await t.json()}catch(e){throw console.error("❌ JSON parse error:",e),new Error("Invalid JSON response from server")}const r=s.data||s;return this.storage&&(r.access_token&&(this.storage.accessToken=r.access_token),r.refresh_token&&(this.storage.refreshToken=r.refresh_token)),s}async introspect(e=null){let t=e;if(this.storage&&!t&&(t=this.storage.accessToken),!t)throw new Error("No access token available for introspection");const s=new Headers(this.headers);s.set("Authorization",`Bearer ${t}`);const r=await fetch(this.introspectUrl,{method:"GET",headers:s});if(!r.ok){const e=await r.text().catch(()=>r.statusText);throw new Error(`Introspect failed: ${r.status} ${e}`)}let o;try{o=await r.json()}catch(e){throw console.error("❌ JSON parse error:",e),new Error("Invalid JSON response from server")}return o}}class s{constructor(e="auth",t=null){this.prefix=e,this.tenant=t}_key(e){return this.tenant?`${this.prefix}:${this.tenant}_${e}`:`${this.prefix}_${e}`}get accessToken(){return localStorage.getItem(this._key("access_token"))}set accessToken(e){null==e?localStorage.removeItem(this._key("access_token")):localStorage.setItem(this._key("access_token"),e)}get refreshToken(){return localStorage.getItem(this._key("refresh_token"))}set refreshToken(e){null==e?localStorage.removeItem(this._key("refresh_token")):localStorage.setItem(this._key("refresh_token"),e)}clear(){localStorage.removeItem(this._key("access_token")),localStorage.removeItem(this._key("refresh_token"))}}class r{constructor(e,t=null){this.client=e,this.storage=t||new s("auth",e.tenant||null)}async fetch(e,t={},s=null){const r=this.storage.accessToken,o=new Headers(t.headers||{});return r&&o.set("Authorization",`Bearer ${r}`),t.method&&"GET"!==t.method&&t.body?await this._xhrRequest(e,t,o,s):await this._fetchWithDownloadProgress(e,t,o,s)}async _fetchWithDownloadProgress(e,t,s,r){let o=await fetch(e,{...t,headers:s});if(401===o.status&&this.storage.refreshToken)try{const r=await this.client.refresh(this.storage.refreshToken);r.access_token&&(this.storage.accessToken=r.access_token),r.refresh_token&&(this.storage.refreshToken=r.refresh_token),s.set("Authorization",`Bearer ${this.storage.accessToken}`),o=await fetch(e,{...t,headers:s})}catch{throw this.storage.clear(),new Error("Unauthorized, please login again")}if(!r||!o.body)return o;const a=o.body.getReader(),n=+o.headers.get("Content-Length")||0;let h=0;const i=[];for(;;){const{done:e,value:t}=await a.read();if(e)break;i.push(t),h+=t.length,n?r(Math.round(h/n*100),h,n):r(null,h,null)}const c=new Blob(i);return new Response(c,o)}_xhrRequest(e,t,s,r){return new Promise((o,a)=>{const n=new XMLHttpRequest;n.open(t.method||"POST",e,!0);for(const[e,t]of s.entries())n.setRequestHeader(e,t);n.upload&&r&&(n.upload.onprogress=e=>{if(e.lengthComputable){const t=Math.round(e.loaded/e.total*100);r(t,e.loaded,e.total)}else r(null,e.loaded,null)}),n.onload=()=>{o(new Response(n.response,{status:n.status}))},n.onerror=()=>a(new Error("Network error")),n.send(t.body)})}}const o={AuthClient:t,AuthFetch:r,TokenStorage:s};e.AuthClient=t,e.AuthFetch=r,e.TokenStorage=s,e.default=o,Object.defineProperties(e,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}})});
1
+ !function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self).AuthSDK={})}(this,function(e){"use strict";let t=null;function r(){return t}function s(e){t=e}class o{constructor({baseUrl:e,tenant:t=null,loginPath:r=null,refreshPath:s=null,introspectPath:o=null,headers:n={},storage:a=null}){if(!e)throw new Error("baseUrl is required");this.baseUrl=e.replace(/\/$/,"");const h=t?`/api/v1/${t}/auth`:"/api/v1/auth";this.loginUrl=this.baseUrl+(r||`${h}/login`),this.refreshUrl=this.baseUrl+(s||`${h}/refresh`),this.introspectUrl=this.baseUrl+(o||`${h}/me`),this.tenant=t,this.headers={"Content-Type":"application/json",...n},this.storage=a}async login(e,t,r=null,s={}){const o={identifier:e,password:t,...s};r&&(o.totp=r);const n=await fetch(this.loginUrl,{method:"POST",headers:this.headers,body:JSON.stringify(o)});if(!n.ok){const e=await n.text().catch(()=>n.statusText);throw new Error(`Login failed: ${n.status} ${e}`)}let a;try{a=await n.json()}catch(e){throw console.error("❌ JSON parse error:",e),new Error("Invalid JSON response from server")}const h=a.data||a;return this.storage&&(h.access_token&&(this.storage.accessToken=h.access_token),h.refresh_token&&(this.storage.refreshToken=h.refresh_token)),a}async refresh(e){if(r())return r();const t=(async()=>{const t=await fetch(this.refreshUrl,{method:"POST",headers:this.headers,body:JSON.stringify({refresh_token:e})});if(!t.ok){const e=await t.text().catch(()=>t.statusText);throw new Error(`Refresh failed: ${t.status} ${e}`)}let r;try{r=await t.json()}catch(e){throw console.error("❌ JSON parse error:",e),new Error("Invalid JSON response from server")}const s=r.data||r;return this.storage&&(s.access_token&&(this.storage.accessToken=s.access_token),s.refresh_token&&(this.storage.refreshToken=s.refresh_token)),r})();s(t);try{return await t}finally{s(null)}}async introspect(e=null){let t=e;if(this.storage&&!t&&(t=this.storage.accessToken),!t)throw new Error("No access token available for introspection");const r=new Headers(this.headers);r.set("Authorization",`Bearer ${t}`);const s=await fetch(this.introspectUrl,{method:"GET",headers:r});if(!s.ok){const e=await s.text().catch(()=>s.statusText);throw new Error(`Introspect failed: ${s.status} ${e}`)}let o;try{o=await s.json()}catch(e){throw console.error("❌ JSON parse error:",e),new Error("Invalid JSON response from server")}return o}}class n{constructor(e="auth",t=null){this.prefix=e,this.tenant=t}_key(e){return this.tenant?`${this.prefix}:${this.tenant}_${e}`:`${this.prefix}_${e}`}get accessToken(){return localStorage.getItem(this._key("access_token"))}set accessToken(e){null==e?localStorage.removeItem(this._key("access_token")):localStorage.setItem(this._key("access_token"),e)}get refreshToken(){return localStorage.getItem(this._key("refresh_token"))}set refreshToken(e){null==e?localStorage.removeItem(this._key("refresh_token")):localStorage.setItem(this._key("refresh_token"),e)}clear(){localStorage.removeItem(this._key("access_token")),localStorage.removeItem(this._key("refresh_token"))}}class a{constructor(e,t=null){this.client=e,this.storage=t||new n("auth",e.tenant||null)}async fetch(e,t={},r=null){const s=this.storage.accessToken,o=new Headers(t.headers||{});return s&&o.set("Authorization",`Bearer ${s}`),t.method&&"GET"!==t.method&&t.body?await this._xhrRequest(e,t,o,r):await this._fetchWithDownloadProgress(e,t,o,r)}async _fetchWithDownloadProgress(e,t,r,s){let o=await fetch(e,{...t,headers:r});if(401===o.status&&this.storage.refreshToken)try{const s=await this.client.refresh(this.storage.refreshToken);s.access_token&&(this.storage.accessToken=s.access_token),s.refresh_token&&(this.storage.refreshToken=s.refresh_token),r.set("Authorization",`Bearer ${this.storage.accessToken}`),o=await fetch(e,{...t,headers:r})}catch{throw this.storage.clear(),new Error("Unauthorized, please login again")}if(!s||!o.body)return o;const n=o.body.getReader(),a=+o.headers.get("Content-Length")||0;let h=0;const i=[];for(;;){const{done:e,value:t}=await n.read();if(e)break;i.push(t),h+=t.length,a?s(Math.round(h/a*100),h,a):s(null,h,null)}const l=new Blob(i);return new Response(l,o)}_xhrRequest(e,t,r,s){return new Promise((o,n)=>{const a=new XMLHttpRequest;a.open(t.method||"POST",e,!0);for(const[e,t]of r.entries())a.setRequestHeader(e,t);a.upload&&s&&(a.upload.onprogress=e=>{if(e.lengthComputable){const t=Math.round(e.loaded/e.total*100);s(t,e.loaded,e.total)}else s(null,e.loaded,null)}),a.onload=()=>{o(new Response(a.response,{status:a.status}))},a.onerror=()=>n(new Error("Network error")),a.send(t.body)})}}const h={AuthClient:o,AuthFetch:a,TokenStorage:n};e.AuthClient=o,e.AuthFetch=a,e.TokenStorage=n,e.default=h,Object.defineProperties(e,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}})});
@@ -1 +1 @@
1
- !function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self).AuthSDK={})}(this,function(e){"use strict";class t{constructor({baseUrl:e,tenant:t=null,loginPath:s=null,refreshPath:r=null,introspectPath:o=null,headers:a={},storage:n=null}){if(!e)throw new Error("baseUrl is required");this.baseUrl=e.replace(/\/$/,"");const h=t?`/api/v1/${t}/auth`:"/api/v1/auth";this.loginUrl=this.baseUrl+(s||`${h}/login`),this.refreshUrl=this.baseUrl+(r||`${h}/refresh`),this.introspectUrl=this.baseUrl+(o||`${h}/me`),this.tenant=t,this.headers={"Content-Type":"application/json",...a},this.storage=n}async login(e,t,s=null,r={}){const o={identifier:e,password:t,...r};s&&(o.totp=s);const a=await fetch(this.loginUrl,{method:"POST",headers:this.headers,body:JSON.stringify(o)});if(!a.ok){const e=await a.text().catch(()=>a.statusText);throw new Error(`Login failed: ${a.status} ${e}`)}let n;try{n=await a.json()}catch(i){throw console.error("❌ JSON parse error:",i),new Error("Invalid JSON response from server")}const h=n.data||n;return this.storage&&(h.access_token&&(this.storage.accessToken=h.access_token),h.refresh_token&&(this.storage.refreshToken=h.refresh_token)),n}async refresh(e){const t=await fetch(this.refreshUrl,{method:"POST",headers:this.headers,body:JSON.stringify({refresh_token:e})});if(!t.ok){const e=await t.text().catch(()=>t.statusText);throw new Error(`Refresh failed: ${t.status} ${e}`)}let s;try{s=await t.json()}catch(o){throw console.error("❌ JSON parse error:",o),new Error("Invalid JSON response from server")}const r=s.data||s;return this.storage&&(r.access_token&&(this.storage.accessToken=r.access_token),r.refresh_token&&(this.storage.refreshToken=r.refresh_token)),s}async introspect(e=null){let t=e;if(this.storage&&!t&&(t=this.storage.accessToken),!t)throw new Error("No access token available for introspection");const s=new Headers(this.headers);s.set("Authorization",`Bearer ${t}`);const r=await fetch(this.introspectUrl,{method:"GET",headers:s});if(!r.ok){const e=await r.text().catch(()=>r.statusText);throw new Error(`Introspect failed: ${r.status} ${e}`)}let o;try{o=await r.json()}catch(a){throw console.error("❌ JSON parse error:",a),new Error("Invalid JSON response from server")}return o}}class s{constructor(e="auth",t=null){this.prefix=e,this.tenant=t}_key(e){return this.tenant?`${this.prefix}:${this.tenant}_${e}`:`${this.prefix}_${e}`}get accessToken(){return localStorage.getItem(this._key("access_token"))}set accessToken(e){null==e?localStorage.removeItem(this._key("access_token")):localStorage.setItem(this._key("access_token"),e)}get refreshToken(){return localStorage.getItem(this._key("refresh_token"))}set refreshToken(e){null==e?localStorage.removeItem(this._key("refresh_token")):localStorage.setItem(this._key("refresh_token"),e)}clear(){localStorage.removeItem(this._key("access_token")),localStorage.removeItem(this._key("refresh_token"))}}class r{constructor(e,t=null){this.client=e,this.storage=t||new s("auth",e.tenant||null)}async fetch(e,t={},s=null){const r=this.storage.accessToken,o=new Headers(t.headers||{});return r&&o.set("Authorization",`Bearer ${r}`),t.method&&"GET"!==t.method&&t.body?await this._xhrRequest(e,t,o,s):await this._fetchWithDownloadProgress(e,t,o,s)}async _fetchWithDownloadProgress(e,t,s,r){let o=await fetch(e,{...t,headers:s});if(401===o.status&&this.storage.refreshToken)try{const r=await this.client.refresh(this.storage.refreshToken);r.access_token&&(this.storage.accessToken=r.access_token),r.refresh_token&&(this.storage.refreshToken=r.refresh_token),s.set("Authorization",`Bearer ${this.storage.accessToken}`),o=await fetch(e,{...t,headers:s})}catch{throw this.storage.clear(),new Error("Unauthorized, please login again")}if(!r||!o.body)return o;const a=o.body.getReader(),n=+o.headers.get("Content-Length")||0;let h=0;const i=[];for(;;){const{done:e,value:t}=await a.read();if(e)break;if(i.push(t),h+=t.length,n){r(Math.round(h/n*100),h,n)}else r(null,h,null)}const c=new Blob(i);return new Response(c,o)}_xhrRequest(e,t,s,r){return new Promise((o,a)=>{const n=new XMLHttpRequest;n.open(t.method||"POST",e,!0);for(const[e,t]of s.entries())n.setRequestHeader(e,t);n.upload&&r&&(n.upload.onprogress=e=>{if(e.lengthComputable){const t=Math.round(e.loaded/e.total*100);r(t,e.loaded,e.total)}else r(null,e.loaded,null)}),n.onload=()=>{o(new Response(n.response,{status:n.status}))},n.onerror=()=>a(new Error("Network error")),n.send(t.body)})}}const o={AuthClient:t,AuthFetch:r,TokenStorage:s};e.AuthClient=t,e.AuthFetch=r,e.TokenStorage=s,e.default=o,Object.defineProperties(e,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}})});
1
+ !function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self).AuthSDK={})}(this,function(e){"use strict";let t=null;function r(){return t}function s(e){t=e}class o{constructor({baseUrl:e,tenant:t=null,loginPath:r=null,refreshPath:s=null,introspectPath:o=null,headers:n={},storage:a=null}){if(!e)throw new Error("baseUrl is required");this.baseUrl=e.replace(/\/$/,"");const h=t?`/api/v1/${t}/auth`:"/api/v1/auth";this.loginUrl=this.baseUrl+(r||`${h}/login`),this.refreshUrl=this.baseUrl+(s||`${h}/refresh`),this.introspectUrl=this.baseUrl+(o||`${h}/me`),this.tenant=t,this.headers={"Content-Type":"application/json",...n},this.storage=a}async login(e,t,r=null,s={}){const o={identifier:e,password:t,...s};r&&(o.totp=r);const n=await fetch(this.loginUrl,{method:"POST",headers:this.headers,body:JSON.stringify(o)});if(!n.ok){const e=await n.text().catch(()=>n.statusText);throw new Error(`Login failed: ${n.status} ${e}`)}let a;try{a=await n.json()}catch(i){throw console.error("❌ JSON parse error:",i),new Error("Invalid JSON response from server")}const h=a.data||a;return this.storage&&(h.access_token&&(this.storage.accessToken=h.access_token),h.refresh_token&&(this.storage.refreshToken=h.refresh_token)),a}async refresh(e){if(r())return r();const t=(async()=>{const t=await fetch(this.refreshUrl,{method:"POST",headers:this.headers,body:JSON.stringify({refresh_token:e})});if(!t.ok){const e=await t.text().catch(()=>t.statusText);throw new Error(`Refresh failed: ${t.status} ${e}`)}let r;try{r=await t.json()}catch(o){throw console.error("❌ JSON parse error:",o),new Error("Invalid JSON response from server")}const s=r.data||r;return this.storage&&(s.access_token&&(this.storage.accessToken=s.access_token),s.refresh_token&&(this.storage.refreshToken=s.refresh_token)),r})();s(t);try{return await t}finally{s(null)}}async introspect(e=null){let t=e;if(this.storage&&!t&&(t=this.storage.accessToken),!t)throw new Error("No access token available for introspection");const r=new Headers(this.headers);r.set("Authorization",`Bearer ${t}`);const s=await fetch(this.introspectUrl,{method:"GET",headers:r});if(!s.ok){const e=await s.text().catch(()=>s.statusText);throw new Error(`Introspect failed: ${s.status} ${e}`)}let o;try{o=await s.json()}catch(n){throw console.error("❌ JSON parse error:",n),new Error("Invalid JSON response from server")}return o}}class n{constructor(e="auth",t=null){this.prefix=e,this.tenant=t}_key(e){return this.tenant?`${this.prefix}:${this.tenant}_${e}`:`${this.prefix}_${e}`}get accessToken(){return localStorage.getItem(this._key("access_token"))}set accessToken(e){null==e?localStorage.removeItem(this._key("access_token")):localStorage.setItem(this._key("access_token"),e)}get refreshToken(){return localStorage.getItem(this._key("refresh_token"))}set refreshToken(e){null==e?localStorage.removeItem(this._key("refresh_token")):localStorage.setItem(this._key("refresh_token"),e)}clear(){localStorage.removeItem(this._key("access_token")),localStorage.removeItem(this._key("refresh_token"))}}class a{constructor(e,t=null){this.client=e,this.storage=t||new n("auth",e.tenant||null)}async fetch(e,t={},r=null){const s=this.storage.accessToken,o=new Headers(t.headers||{});return s&&o.set("Authorization",`Bearer ${s}`),t.method&&"GET"!==t.method&&t.body?await this._xhrRequest(e,t,o,r):await this._fetchWithDownloadProgress(e,t,o,r)}async _fetchWithDownloadProgress(e,t,r,s){let o=await fetch(e,{...t,headers:r});if(401===o.status&&this.storage.refreshToken)try{const s=await this.client.refresh(this.storage.refreshToken);s.access_token&&(this.storage.accessToken=s.access_token),s.refresh_token&&(this.storage.refreshToken=s.refresh_token),r.set("Authorization",`Bearer ${this.storage.accessToken}`),o=await fetch(e,{...t,headers:r})}catch{throw this.storage.clear(),new Error("Unauthorized, please login again")}if(!s||!o.body)return o;const n=o.body.getReader(),a=+o.headers.get("Content-Length")||0;let h=0;const i=[];for(;;){const{done:e,value:t}=await n.read();if(e)break;if(i.push(t),h+=t.length,a){s(Math.round(h/a*100),h,a)}else s(null,h,null)}const l=new Blob(i);return new Response(l,o)}_xhrRequest(e,t,r,s){return new Promise((o,n)=>{const a=new XMLHttpRequest;a.open(t.method||"POST",e,!0);for(const[e,t]of r.entries())a.setRequestHeader(e,t);a.upload&&s&&(a.upload.onprogress=e=>{if(e.lengthComputable){const t=Math.round(e.loaded/e.total*100);s(t,e.loaded,e.total)}else s(null,e.loaded,null)}),a.onload=()=>{o(new Response(a.response,{status:a.status}))},a.onerror=()=>n(new Error("Network error")),a.send(t.body)})}}const h={AuthClient:o,AuthFetch:a,TokenStorage:n};e.AuthClient=o,e.AuthFetch=a,e.TokenStorage=n,e.default=h,Object.defineProperties(e,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}})});
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gh-platform/auth-sdk",
3
- "version": "1.0.0",
3
+ "version": "1.0.2",
4
4
  "description": "VanillaJS Auth SDK for GH Platform",
5
5
  "type": "module",
6
6
  "main": "dist/auth-sdk.umd.js",
package/src/client.js CHANGED
@@ -1,4 +1,6 @@
1
1
  // src/client.js
2
+ import { getRefreshPromise, setRefreshPromise } from "./refresh-lock.js";
3
+
2
4
  export default class AuthClient {
3
5
  /**
4
6
  * options:
@@ -74,33 +76,45 @@ export default class AuthClient {
74
76
  }
75
77
 
76
78
  async refresh(refreshToken) {
77
- const res = await fetch(this.refreshUrl, {
78
- method: "POST",
79
- headers: this.headers,
80
- body: JSON.stringify({ refresh_token: refreshToken }),
81
- });
82
- if (!res.ok) {
83
- const text = await res.text().catch(() => res.statusText);
84
- throw new Error(`Refresh failed: ${res.status} ${text}`);
79
+ if (getRefreshPromise()) {
80
+ return getRefreshPromise();
85
81
  }
86
-
87
- let json; // 👈 MUST DECLARE
82
+ const promise = (async () => {
83
+ const res = await fetch(this.refreshUrl, {
84
+ method: "POST",
85
+ headers: this.headers,
86
+ body: JSON.stringify({ refresh_token: refreshToken }),
87
+ });
88
+ if (!res.ok) {
89
+ const text = await res.text().catch(() => res.statusText);
90
+ throw new Error(`Refresh failed: ${res.status} ${text}`);
91
+ }
92
+
93
+ let json; // 👈 MUST DECLARE
94
+ try {
95
+ json = await res.json();
96
+ } catch (e) {
97
+ console.error("❌ JSON parse error:", e);
98
+ throw new Error("Invalid JSON response from server");
99
+ }
100
+ const data = json.data || json;
101
+ // -----------------------------
102
+ // 🔥 Auto-save refreshed tokens
103
+ // -----------------------------
104
+ if (this.storage) {
105
+ if (data.access_token) this.storage.accessToken = data.access_token;
106
+ if (data.refresh_token) this.storage.refreshToken = data.refresh_token;
107
+ }
108
+
109
+ return json;
110
+ })();
111
+ setRefreshPromise(promise);
88
112
  try {
89
- json = await res.json();
90
- } catch (e) {
91
- console.error("❌ JSON parse error:", e);
92
- throw new Error("Invalid JSON response from server");
93
- }
94
- const data = json.data || json;
95
- // -----------------------------
96
- // 🔥 Auto-save refreshed tokens
97
- // -----------------------------
98
- if (this.storage) {
99
- if (data.access_token) this.storage.accessToken = data.access_token;
100
- if (data.refresh_token) this.storage.refreshToken = data.refresh_token;
113
+ return await promise;
114
+ } finally {
115
+ // 🔓 Luôn reset lock (kể cả khi lỗi)
116
+ setRefreshPromise(null);
101
117
  }
102
-
103
- return json;
104
118
  }
105
119
 
106
120
  async introspect(token = null) {
@@ -0,0 +1,9 @@
1
+ let _refreshPromise = null;
2
+
3
+ export function getRefreshPromise() {
4
+ return _refreshPromise;
5
+ }
6
+
7
+ export function setRefreshPromise(promise) {
8
+ _refreshPromise = promise;
9
+ }