@scalemule/nextjs 0.1.16 → 0.1.18
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/client.d.mts +13 -1
- package/dist/client.d.ts +13 -1
- package/dist/client.js +32 -0
- package/dist/client.mjs +32 -0
- package/dist/index.js +32 -0
- package/dist/index.mjs +32 -0
- package/dist/server/auth.js +130 -21
- package/dist/server/auth.mjs +130 -21
- package/dist/server/index.d.mts +2 -2
- package/dist/server/index.d.ts +2 -2
- package/dist/server/index.js +140 -26
- package/dist/server/index.mjs +140 -26
- package/dist/server/ledvery.js +10 -5
- package/dist/server/ledvery.mjs +10 -5
- package/dist/server/webhook-handler.d.mts +1 -1
- package/dist/server/webhook-handler.d.ts +1 -1
- package/dist/{webhook-handler-C07AK4QB.d.ts → webhook-handler-CTx4Uh8u.d.ts} +33 -7
- package/dist/{webhook-handler-CUp_A0_p.d.mts → webhook-handler-DsxJS_He.d.mts} +33 -7
- package/package.json +2 -2
package/dist/client.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a8 as StorageAdapter } from './index-DTWyUcyd.mjs';
|
|
1
|
+
import { a8 as StorageAdapter, A as ApiError } from './index-DTWyUcyd.mjs';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* ScaleMule API Client
|
|
@@ -38,6 +38,12 @@ interface ClientConfig {
|
|
|
38
38
|
enableAccountSwitcher?: boolean;
|
|
39
39
|
/** Privacy level for account switcher */
|
|
40
40
|
accountSwitcherPrivacy?: 'full' | 'masked' | 'minimal';
|
|
41
|
+
/** Optional: Callback when a 401 auto-refresh starts. */
|
|
42
|
+
onRefreshStart?: () => void;
|
|
43
|
+
/** Optional: Callback when a 401 auto-refresh finishes. */
|
|
44
|
+
onRefreshEnd?: () => void;
|
|
45
|
+
/** Optional: Callback when an automatic 401 refresh fails. */
|
|
46
|
+
onAutoRefreshFailed?: (error: ApiError) => void;
|
|
41
47
|
}
|
|
42
48
|
interface RequestOptions extends RequestInit {
|
|
43
49
|
/** Skip adding auth headers (for public endpoints) */
|
|
@@ -48,6 +54,8 @@ interface RequestOptions extends RequestInit {
|
|
|
48
54
|
retries?: number;
|
|
49
55
|
/** Skip retries */
|
|
50
56
|
skipRetry?: boolean;
|
|
57
|
+
/** Internal flag to prevent infinite refresh loops */
|
|
58
|
+
isAutoRefresh?: boolean;
|
|
51
59
|
}
|
|
52
60
|
/**
|
|
53
61
|
* ScaleMule API Client
|
|
@@ -69,6 +77,10 @@ declare class ScaleMuleClient {
|
|
|
69
77
|
private sessionGate;
|
|
70
78
|
private resolveSessionGate;
|
|
71
79
|
private workspaceId;
|
|
80
|
+
private refreshPromise;
|
|
81
|
+
private onRefreshStart?;
|
|
82
|
+
private onRefreshEnd?;
|
|
83
|
+
private onAutoRefreshFailed?;
|
|
72
84
|
constructor(config: ClientConfig);
|
|
73
85
|
/**
|
|
74
86
|
* Sync offline queue when coming back online
|
package/dist/client.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a8 as StorageAdapter } from './index-DTWyUcyd.js';
|
|
1
|
+
import { a8 as StorageAdapter, A as ApiError } from './index-DTWyUcyd.js';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* ScaleMule API Client
|
|
@@ -38,6 +38,12 @@ interface ClientConfig {
|
|
|
38
38
|
enableAccountSwitcher?: boolean;
|
|
39
39
|
/** Privacy level for account switcher */
|
|
40
40
|
accountSwitcherPrivacy?: 'full' | 'masked' | 'minimal';
|
|
41
|
+
/** Optional: Callback when a 401 auto-refresh starts. */
|
|
42
|
+
onRefreshStart?: () => void;
|
|
43
|
+
/** Optional: Callback when a 401 auto-refresh finishes. */
|
|
44
|
+
onRefreshEnd?: () => void;
|
|
45
|
+
/** Optional: Callback when an automatic 401 refresh fails. */
|
|
46
|
+
onAutoRefreshFailed?: (error: ApiError) => void;
|
|
41
47
|
}
|
|
42
48
|
interface RequestOptions extends RequestInit {
|
|
43
49
|
/** Skip adding auth headers (for public endpoints) */
|
|
@@ -48,6 +54,8 @@ interface RequestOptions extends RequestInit {
|
|
|
48
54
|
retries?: number;
|
|
49
55
|
/** Skip retries */
|
|
50
56
|
skipRetry?: boolean;
|
|
57
|
+
/** Internal flag to prevent infinite refresh loops */
|
|
58
|
+
isAutoRefresh?: boolean;
|
|
51
59
|
}
|
|
52
60
|
/**
|
|
53
61
|
* ScaleMule API Client
|
|
@@ -69,6 +77,10 @@ declare class ScaleMuleClient {
|
|
|
69
77
|
private sessionGate;
|
|
70
78
|
private resolveSessionGate;
|
|
71
79
|
private workspaceId;
|
|
80
|
+
private refreshPromise;
|
|
81
|
+
private onRefreshStart?;
|
|
82
|
+
private onRefreshEnd?;
|
|
83
|
+
private onAutoRefreshFailed?;
|
|
72
84
|
constructor(config: ClientConfig);
|
|
73
85
|
/**
|
|
74
86
|
* Sync offline queue when coming back online
|
package/dist/client.js
CHANGED
|
@@ -278,6 +278,7 @@ var ScaleMuleClient = class {
|
|
|
278
278
|
this.sessionGate = null;
|
|
279
279
|
this.resolveSessionGate = null;
|
|
280
280
|
this.workspaceId = null;
|
|
281
|
+
this.refreshPromise = null;
|
|
281
282
|
this.apiKey = config.apiKey;
|
|
282
283
|
this.applicationId = config.applicationId || null;
|
|
283
284
|
this.gatewayUrl = resolveGatewayUrl(config);
|
|
@@ -285,6 +286,9 @@ var ScaleMuleClient = class {
|
|
|
285
286
|
this.storage = config.storage || createDefaultStorage();
|
|
286
287
|
this.enableRateLimitQueue = config.enableRateLimitQueue || false;
|
|
287
288
|
this.enableOfflineQueue = config.enableOfflineQueue || false;
|
|
289
|
+
this.onRefreshStart = config.onRefreshStart;
|
|
290
|
+
this.onRefreshEnd = config.onRefreshEnd;
|
|
291
|
+
this.onAutoRefreshFailed = config.onAutoRefreshFailed;
|
|
288
292
|
if (this.enableRateLimitQueue) {
|
|
289
293
|
this.rateLimitQueue = new RateLimitQueue();
|
|
290
294
|
}
|
|
@@ -503,6 +507,10 @@ var ScaleMuleClient = class {
|
|
|
503
507
|
signal: controller.signal
|
|
504
508
|
});
|
|
505
509
|
clearTimeout(timeoutId);
|
|
510
|
+
const rotatedToken = response.headers.get("x-rotated-session-token");
|
|
511
|
+
if (rotatedToken && this.sessionToken && this.userId) {
|
|
512
|
+
this.setSession(rotatedToken, this.userId);
|
|
513
|
+
}
|
|
506
514
|
const text = await response.text();
|
|
507
515
|
let responseData = null;
|
|
508
516
|
try {
|
|
@@ -512,6 +520,30 @@ var ScaleMuleClient = class {
|
|
|
512
520
|
if (!response.ok) {
|
|
513
521
|
const rawError = responseData?.error;
|
|
514
522
|
const error = rawError && typeof rawError === "object" ? rawError : { code: `HTTP_${response.status}`, message: typeof rawError === "string" ? rawError : responseData?.message || text || response.statusText };
|
|
523
|
+
if (response.status === 401 && this.sessionToken && !options.isAutoRefresh) {
|
|
524
|
+
if (this.debug) console.log("[ScaleMule] 401 received, attempting auto-refresh...");
|
|
525
|
+
if (!this.refreshPromise) {
|
|
526
|
+
this.onRefreshStart?.();
|
|
527
|
+
this.refreshPromise = this.post("/api/auth/refresh", {}, { isAutoRefresh: true });
|
|
528
|
+
}
|
|
529
|
+
try {
|
|
530
|
+
await this.refreshPromise;
|
|
531
|
+
if (this.debug) console.log("[ScaleMule] Auto-refresh succeeded, retrying original request...");
|
|
532
|
+
attempt--;
|
|
533
|
+
if (this.sessionToken) {
|
|
534
|
+
headers.set("Authorization", `Bearer ${this.sessionToken}`);
|
|
535
|
+
}
|
|
536
|
+
continue;
|
|
537
|
+
} catch (refreshErr) {
|
|
538
|
+
if (this.debug) console.error("[ScaleMule] Auto-refresh failed:", refreshErr);
|
|
539
|
+
const refreshApiError = refreshErr instanceof ScaleMuleApiError ? { code: refreshErr.code, message: refreshErr.message, field: refreshErr.field } : { code: "REFRESH_FAILED", message: "Auto-refresh failed" };
|
|
540
|
+
this.onAutoRefreshFailed?.(refreshApiError);
|
|
541
|
+
throw new ScaleMuleApiError(error);
|
|
542
|
+
} finally {
|
|
543
|
+
this.refreshPromise = null;
|
|
544
|
+
this.onRefreshEnd?.();
|
|
545
|
+
}
|
|
546
|
+
}
|
|
515
547
|
if (attempt < maxRetries && RETRYABLE_STATUS_CODES.has(response.status)) {
|
|
516
548
|
lastError = error;
|
|
517
549
|
const delay = getBackoffDelay(attempt);
|
package/dist/client.mjs
CHANGED
|
@@ -276,6 +276,7 @@ var ScaleMuleClient = class {
|
|
|
276
276
|
this.sessionGate = null;
|
|
277
277
|
this.resolveSessionGate = null;
|
|
278
278
|
this.workspaceId = null;
|
|
279
|
+
this.refreshPromise = null;
|
|
279
280
|
this.apiKey = config.apiKey;
|
|
280
281
|
this.applicationId = config.applicationId || null;
|
|
281
282
|
this.gatewayUrl = resolveGatewayUrl(config);
|
|
@@ -283,6 +284,9 @@ var ScaleMuleClient = class {
|
|
|
283
284
|
this.storage = config.storage || createDefaultStorage();
|
|
284
285
|
this.enableRateLimitQueue = config.enableRateLimitQueue || false;
|
|
285
286
|
this.enableOfflineQueue = config.enableOfflineQueue || false;
|
|
287
|
+
this.onRefreshStart = config.onRefreshStart;
|
|
288
|
+
this.onRefreshEnd = config.onRefreshEnd;
|
|
289
|
+
this.onAutoRefreshFailed = config.onAutoRefreshFailed;
|
|
286
290
|
if (this.enableRateLimitQueue) {
|
|
287
291
|
this.rateLimitQueue = new RateLimitQueue();
|
|
288
292
|
}
|
|
@@ -501,6 +505,10 @@ var ScaleMuleClient = class {
|
|
|
501
505
|
signal: controller.signal
|
|
502
506
|
});
|
|
503
507
|
clearTimeout(timeoutId);
|
|
508
|
+
const rotatedToken = response.headers.get("x-rotated-session-token");
|
|
509
|
+
if (rotatedToken && this.sessionToken && this.userId) {
|
|
510
|
+
this.setSession(rotatedToken, this.userId);
|
|
511
|
+
}
|
|
504
512
|
const text = await response.text();
|
|
505
513
|
let responseData = null;
|
|
506
514
|
try {
|
|
@@ -510,6 +518,30 @@ var ScaleMuleClient = class {
|
|
|
510
518
|
if (!response.ok) {
|
|
511
519
|
const rawError = responseData?.error;
|
|
512
520
|
const error = rawError && typeof rawError === "object" ? rawError : { code: `HTTP_${response.status}`, message: typeof rawError === "string" ? rawError : responseData?.message || text || response.statusText };
|
|
521
|
+
if (response.status === 401 && this.sessionToken && !options.isAutoRefresh) {
|
|
522
|
+
if (this.debug) console.log("[ScaleMule] 401 received, attempting auto-refresh...");
|
|
523
|
+
if (!this.refreshPromise) {
|
|
524
|
+
this.onRefreshStart?.();
|
|
525
|
+
this.refreshPromise = this.post("/api/auth/refresh", {}, { isAutoRefresh: true });
|
|
526
|
+
}
|
|
527
|
+
try {
|
|
528
|
+
await this.refreshPromise;
|
|
529
|
+
if (this.debug) console.log("[ScaleMule] Auto-refresh succeeded, retrying original request...");
|
|
530
|
+
attempt--;
|
|
531
|
+
if (this.sessionToken) {
|
|
532
|
+
headers.set("Authorization", `Bearer ${this.sessionToken}`);
|
|
533
|
+
}
|
|
534
|
+
continue;
|
|
535
|
+
} catch (refreshErr) {
|
|
536
|
+
if (this.debug) console.error("[ScaleMule] Auto-refresh failed:", refreshErr);
|
|
537
|
+
const refreshApiError = refreshErr instanceof ScaleMuleApiError ? { code: refreshErr.code, message: refreshErr.message, field: refreshErr.field } : { code: "REFRESH_FAILED", message: "Auto-refresh failed" };
|
|
538
|
+
this.onAutoRefreshFailed?.(refreshApiError);
|
|
539
|
+
throw new ScaleMuleApiError(error);
|
|
540
|
+
} finally {
|
|
541
|
+
this.refreshPromise = null;
|
|
542
|
+
this.onRefreshEnd?.();
|
|
543
|
+
}
|
|
544
|
+
}
|
|
513
545
|
if (attempt < maxRetries && RETRYABLE_STATUS_CODES.has(response.status)) {
|
|
514
546
|
lastError = error;
|
|
515
547
|
const delay = getBackoffDelay(attempt);
|
package/dist/index.js
CHANGED
|
@@ -7023,6 +7023,7 @@ var ScaleMuleClient2 = class {
|
|
|
7023
7023
|
this.sessionGate = null;
|
|
7024
7024
|
this.resolveSessionGate = null;
|
|
7025
7025
|
this.workspaceId = null;
|
|
7026
|
+
this.refreshPromise = null;
|
|
7026
7027
|
this.apiKey = config.apiKey;
|
|
7027
7028
|
this.applicationId = config.applicationId || null;
|
|
7028
7029
|
this.gatewayUrl = resolveGatewayUrl(config);
|
|
@@ -7030,6 +7031,9 @@ var ScaleMuleClient2 = class {
|
|
|
7030
7031
|
this.storage = config.storage || createDefaultStorage2();
|
|
7031
7032
|
this.enableRateLimitQueue = config.enableRateLimitQueue || false;
|
|
7032
7033
|
this.enableOfflineQueue = config.enableOfflineQueue || false;
|
|
7034
|
+
this.onRefreshStart = config.onRefreshStart;
|
|
7035
|
+
this.onRefreshEnd = config.onRefreshEnd;
|
|
7036
|
+
this.onAutoRefreshFailed = config.onAutoRefreshFailed;
|
|
7033
7037
|
if (this.enableRateLimitQueue) {
|
|
7034
7038
|
this.rateLimitQueue = new RateLimitQueue2();
|
|
7035
7039
|
}
|
|
@@ -7248,6 +7252,10 @@ var ScaleMuleClient2 = class {
|
|
|
7248
7252
|
signal: controller.signal
|
|
7249
7253
|
});
|
|
7250
7254
|
clearTimeout(timeoutId);
|
|
7255
|
+
const rotatedToken = response.headers.get("x-rotated-session-token");
|
|
7256
|
+
if (rotatedToken && this.sessionToken && this.userId) {
|
|
7257
|
+
this.setSession(rotatedToken, this.userId);
|
|
7258
|
+
}
|
|
7251
7259
|
const text = await response.text();
|
|
7252
7260
|
let responseData = null;
|
|
7253
7261
|
try {
|
|
@@ -7257,6 +7265,30 @@ var ScaleMuleClient2 = class {
|
|
|
7257
7265
|
if (!response.ok) {
|
|
7258
7266
|
const rawError = responseData?.error;
|
|
7259
7267
|
const error = rawError && typeof rawError === "object" ? rawError : { code: `HTTP_${response.status}`, message: typeof rawError === "string" ? rawError : responseData?.message || text || response.statusText };
|
|
7268
|
+
if (response.status === 401 && this.sessionToken && !options.isAutoRefresh) {
|
|
7269
|
+
if (this.debug) console.log("[ScaleMule] 401 received, attempting auto-refresh...");
|
|
7270
|
+
if (!this.refreshPromise) {
|
|
7271
|
+
this.onRefreshStart?.();
|
|
7272
|
+
this.refreshPromise = this.post("/api/auth/refresh", {}, { isAutoRefresh: true });
|
|
7273
|
+
}
|
|
7274
|
+
try {
|
|
7275
|
+
await this.refreshPromise;
|
|
7276
|
+
if (this.debug) console.log("[ScaleMule] Auto-refresh succeeded, retrying original request...");
|
|
7277
|
+
attempt--;
|
|
7278
|
+
if (this.sessionToken) {
|
|
7279
|
+
headers.set("Authorization", `Bearer ${this.sessionToken}`);
|
|
7280
|
+
}
|
|
7281
|
+
continue;
|
|
7282
|
+
} catch (refreshErr) {
|
|
7283
|
+
if (this.debug) console.error("[ScaleMule] Auto-refresh failed:", refreshErr);
|
|
7284
|
+
const refreshApiError = refreshErr instanceof ScaleMuleApiError ? { code: refreshErr.code, message: refreshErr.message, field: refreshErr.field } : { code: "REFRESH_FAILED", message: "Auto-refresh failed" };
|
|
7285
|
+
this.onAutoRefreshFailed?.(refreshApiError);
|
|
7286
|
+
throw new ScaleMuleApiError(error);
|
|
7287
|
+
} finally {
|
|
7288
|
+
this.refreshPromise = null;
|
|
7289
|
+
this.onRefreshEnd?.();
|
|
7290
|
+
}
|
|
7291
|
+
}
|
|
7260
7292
|
if (attempt < maxRetries && RETRYABLE_STATUS_CODES3.has(response.status)) {
|
|
7261
7293
|
lastError = error;
|
|
7262
7294
|
const delay = getBackoffDelay2(attempt);
|
package/dist/index.mjs
CHANGED
|
@@ -7003,6 +7003,7 @@ var ScaleMuleClient2 = class {
|
|
|
7003
7003
|
this.sessionGate = null;
|
|
7004
7004
|
this.resolveSessionGate = null;
|
|
7005
7005
|
this.workspaceId = null;
|
|
7006
|
+
this.refreshPromise = null;
|
|
7006
7007
|
this.apiKey = config.apiKey;
|
|
7007
7008
|
this.applicationId = config.applicationId || null;
|
|
7008
7009
|
this.gatewayUrl = resolveGatewayUrl(config);
|
|
@@ -7010,6 +7011,9 @@ var ScaleMuleClient2 = class {
|
|
|
7010
7011
|
this.storage = config.storage || createDefaultStorage2();
|
|
7011
7012
|
this.enableRateLimitQueue = config.enableRateLimitQueue || false;
|
|
7012
7013
|
this.enableOfflineQueue = config.enableOfflineQueue || false;
|
|
7014
|
+
this.onRefreshStart = config.onRefreshStart;
|
|
7015
|
+
this.onRefreshEnd = config.onRefreshEnd;
|
|
7016
|
+
this.onAutoRefreshFailed = config.onAutoRefreshFailed;
|
|
7013
7017
|
if (this.enableRateLimitQueue) {
|
|
7014
7018
|
this.rateLimitQueue = new RateLimitQueue2();
|
|
7015
7019
|
}
|
|
@@ -7228,6 +7232,10 @@ var ScaleMuleClient2 = class {
|
|
|
7228
7232
|
signal: controller.signal
|
|
7229
7233
|
});
|
|
7230
7234
|
clearTimeout(timeoutId);
|
|
7235
|
+
const rotatedToken = response.headers.get("x-rotated-session-token");
|
|
7236
|
+
if (rotatedToken && this.sessionToken && this.userId) {
|
|
7237
|
+
this.setSession(rotatedToken, this.userId);
|
|
7238
|
+
}
|
|
7231
7239
|
const text = await response.text();
|
|
7232
7240
|
let responseData = null;
|
|
7233
7241
|
try {
|
|
@@ -7237,6 +7245,30 @@ var ScaleMuleClient2 = class {
|
|
|
7237
7245
|
if (!response.ok) {
|
|
7238
7246
|
const rawError = responseData?.error;
|
|
7239
7247
|
const error = rawError && typeof rawError === "object" ? rawError : { code: `HTTP_${response.status}`, message: typeof rawError === "string" ? rawError : responseData?.message || text || response.statusText };
|
|
7248
|
+
if (response.status === 401 && this.sessionToken && !options.isAutoRefresh) {
|
|
7249
|
+
if (this.debug) console.log("[ScaleMule] 401 received, attempting auto-refresh...");
|
|
7250
|
+
if (!this.refreshPromise) {
|
|
7251
|
+
this.onRefreshStart?.();
|
|
7252
|
+
this.refreshPromise = this.post("/api/auth/refresh", {}, { isAutoRefresh: true });
|
|
7253
|
+
}
|
|
7254
|
+
try {
|
|
7255
|
+
await this.refreshPromise;
|
|
7256
|
+
if (this.debug) console.log("[ScaleMule] Auto-refresh succeeded, retrying original request...");
|
|
7257
|
+
attempt--;
|
|
7258
|
+
if (this.sessionToken) {
|
|
7259
|
+
headers.set("Authorization", `Bearer ${this.sessionToken}`);
|
|
7260
|
+
}
|
|
7261
|
+
continue;
|
|
7262
|
+
} catch (refreshErr) {
|
|
7263
|
+
if (this.debug) console.error("[ScaleMule] Auto-refresh failed:", refreshErr);
|
|
7264
|
+
const refreshApiError = refreshErr instanceof ScaleMuleApiError ? { code: refreshErr.code, message: refreshErr.message, field: refreshErr.field } : { code: "REFRESH_FAILED", message: "Auto-refresh failed" };
|
|
7265
|
+
this.onAutoRefreshFailed?.(refreshApiError);
|
|
7266
|
+
throw new ScaleMuleApiError(error);
|
|
7267
|
+
} finally {
|
|
7268
|
+
this.refreshPromise = null;
|
|
7269
|
+
this.onRefreshEnd?.();
|
|
7270
|
+
}
|
|
7271
|
+
}
|
|
7240
7272
|
if (attempt < maxRetries && RETRYABLE_STATUS_CODES3.has(response.status)) {
|
|
7241
7273
|
lastError = error;
|
|
7242
7274
|
const delay = getBackoffDelay2(attempt);
|
package/dist/server/auth.js
CHANGED
|
@@ -132,23 +132,31 @@ var ScaleMuleServer = class {
|
|
|
132
132
|
/**
|
|
133
133
|
* Logout user
|
|
134
134
|
*/
|
|
135
|
-
logout: async (sessionToken) => {
|
|
135
|
+
logout: async (sessionToken, options) => {
|
|
136
136
|
return this.request("POST", "/v1/auth/logout", {
|
|
137
|
-
|
|
137
|
+
sessionToken,
|
|
138
|
+
body: { session_token: sessionToken },
|
|
139
|
+
onTokenRotated: options?.onTokenRotated
|
|
138
140
|
});
|
|
139
141
|
},
|
|
140
142
|
/**
|
|
141
143
|
* Get current user from session token
|
|
142
144
|
*/
|
|
143
|
-
me: async (sessionToken) => {
|
|
144
|
-
return this.request("GET", "/v1/auth/me", {
|
|
145
|
+
me: async (sessionToken, options) => {
|
|
146
|
+
return this.request("GET", "/v1/auth/me", {
|
|
147
|
+
sessionToken,
|
|
148
|
+
onTokenRotated: options?.onTokenRotated
|
|
149
|
+
});
|
|
145
150
|
},
|
|
146
151
|
/**
|
|
147
152
|
* Refresh session token
|
|
148
153
|
*/
|
|
149
|
-
refresh: async (sessionToken) => {
|
|
154
|
+
refresh: async (sessionToken, options) => {
|
|
150
155
|
return this.request("POST", "/v1/auth/refresh", {
|
|
151
|
-
|
|
156
|
+
sessionToken,
|
|
157
|
+
clientContext: options?.clientContext,
|
|
158
|
+
isAutoRefresh: options?.isAutoRefresh,
|
|
159
|
+
onTokenRotated: options?.onTokenRotated
|
|
152
160
|
});
|
|
153
161
|
},
|
|
154
162
|
/**
|
|
@@ -180,7 +188,8 @@ var ScaleMuleServer = class {
|
|
|
180
188
|
if (options?.email) {
|
|
181
189
|
return this.request("POST", "/v1/auth/resend-verification", {
|
|
182
190
|
sessionToken: sessionTokenOrEmail,
|
|
183
|
-
body: { email: options.email }
|
|
191
|
+
body: { email: options.email },
|
|
192
|
+
onTokenRotated: options?.onTokenRotated
|
|
184
193
|
});
|
|
185
194
|
}
|
|
186
195
|
if (sessionTokenOrEmail.includes("@")) {
|
|
@@ -189,7 +198,8 @@ var ScaleMuleServer = class {
|
|
|
189
198
|
});
|
|
190
199
|
}
|
|
191
200
|
return this.request("POST", "/v1/auth/resend-verification", {
|
|
192
|
-
sessionToken: sessionTokenOrEmail
|
|
201
|
+
sessionToken: sessionTokenOrEmail,
|
|
202
|
+
onTokenRotated: options?.onTokenRotated
|
|
193
203
|
});
|
|
194
204
|
}
|
|
195
205
|
};
|
|
@@ -200,37 +210,41 @@ var ScaleMuleServer = class {
|
|
|
200
210
|
/**
|
|
201
211
|
* Update user profile
|
|
202
212
|
*/
|
|
203
|
-
update: async (sessionToken, data) => {
|
|
213
|
+
update: async (sessionToken, data, options) => {
|
|
204
214
|
return this.request("PATCH", "/v1/auth/profile", {
|
|
205
215
|
sessionToken,
|
|
206
|
-
body: data
|
|
216
|
+
body: data,
|
|
217
|
+
onTokenRotated: options?.onTokenRotated
|
|
207
218
|
});
|
|
208
219
|
},
|
|
209
220
|
/**
|
|
210
221
|
* Change password
|
|
211
222
|
*/
|
|
212
|
-
changePassword: async (sessionToken, currentPassword, newPassword) => {
|
|
223
|
+
changePassword: async (sessionToken, currentPassword, newPassword, options) => {
|
|
213
224
|
return this.request("POST", "/v1/auth/change-password", {
|
|
214
225
|
sessionToken,
|
|
215
|
-
body: { current_password: currentPassword, new_password: newPassword }
|
|
226
|
+
body: { current_password: currentPassword, new_password: newPassword },
|
|
227
|
+
onTokenRotated: options?.onTokenRotated
|
|
216
228
|
});
|
|
217
229
|
},
|
|
218
230
|
/**
|
|
219
231
|
* Change email
|
|
220
232
|
*/
|
|
221
|
-
changeEmail: async (sessionToken, newEmail, password) => {
|
|
233
|
+
changeEmail: async (sessionToken, newEmail, password, options) => {
|
|
222
234
|
return this.request("POST", "/v1/auth/change-email", {
|
|
223
235
|
sessionToken,
|
|
224
|
-
body: { new_email: newEmail, password }
|
|
236
|
+
body: { new_email: newEmail, password },
|
|
237
|
+
onTokenRotated: options?.onTokenRotated
|
|
225
238
|
});
|
|
226
239
|
},
|
|
227
240
|
/**
|
|
228
241
|
* Delete account
|
|
229
242
|
*/
|
|
230
|
-
deleteAccount: async (sessionToken, password) => {
|
|
243
|
+
deleteAccount: async (sessionToken, password, options) => {
|
|
231
244
|
return this.request("DELETE", "/v1/auth/me", {
|
|
232
245
|
sessionToken,
|
|
233
|
-
body: { password }
|
|
246
|
+
body: { password },
|
|
247
|
+
onTokenRotated: options?.onTokenRotated
|
|
234
248
|
});
|
|
235
249
|
}
|
|
236
250
|
};
|
|
@@ -691,6 +705,9 @@ var ScaleMuleServer = class {
|
|
|
691
705
|
this.apiKey = config.apiKey;
|
|
692
706
|
this.gatewayUrl = resolveGatewayUrl(config);
|
|
693
707
|
this.debug = config.debug || false;
|
|
708
|
+
this.onRefreshStart = config.onRefreshStart;
|
|
709
|
+
this.onRefreshEnd = config.onRefreshEnd;
|
|
710
|
+
this.onAutoRefreshFailed = config.onAutoRefreshFailed;
|
|
694
711
|
this.money = money.createMoneyClient({
|
|
695
712
|
apiKey: this.apiKey,
|
|
696
713
|
gatewayUrl: this.gatewayUrl,
|
|
@@ -734,6 +751,10 @@ var ScaleMuleServer = class {
|
|
|
734
751
|
headers,
|
|
735
752
|
body: options.body ? JSON.stringify(options.body) : void 0
|
|
736
753
|
});
|
|
754
|
+
const rotated = response.headers.get("x-rotated-session-token");
|
|
755
|
+
if (rotated && options.onTokenRotated) {
|
|
756
|
+
options.onTokenRotated(rotated);
|
|
757
|
+
}
|
|
737
758
|
const text = await response.text();
|
|
738
759
|
let responseData = null;
|
|
739
760
|
try {
|
|
@@ -745,6 +766,33 @@ var ScaleMuleServer = class {
|
|
|
745
766
|
code: `HTTP_${response.status}`,
|
|
746
767
|
message: responseData?.message || text || response.statusText
|
|
747
768
|
};
|
|
769
|
+
if (response.status === 401 && options.sessionToken && !options.isAutoRefresh) {
|
|
770
|
+
if (this.debug) console.log("[ScaleMule Server] 401 received, attempting auto-refresh...");
|
|
771
|
+
try {
|
|
772
|
+
this.onRefreshStart?.();
|
|
773
|
+
const refreshData = await this.auth.refresh(options.sessionToken, {
|
|
774
|
+
clientContext: options.clientContext,
|
|
775
|
+
isAutoRefresh: true
|
|
776
|
+
// Prevent infinite loops
|
|
777
|
+
});
|
|
778
|
+
const newToken = refreshData.session_token;
|
|
779
|
+
if (this.debug) console.log("[ScaleMule Server] Auto-refresh succeeded, retrying original request...");
|
|
780
|
+
options.onTokenRotated?.(newToken);
|
|
781
|
+
return this.request(method, path, {
|
|
782
|
+
...options,
|
|
783
|
+
sessionToken: newToken,
|
|
784
|
+
isAutoRefresh: true
|
|
785
|
+
// Don't refresh again on this retry
|
|
786
|
+
});
|
|
787
|
+
} catch (refreshErr) {
|
|
788
|
+
if (this.debug) console.error("[ScaleMule Server] Auto-refresh failed:", refreshErr);
|
|
789
|
+
const refreshApiError = refreshErr instanceof ScaleMuleApiError ? { code: refreshErr.code, message: refreshErr.message } : { code: "REFRESH_FAILED", message: "Auto-refresh failed" };
|
|
790
|
+
this.onAutoRefreshFailed?.(refreshApiError);
|
|
791
|
+
throw new ScaleMuleApiError(error);
|
|
792
|
+
} finally {
|
|
793
|
+
this.onRefreshEnd?.();
|
|
794
|
+
}
|
|
795
|
+
}
|
|
748
796
|
throw new ScaleMuleApiError(error);
|
|
749
797
|
}
|
|
750
798
|
const data = responseData?.data !== void 0 ? responseData.data : responseData;
|
|
@@ -1173,8 +1221,16 @@ function createAuthRoutes(config = {}) {
|
|
|
1173
1221
|
// ==================== Logout ====================
|
|
1174
1222
|
case "logout": {
|
|
1175
1223
|
const session = await getSession();
|
|
1224
|
+
let rotated = null;
|
|
1176
1225
|
if (session) {
|
|
1177
|
-
|
|
1226
|
+
try {
|
|
1227
|
+
await sm.auth.logout(session.sessionToken, {
|
|
1228
|
+
onTokenRotated: (newToken) => {
|
|
1229
|
+
rotated = newToken;
|
|
1230
|
+
}
|
|
1231
|
+
});
|
|
1232
|
+
} catch {
|
|
1233
|
+
}
|
|
1178
1234
|
}
|
|
1179
1235
|
if (config.onLogout) {
|
|
1180
1236
|
await config.onLogout();
|
|
@@ -1239,6 +1295,7 @@ function createAuthRoutes(config = {}) {
|
|
|
1239
1295
|
case "resend-verification": {
|
|
1240
1296
|
const { email } = body;
|
|
1241
1297
|
const session = await getSession();
|
|
1298
|
+
let rotated = null;
|
|
1242
1299
|
if (email) {
|
|
1243
1300
|
try {
|
|
1244
1301
|
await sm.auth.resendVerification(email);
|
|
@@ -1256,7 +1313,11 @@ function createAuthRoutes(config = {}) {
|
|
|
1256
1313
|
return errorResponse("UNAUTHORIZED", "Email or session required", 401);
|
|
1257
1314
|
}
|
|
1258
1315
|
try {
|
|
1259
|
-
await sm.auth.resendVerification(session.sessionToken
|
|
1316
|
+
await sm.auth.resendVerification(session.sessionToken, {
|
|
1317
|
+
onTokenRotated: (newToken) => {
|
|
1318
|
+
rotated = newToken;
|
|
1319
|
+
}
|
|
1320
|
+
});
|
|
1260
1321
|
} catch (err) {
|
|
1261
1322
|
const apiErr = err instanceof ScaleMuleApiError ? err : null;
|
|
1262
1323
|
return errorResponse(
|
|
@@ -1265,6 +1326,14 @@ function createAuthRoutes(config = {}) {
|
|
|
1265
1326
|
400
|
|
1266
1327
|
);
|
|
1267
1328
|
}
|
|
1329
|
+
if (rotated) {
|
|
1330
|
+
return withRefreshedSession(
|
|
1331
|
+
rotated,
|
|
1332
|
+
session.userId,
|
|
1333
|
+
{ success: true, data: { message: "Verification email sent" } },
|
|
1334
|
+
cookieOptions
|
|
1335
|
+
);
|
|
1336
|
+
}
|
|
1268
1337
|
return successResponse({ message: "Verification email sent" });
|
|
1269
1338
|
}
|
|
1270
1339
|
// ==================== Refresh Session ====================
|
|
@@ -1299,11 +1368,17 @@ function createAuthRoutes(config = {}) {
|
|
|
1299
1368
|
if (!current_password || !new_password) {
|
|
1300
1369
|
return errorResponse("VALIDATION_ERROR", "Current and new password required", 400);
|
|
1301
1370
|
}
|
|
1371
|
+
let rotated = null;
|
|
1302
1372
|
try {
|
|
1303
1373
|
await sm.user.changePassword(
|
|
1304
1374
|
session.sessionToken,
|
|
1305
1375
|
current_password,
|
|
1306
|
-
new_password
|
|
1376
|
+
new_password,
|
|
1377
|
+
{
|
|
1378
|
+
onTokenRotated: (newToken) => {
|
|
1379
|
+
rotated = newToken;
|
|
1380
|
+
}
|
|
1381
|
+
}
|
|
1307
1382
|
);
|
|
1308
1383
|
} catch (err) {
|
|
1309
1384
|
const apiErr = err instanceof ScaleMuleApiError ? err : null;
|
|
@@ -1313,6 +1388,14 @@ function createAuthRoutes(config = {}) {
|
|
|
1313
1388
|
400
|
|
1314
1389
|
);
|
|
1315
1390
|
}
|
|
1391
|
+
if (rotated) {
|
|
1392
|
+
return withRefreshedSession(
|
|
1393
|
+
rotated,
|
|
1394
|
+
session.userId,
|
|
1395
|
+
{ success: true, data: { message: "Password changed successfully" } },
|
|
1396
|
+
cookieOptions
|
|
1397
|
+
);
|
|
1398
|
+
}
|
|
1316
1399
|
return successResponse({ message: "Password changed successfully" });
|
|
1317
1400
|
}
|
|
1318
1401
|
// ==================== Switch Account ====================
|
|
@@ -1393,14 +1476,27 @@ function createAuthRoutes(config = {}) {
|
|
|
1393
1476
|
return withNorm(errorResponse("UNAUTHORIZED", "Authentication required", 401));
|
|
1394
1477
|
}
|
|
1395
1478
|
let userData;
|
|
1479
|
+
let rotated = null;
|
|
1396
1480
|
try {
|
|
1397
|
-
userData = await sm.auth.me(session.sessionToken
|
|
1481
|
+
userData = await sm.auth.me(session.sessionToken, {
|
|
1482
|
+
onTokenRotated: (newToken) => {
|
|
1483
|
+
rotated = newToken;
|
|
1484
|
+
}
|
|
1485
|
+
});
|
|
1398
1486
|
} catch {
|
|
1399
1487
|
return withNorm(clearSession(
|
|
1400
1488
|
{ error: { code: "SESSION_EXPIRED", message: "Session expired" } },
|
|
1401
1489
|
cookieOptions
|
|
1402
1490
|
));
|
|
1403
1491
|
}
|
|
1492
|
+
if (rotated) {
|
|
1493
|
+
return withNorm(withRefreshedSession(
|
|
1494
|
+
rotated,
|
|
1495
|
+
session.userId,
|
|
1496
|
+
{ user: userData, sessionToken: rotated, userId: session.userId },
|
|
1497
|
+
cookieOptions
|
|
1498
|
+
));
|
|
1499
|
+
}
|
|
1404
1500
|
return withNorm(successResponse({ user: userData, sessionToken: session.sessionToken, userId: session.userId }));
|
|
1405
1501
|
}
|
|
1406
1502
|
// ==================== Get Session Status ====================
|
|
@@ -1479,8 +1575,13 @@ function createAuthRoutes(config = {}) {
|
|
|
1479
1575
|
const body = await request.json().catch(() => ({}));
|
|
1480
1576
|
const { full_name, avatar_url } = body;
|
|
1481
1577
|
let updatedUser;
|
|
1578
|
+
let rotated = null;
|
|
1482
1579
|
try {
|
|
1483
|
-
updatedUser = await sm.user.update(session.sessionToken, { full_name, avatar_url }
|
|
1580
|
+
updatedUser = await sm.user.update(session.sessionToken, { full_name, avatar_url }, {
|
|
1581
|
+
onTokenRotated: (newToken) => {
|
|
1582
|
+
rotated = newToken;
|
|
1583
|
+
}
|
|
1584
|
+
});
|
|
1484
1585
|
} catch (err) {
|
|
1485
1586
|
const apiErr = err instanceof ScaleMuleApiError ? err : null;
|
|
1486
1587
|
return errorResponse(
|
|
@@ -1489,6 +1590,14 @@ function createAuthRoutes(config = {}) {
|
|
|
1489
1590
|
400
|
|
1490
1591
|
);
|
|
1491
1592
|
}
|
|
1593
|
+
if (rotated) {
|
|
1594
|
+
return withRefreshedSession(
|
|
1595
|
+
rotated,
|
|
1596
|
+
session.userId,
|
|
1597
|
+
{ success: true, data: { user: updatedUser } },
|
|
1598
|
+
cookieOptions
|
|
1599
|
+
);
|
|
1600
|
+
}
|
|
1492
1601
|
return successResponse({ user: updatedUser });
|
|
1493
1602
|
}
|
|
1494
1603
|
default:
|