@gajay/axios-refresh-core 1.1.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 ADDED
@@ -0,0 +1,245 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ createAxiosRefresh: () => createAxiosRefresh
24
+ });
25
+ module.exports = __toCommonJS(index_exports);
26
+
27
+ // src/createCircuitBreaker.ts
28
+ var createCircuitBreaker = (config) => {
29
+ if (!config?.enabled) {
30
+ return {
31
+ canExecute: () => true,
32
+ onSuccess: () => {
33
+ },
34
+ onFailure: () => {
35
+ },
36
+ getState: () => "CLOSED"
37
+ };
38
+ }
39
+ let state = "CLOSED";
40
+ let failures = 0;
41
+ let successes = 0;
42
+ let nextTry = 0;
43
+ const failureThreshold = config.failureThreshold ?? 3;
44
+ const successThreshold = config.successThreshold ?? 1;
45
+ const timeoutMs = config.timeoutMs ?? 1e4;
46
+ const now = () => Date.now();
47
+ const canExecute = () => {
48
+ if (state === "OPEN") {
49
+ if (now() >= nextTry) {
50
+ state = "HALF_OPEN";
51
+ return true;
52
+ }
53
+ return false;
54
+ }
55
+ return true;
56
+ };
57
+ const onSuccess = () => {
58
+ if (state === "HALF_OPEN") {
59
+ successes++;
60
+ if (successes >= successThreshold) {
61
+ state = "CLOSED";
62
+ failures = 0;
63
+ successes = 0;
64
+ }
65
+ } else {
66
+ failures = 0;
67
+ }
68
+ };
69
+ const onFailure = () => {
70
+ failures++;
71
+ if (failures >= failureThreshold) {
72
+ state = "OPEN";
73
+ nextTry = now() + timeoutMs;
74
+ }
75
+ };
76
+ return {
77
+ canExecute,
78
+ onSuccess,
79
+ onFailure,
80
+ getState: () => state
81
+ };
82
+ };
83
+
84
+ // src/createCooldownLock.ts
85
+ var createCooldownLock = (config) => {
86
+ if (!config?.enabled) {
87
+ return {
88
+ canProceed: async () => true,
89
+ activate: () => {
90
+ }
91
+ };
92
+ }
93
+ let lockedUntil = 0;
94
+ return {
95
+ canProceed: async () => {
96
+ const now = Date.now();
97
+ if (now >= lockedUntil) return true;
98
+ if (config.strategy === "wait") {
99
+ await new Promise(
100
+ (r) => setTimeout(r, lockedUntil - now)
101
+ );
102
+ return true;
103
+ }
104
+ return false;
105
+ },
106
+ activate: () => {
107
+ lockedUntil = Date.now() + (config.durationMs ?? 3e3);
108
+ }
109
+ };
110
+ };
111
+
112
+ // src/createCrossTabSync.ts
113
+ var createCrossTabSync = () => {
114
+ if (typeof window === "undefined" || typeof globalThis.BroadcastChannel === "undefined") {
115
+ return {
116
+ broadcast: () => {
117
+ },
118
+ listen: () => {
119
+ }
120
+ };
121
+ }
122
+ const channel = new globalThis.BroadcastChannel(
123
+ "axios-refresh"
124
+ );
125
+ return {
126
+ broadcast: (token) => {
127
+ channel.postMessage({ token });
128
+ },
129
+ listen: (cb) => {
130
+ channel.addEventListener("message", (e) => {
131
+ if (e.data?.token) cb(e.data.token);
132
+ });
133
+ }
134
+ };
135
+ };
136
+
137
+ // src/createDevtoolsBridge.ts
138
+ var createDevtoolsBridge = () => {
139
+ if (typeof window === "undefined") return null;
140
+ const w = window;
141
+ if (!w.__AXIOS_REFRESH_DEVTOOLS__) {
142
+ w.__AXIOS_REFRESH_DEVTOOLS__ = {
143
+ events: [],
144
+ emit(e) {
145
+ this.events.push(e);
146
+ }
147
+ };
148
+ }
149
+ return w.__AXIOS_REFRESH_DEVTOOLS__;
150
+ };
151
+
152
+ // src/retryRequest.ts
153
+ var retryRequest = async (fn, retries = 0) => {
154
+ let attempt = 0;
155
+ while (attempt <= retries) {
156
+ try {
157
+ return await fn();
158
+ } catch (e) {
159
+ if (attempt === retries) throw e;
160
+ attempt++;
161
+ }
162
+ }
163
+ };
164
+
165
+ // src/anomaly.ts
166
+ var history = [];
167
+ var detectAnomaly = async (config) => {
168
+ if (!config) return;
169
+ const now = Date.now();
170
+ history.push(now);
171
+ history = history.filter((t) => now - t < 6e4);
172
+ if (history.length > (config.maxPerMinute ?? 5)) {
173
+ if (config.reportToServer && config.reportEndpoint) {
174
+ await fetch(config.reportEndpoint, {
175
+ method: "POST",
176
+ body: JSON.stringify({
177
+ type: "excessive_refresh",
178
+ timestamp: now
179
+ })
180
+ });
181
+ }
182
+ }
183
+ };
184
+
185
+ // src/createAxiosRefresh.ts
186
+ var createAxiosRefresh = (config) => {
187
+ const breaker = createCircuitBreaker(config.circuitBreaker);
188
+ const cooldown = createCooldownLock(config.cooldown);
189
+ const crossTab = createCrossTabSync();
190
+ const devtools = config.devtools?.enabled && createDevtoolsBridge();
191
+ let isRefreshing = false;
192
+ let queue = [];
193
+ const processQueue = (token) => {
194
+ queue.forEach((cb) => cb(token));
195
+ queue = [];
196
+ };
197
+ crossTab.listen((token) => {
198
+ config.setAccessToken?.(token);
199
+ });
200
+ const refresh = async () => {
201
+ if (!breaker.canExecute())
202
+ throw new Error("Circuit OPEN");
203
+ if (!await cooldown.canProceed())
204
+ throw new Error("Cooldown");
205
+ if (isRefreshing)
206
+ return new Promise((res) => queue.push(res));
207
+ isRefreshing = true;
208
+ try {
209
+ const token = await retryRequest(
210
+ () => config.refreshTokenFn(),
211
+ config.retry?.maxRetries ?? 0
212
+ );
213
+ breaker.onSuccess();
214
+ cooldown.activate();
215
+ config.setAccessToken?.(token);
216
+ crossTab.broadcast(token);
217
+ processQueue(token);
218
+ await detectAnomaly(config.anomaly);
219
+ devtools?.emit({ type: "refresh_success" });
220
+ return token;
221
+ } catch (e) {
222
+ breaker.onFailure();
223
+ cooldown.activate();
224
+ config.logout?.();
225
+ throw e;
226
+ } finally {
227
+ isRefreshing = false;
228
+ }
229
+ };
230
+ config.axiosInstance.interceptors.response.use(
231
+ (r) => r,
232
+ async (error) => {
233
+ if (error.response?.status === 401) {
234
+ const token = await refresh();
235
+ error.config.headers.Authorization = token;
236
+ return config.axiosInstance(error.config);
237
+ }
238
+ return Promise.reject(error);
239
+ }
240
+ );
241
+ };
242
+ // Annotate the CommonJS export names for ESM import in node:
243
+ 0 && (module.exports = {
244
+ createAxiosRefresh
245
+ });
@@ -0,0 +1,38 @@
1
+ import { AxiosInstance } from 'axios';
2
+
3
+ interface CircuitBreakerConfig {
4
+ enabled?: boolean;
5
+ failureThreshold?: number;
6
+ successThreshold?: number;
7
+ timeoutMs?: number;
8
+ }
9
+ interface CooldownConfig {
10
+ enabled?: boolean;
11
+ durationMs?: number;
12
+ strategy?: "reject" | "wait";
13
+ }
14
+ interface AnomalyConfig {
15
+ maxPerMinute?: number;
16
+ reportToServer?: boolean;
17
+ reportEndpoint?: string;
18
+ }
19
+ interface CreateAxiosRefreshConfig {
20
+ axiosInstance: AxiosInstance;
21
+ refreshTokenFn: () => Promise<string>;
22
+ setAccessToken?: (token: string) => void;
23
+ logout?: () => void;
24
+ retry?: {
25
+ enabled?: boolean;
26
+ maxRetries?: number;
27
+ };
28
+ circuitBreaker?: CircuitBreakerConfig;
29
+ cooldown?: CooldownConfig;
30
+ anomaly?: AnomalyConfig;
31
+ devtools?: {
32
+ enabled?: boolean;
33
+ };
34
+ }
35
+
36
+ declare const createAxiosRefresh: (config: any) => void;
37
+
38
+ export { type AnomalyConfig, type CircuitBreakerConfig, type CooldownConfig, type CreateAxiosRefreshConfig, createAxiosRefresh };
@@ -0,0 +1,38 @@
1
+ import { AxiosInstance } from 'axios';
2
+
3
+ interface CircuitBreakerConfig {
4
+ enabled?: boolean;
5
+ failureThreshold?: number;
6
+ successThreshold?: number;
7
+ timeoutMs?: number;
8
+ }
9
+ interface CooldownConfig {
10
+ enabled?: boolean;
11
+ durationMs?: number;
12
+ strategy?: "reject" | "wait";
13
+ }
14
+ interface AnomalyConfig {
15
+ maxPerMinute?: number;
16
+ reportToServer?: boolean;
17
+ reportEndpoint?: string;
18
+ }
19
+ interface CreateAxiosRefreshConfig {
20
+ axiosInstance: AxiosInstance;
21
+ refreshTokenFn: () => Promise<string>;
22
+ setAccessToken?: (token: string) => void;
23
+ logout?: () => void;
24
+ retry?: {
25
+ enabled?: boolean;
26
+ maxRetries?: number;
27
+ };
28
+ circuitBreaker?: CircuitBreakerConfig;
29
+ cooldown?: CooldownConfig;
30
+ anomaly?: AnomalyConfig;
31
+ devtools?: {
32
+ enabled?: boolean;
33
+ };
34
+ }
35
+
36
+ declare const createAxiosRefresh: (config: any) => void;
37
+
38
+ export { type AnomalyConfig, type CircuitBreakerConfig, type CooldownConfig, type CreateAxiosRefreshConfig, createAxiosRefresh };
package/dist/index.js ADDED
@@ -0,0 +1,218 @@
1
+ // src/createCircuitBreaker.ts
2
+ var createCircuitBreaker = (config) => {
3
+ if (!config?.enabled) {
4
+ return {
5
+ canExecute: () => true,
6
+ onSuccess: () => {
7
+ },
8
+ onFailure: () => {
9
+ },
10
+ getState: () => "CLOSED"
11
+ };
12
+ }
13
+ let state = "CLOSED";
14
+ let failures = 0;
15
+ let successes = 0;
16
+ let nextTry = 0;
17
+ const failureThreshold = config.failureThreshold ?? 3;
18
+ const successThreshold = config.successThreshold ?? 1;
19
+ const timeoutMs = config.timeoutMs ?? 1e4;
20
+ const now = () => Date.now();
21
+ const canExecute = () => {
22
+ if (state === "OPEN") {
23
+ if (now() >= nextTry) {
24
+ state = "HALF_OPEN";
25
+ return true;
26
+ }
27
+ return false;
28
+ }
29
+ return true;
30
+ };
31
+ const onSuccess = () => {
32
+ if (state === "HALF_OPEN") {
33
+ successes++;
34
+ if (successes >= successThreshold) {
35
+ state = "CLOSED";
36
+ failures = 0;
37
+ successes = 0;
38
+ }
39
+ } else {
40
+ failures = 0;
41
+ }
42
+ };
43
+ const onFailure = () => {
44
+ failures++;
45
+ if (failures >= failureThreshold) {
46
+ state = "OPEN";
47
+ nextTry = now() + timeoutMs;
48
+ }
49
+ };
50
+ return {
51
+ canExecute,
52
+ onSuccess,
53
+ onFailure,
54
+ getState: () => state
55
+ };
56
+ };
57
+
58
+ // src/createCooldownLock.ts
59
+ var createCooldownLock = (config) => {
60
+ if (!config?.enabled) {
61
+ return {
62
+ canProceed: async () => true,
63
+ activate: () => {
64
+ }
65
+ };
66
+ }
67
+ let lockedUntil = 0;
68
+ return {
69
+ canProceed: async () => {
70
+ const now = Date.now();
71
+ if (now >= lockedUntil) return true;
72
+ if (config.strategy === "wait") {
73
+ await new Promise(
74
+ (r) => setTimeout(r, lockedUntil - now)
75
+ );
76
+ return true;
77
+ }
78
+ return false;
79
+ },
80
+ activate: () => {
81
+ lockedUntil = Date.now() + (config.durationMs ?? 3e3);
82
+ }
83
+ };
84
+ };
85
+
86
+ // src/createCrossTabSync.ts
87
+ var createCrossTabSync = () => {
88
+ if (typeof window === "undefined" || typeof globalThis.BroadcastChannel === "undefined") {
89
+ return {
90
+ broadcast: () => {
91
+ },
92
+ listen: () => {
93
+ }
94
+ };
95
+ }
96
+ const channel = new globalThis.BroadcastChannel(
97
+ "axios-refresh"
98
+ );
99
+ return {
100
+ broadcast: (token) => {
101
+ channel.postMessage({ token });
102
+ },
103
+ listen: (cb) => {
104
+ channel.addEventListener("message", (e) => {
105
+ if (e.data?.token) cb(e.data.token);
106
+ });
107
+ }
108
+ };
109
+ };
110
+
111
+ // src/createDevtoolsBridge.ts
112
+ var createDevtoolsBridge = () => {
113
+ if (typeof window === "undefined") return null;
114
+ const w = window;
115
+ if (!w.__AXIOS_REFRESH_DEVTOOLS__) {
116
+ w.__AXIOS_REFRESH_DEVTOOLS__ = {
117
+ events: [],
118
+ emit(e) {
119
+ this.events.push(e);
120
+ }
121
+ };
122
+ }
123
+ return w.__AXIOS_REFRESH_DEVTOOLS__;
124
+ };
125
+
126
+ // src/retryRequest.ts
127
+ var retryRequest = async (fn, retries = 0) => {
128
+ let attempt = 0;
129
+ while (attempt <= retries) {
130
+ try {
131
+ return await fn();
132
+ } catch (e) {
133
+ if (attempt === retries) throw e;
134
+ attempt++;
135
+ }
136
+ }
137
+ };
138
+
139
+ // src/anomaly.ts
140
+ var history = [];
141
+ var detectAnomaly = async (config) => {
142
+ if (!config) return;
143
+ const now = Date.now();
144
+ history.push(now);
145
+ history = history.filter((t) => now - t < 6e4);
146
+ if (history.length > (config.maxPerMinute ?? 5)) {
147
+ if (config.reportToServer && config.reportEndpoint) {
148
+ await fetch(config.reportEndpoint, {
149
+ method: "POST",
150
+ body: JSON.stringify({
151
+ type: "excessive_refresh",
152
+ timestamp: now
153
+ })
154
+ });
155
+ }
156
+ }
157
+ };
158
+
159
+ // src/createAxiosRefresh.ts
160
+ var createAxiosRefresh = (config) => {
161
+ const breaker = createCircuitBreaker(config.circuitBreaker);
162
+ const cooldown = createCooldownLock(config.cooldown);
163
+ const crossTab = createCrossTabSync();
164
+ const devtools = config.devtools?.enabled && createDevtoolsBridge();
165
+ let isRefreshing = false;
166
+ let queue = [];
167
+ const processQueue = (token) => {
168
+ queue.forEach((cb) => cb(token));
169
+ queue = [];
170
+ };
171
+ crossTab.listen((token) => {
172
+ config.setAccessToken?.(token);
173
+ });
174
+ const refresh = async () => {
175
+ if (!breaker.canExecute())
176
+ throw new Error("Circuit OPEN");
177
+ if (!await cooldown.canProceed())
178
+ throw new Error("Cooldown");
179
+ if (isRefreshing)
180
+ return new Promise((res) => queue.push(res));
181
+ isRefreshing = true;
182
+ try {
183
+ const token = await retryRequest(
184
+ () => config.refreshTokenFn(),
185
+ config.retry?.maxRetries ?? 0
186
+ );
187
+ breaker.onSuccess();
188
+ cooldown.activate();
189
+ config.setAccessToken?.(token);
190
+ crossTab.broadcast(token);
191
+ processQueue(token);
192
+ await detectAnomaly(config.anomaly);
193
+ devtools?.emit({ type: "refresh_success" });
194
+ return token;
195
+ } catch (e) {
196
+ breaker.onFailure();
197
+ cooldown.activate();
198
+ config.logout?.();
199
+ throw e;
200
+ } finally {
201
+ isRefreshing = false;
202
+ }
203
+ };
204
+ config.axiosInstance.interceptors.response.use(
205
+ (r) => r,
206
+ async (error) => {
207
+ if (error.response?.status === 401) {
208
+ const token = await refresh();
209
+ error.config.headers.Authorization = token;
210
+ return config.axiosInstance(error.config);
211
+ }
212
+ return Promise.reject(error);
213
+ }
214
+ );
215
+ };
216
+ export {
217
+ createAxiosRefresh
218
+ };
package/package.json ADDED
@@ -0,0 +1,22 @@
1
+ {
2
+ "name": "@gajay/axios-refresh-core",
3
+ "version": "1.1.0",
4
+ "publishConfig": {
5
+ "access": "public"
6
+ },
7
+ "type": "module",
8
+ "main": "dist/index.cjs",
9
+ "module": "dist/index.mjs",
10
+ "types": "dist/index.d.ts",
11
+ "sideEffects": false,
12
+ "files": [
13
+ "dist"
14
+ ],
15
+ "peerDependencies": {
16
+ "axios": "^1"
17
+ },
18
+ "scripts": {
19
+ "build": "tsup",
20
+ "test": "jest"
21
+ }
22
+ }