@edge-markets/connect-node 1.0.3 → 1.2.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/README.md +15 -9
- package/dist/index.d.mts +32 -348
- package/dist/index.d.ts +32 -348
- package/dist/index.js +127 -292
- package/dist/index.mjs +129 -293
- package/package.json +10 -12
package/dist/index.js
CHANGED
|
@@ -43,13 +43,28 @@ module.exports = __toCommonJS(index_exports);
|
|
|
43
43
|
var import_connect = require("@edge-markets/connect");
|
|
44
44
|
var DEFAULT_TIMEOUT = 3e4;
|
|
45
45
|
var USER_AGENT = "@edge-markets/connect-node/1.0.0";
|
|
46
|
-
var
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
46
|
+
var DEFAULT_RETRY_CONFIG = {
|
|
47
|
+
maxRetries: 3,
|
|
48
|
+
retryOn: [408, 429, 500, 502, 503, 504],
|
|
49
|
+
backoff: "exponential",
|
|
50
|
+
baseDelayMs: 1e3
|
|
51
|
+
};
|
|
52
|
+
var instances = /* @__PURE__ */ new Map();
|
|
53
|
+
function getInstanceKey(config) {
|
|
54
|
+
return `${config.clientId}:${config.environment}`;
|
|
55
|
+
}
|
|
56
|
+
var EdgeConnectServer = class _EdgeConnectServer {
|
|
57
|
+
static getInstance(config) {
|
|
58
|
+
const key = getInstanceKey(config);
|
|
59
|
+
const existing = instances.get(key);
|
|
60
|
+
if (existing) return existing;
|
|
61
|
+
const instance = new _EdgeConnectServer(config);
|
|
62
|
+
instances.set(key, instance);
|
|
63
|
+
return instance;
|
|
64
|
+
}
|
|
65
|
+
static clearInstances() {
|
|
66
|
+
instances.clear();
|
|
67
|
+
}
|
|
53
68
|
constructor(config) {
|
|
54
69
|
if (!config.clientId) {
|
|
55
70
|
throw new Error("EdgeConnectServer: clientId is required");
|
|
@@ -63,66 +78,29 @@ var EdgeConnectServer = class {
|
|
|
63
78
|
this.config = config;
|
|
64
79
|
const envConfig = (0, import_connect.getEnvironmentConfig)(config.environment);
|
|
65
80
|
this.apiBaseUrl = config.apiBaseUrl || envConfig.apiBaseUrl;
|
|
66
|
-
this.oauthBaseUrl = envConfig.oauthBaseUrl;
|
|
81
|
+
this.oauthBaseUrl = config.oauthBaseUrl || envConfig.oauthBaseUrl;
|
|
67
82
|
this.timeout = config.timeout || DEFAULT_TIMEOUT;
|
|
83
|
+
this.retryConfig = {
|
|
84
|
+
...DEFAULT_RETRY_CONFIG,
|
|
85
|
+
...config.retry
|
|
86
|
+
};
|
|
68
87
|
}
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
// ===========================================================================
|
|
72
|
-
/**
|
|
73
|
-
* Exchanges an authorization code for tokens.
|
|
74
|
-
*
|
|
75
|
-
* Call this after receiving the code from EdgeLink's `onSuccess` callback.
|
|
76
|
-
* The code is single-use and expires in ~10 minutes.
|
|
77
|
-
*
|
|
78
|
-
* @param code - Authorization code from EdgeLink
|
|
79
|
-
* @param codeVerifier - PKCE code verifier from EdgeLink
|
|
80
|
-
* @returns Access token, refresh token, and metadata
|
|
81
|
-
* @throws EdgeTokenExchangeError if exchange fails
|
|
82
|
-
*
|
|
83
|
-
* @example
|
|
84
|
-
* ```typescript
|
|
85
|
-
* // In your /api/edge/exchange endpoint
|
|
86
|
-
* const { code, codeVerifier } = req.body
|
|
87
|
-
*
|
|
88
|
-
* try {
|
|
89
|
-
* const tokens = await edge.exchangeCode(code, codeVerifier)
|
|
90
|
-
*
|
|
91
|
-
* // Store tokens securely
|
|
92
|
-
* await db.edgeConnections.upsert({
|
|
93
|
-
* userId: req.user.id,
|
|
94
|
-
* accessToken: encrypt(tokens.accessToken),
|
|
95
|
-
* refreshToken: encrypt(tokens.refreshToken),
|
|
96
|
-
* expiresAt: new Date(tokens.expiresAt),
|
|
97
|
-
* })
|
|
98
|
-
*
|
|
99
|
-
* return { success: true }
|
|
100
|
-
* } catch (error) {
|
|
101
|
-
* if (error instanceof EdgeTokenExchangeError) {
|
|
102
|
-
* // Code expired or already used
|
|
103
|
-
* return { error: 'Please try connecting again' }
|
|
104
|
-
* }
|
|
105
|
-
* throw error
|
|
106
|
-
* }
|
|
107
|
-
* ```
|
|
108
|
-
*/
|
|
109
|
-
async exchangeCode(code, codeVerifier) {
|
|
110
|
-
const tokenUrl = `${this.oauthBaseUrl}/oauth/token`;
|
|
88
|
+
async exchangeCode(code, codeVerifier, redirectUri) {
|
|
89
|
+
const tokenUrl = `${this.oauthBaseUrl}/token`;
|
|
111
90
|
const body = {
|
|
112
91
|
grant_type: "authorization_code",
|
|
113
92
|
code,
|
|
114
93
|
code_verifier: codeVerifier,
|
|
115
94
|
client_id: this.config.clientId,
|
|
116
|
-
client_secret: this.config.clientSecret
|
|
95
|
+
client_secret: this.config.clientSecret,
|
|
96
|
+
redirect_uri: redirectUri || this.config.redirectUri || ""
|
|
117
97
|
};
|
|
118
98
|
try {
|
|
119
99
|
const response = await this.fetchWithTimeout(tokenUrl, {
|
|
120
100
|
method: "POST",
|
|
121
101
|
headers: {
|
|
122
102
|
"Content-Type": "application/json",
|
|
123
|
-
"User-Agent": USER_AGENT
|
|
124
|
-
// Required for ngrok tunnels in local development
|
|
125
|
-
"ngrok-skip-browser-warning": "true"
|
|
103
|
+
"User-Agent": USER_AGENT
|
|
126
104
|
},
|
|
127
105
|
body: JSON.stringify(body)
|
|
128
106
|
});
|
|
@@ -137,44 +115,8 @@ var EdgeConnectServer = class {
|
|
|
137
115
|
throw new import_connect.EdgeNetworkError("Failed to exchange code", error);
|
|
138
116
|
}
|
|
139
117
|
}
|
|
140
|
-
/**
|
|
141
|
-
* Refreshes an access token using a refresh token.
|
|
142
|
-
*
|
|
143
|
-
* Call this when the access token is expired or about to expire.
|
|
144
|
-
* Check `tokens.expiresAt` to know when to refresh.
|
|
145
|
-
*
|
|
146
|
-
* @param refreshToken - Refresh token from previous exchange
|
|
147
|
-
* @returns New tokens (refresh token may or may not change)
|
|
148
|
-
* @throws EdgeAuthenticationError if refresh fails
|
|
149
|
-
*
|
|
150
|
-
* @example
|
|
151
|
-
* ```typescript
|
|
152
|
-
* // Check if token needs refresh (with 5 minute buffer)
|
|
153
|
-
* const BUFFER_MS = 5 * 60 * 1000
|
|
154
|
-
*
|
|
155
|
-
* async function getValidAccessToken(userId: string): Promise<string> {
|
|
156
|
-
* const connection = await db.edgeConnections.get(userId)
|
|
157
|
-
*
|
|
158
|
-
* if (Date.now() > connection.expiresAt.getTime() - BUFFER_MS) {
|
|
159
|
-
* // Token expired or expiring soon - refresh it
|
|
160
|
-
* const newTokens = await edge.refreshTokens(decrypt(connection.refreshToken))
|
|
161
|
-
*
|
|
162
|
-
* // Update stored tokens
|
|
163
|
-
* await db.edgeConnections.update(userId, {
|
|
164
|
-
* accessToken: encrypt(newTokens.accessToken),
|
|
165
|
-
* refreshToken: encrypt(newTokens.refreshToken),
|
|
166
|
-
* expiresAt: new Date(newTokens.expiresAt),
|
|
167
|
-
* })
|
|
168
|
-
*
|
|
169
|
-
* return newTokens.accessToken
|
|
170
|
-
* }
|
|
171
|
-
*
|
|
172
|
-
* return decrypt(connection.accessToken)
|
|
173
|
-
* }
|
|
174
|
-
* ```
|
|
175
|
-
*/
|
|
176
118
|
async refreshTokens(refreshToken) {
|
|
177
|
-
const tokenUrl = `${this.oauthBaseUrl}/
|
|
119
|
+
const tokenUrl = `${this.oauthBaseUrl}/token`;
|
|
178
120
|
const body = {
|
|
179
121
|
grant_type: "refresh_token",
|
|
180
122
|
refresh_token: refreshToken,
|
|
@@ -186,15 +128,14 @@ var EdgeConnectServer = class {
|
|
|
186
128
|
method: "POST",
|
|
187
129
|
headers: {
|
|
188
130
|
"Content-Type": "application/json",
|
|
189
|
-
"User-Agent": USER_AGENT
|
|
190
|
-
"ngrok-skip-browser-warning": "true"
|
|
131
|
+
"User-Agent": USER_AGENT
|
|
191
132
|
},
|
|
192
133
|
body: JSON.stringify(body)
|
|
193
134
|
});
|
|
194
135
|
if (!response.ok) {
|
|
195
136
|
const error = await response.json().catch(() => ({}));
|
|
196
137
|
throw new import_connect.EdgeAuthenticationError(
|
|
197
|
-
error.error_description || "Token refresh failed. User may need to reconnect.",
|
|
138
|
+
error.message || error.error_description || "Token refresh failed. User may need to reconnect.",
|
|
198
139
|
{ tokenError: error }
|
|
199
140
|
);
|
|
200
141
|
}
|
|
@@ -205,80 +146,14 @@ var EdgeConnectServer = class {
|
|
|
205
146
|
throw new import_connect.EdgeNetworkError("Failed to refresh tokens", error);
|
|
206
147
|
}
|
|
207
148
|
}
|
|
208
|
-
// ===========================================================================
|
|
209
|
-
// USER & BALANCE
|
|
210
|
-
// ===========================================================================
|
|
211
|
-
/**
|
|
212
|
-
* Gets the connected user's profile.
|
|
213
|
-
*
|
|
214
|
-
* Requires scope: `user.read`
|
|
215
|
-
*
|
|
216
|
-
* @param accessToken - Valid access token
|
|
217
|
-
* @returns User profile information
|
|
218
|
-
*
|
|
219
|
-
* @example
|
|
220
|
-
* ```typescript
|
|
221
|
-
* const user = await edge.getUser(accessToken)
|
|
222
|
-
* console.log(`Connected: ${user.firstName} ${user.lastName}`)
|
|
223
|
-
* ```
|
|
224
|
-
*/
|
|
225
149
|
async getUser(accessToken) {
|
|
226
150
|
return this.apiRequest("GET", "/user", accessToken);
|
|
227
151
|
}
|
|
228
|
-
/**
|
|
229
|
-
* Gets the connected user's EdgeBoost balance.
|
|
230
|
-
*
|
|
231
|
-
* Requires scope: `balance.read`
|
|
232
|
-
*
|
|
233
|
-
* @param accessToken - Valid access token
|
|
234
|
-
* @returns Balance information
|
|
235
|
-
*
|
|
236
|
-
* @example
|
|
237
|
-
* ```typescript
|
|
238
|
-
* const balance = await edge.getBalance(accessToken)
|
|
239
|
-
* console.log(`Balance: $${balance.availableBalance.toFixed(2)} ${balance.currency}`)
|
|
240
|
-
* ```
|
|
241
|
-
*/
|
|
242
152
|
async getBalance(accessToken) {
|
|
243
153
|
return this.apiRequest("GET", "/balance", accessToken);
|
|
244
154
|
}
|
|
245
|
-
// ===========================================================================
|
|
246
|
-
// TRANSFERS
|
|
247
|
-
// ===========================================================================
|
|
248
|
-
/**
|
|
249
|
-
* Initiates a fund transfer.
|
|
250
|
-
*
|
|
251
|
-
* Requires scope: `transfer.write`
|
|
252
|
-
*
|
|
253
|
-
* **Transfer Types:**
|
|
254
|
-
* - `debit`: Pull funds FROM user's EdgeBoost TO your platform
|
|
255
|
-
* - `credit`: Push funds FROM your platform TO user's EdgeBoost
|
|
256
|
-
*
|
|
257
|
-
* **Idempotency:** Using the same `idempotencyKey` returns the existing
|
|
258
|
-
* transfer instead of creating a duplicate. Use a unique key per transaction.
|
|
259
|
-
*
|
|
260
|
-
* **OTP Verification:** Transfers require OTP verification before completion.
|
|
261
|
-
* The response includes `otpMethod` indicating how the user will receive the code.
|
|
262
|
-
*
|
|
263
|
-
* @param accessToken - Valid access token
|
|
264
|
-
* @param options - Transfer options
|
|
265
|
-
* @returns Transfer with status and OTP method
|
|
266
|
-
*
|
|
267
|
-
* @example
|
|
268
|
-
* ```typescript
|
|
269
|
-
* const transfer = await edge.initiateTransfer(accessToken, {
|
|
270
|
-
* type: 'debit',
|
|
271
|
-
* amount: '100.00',
|
|
272
|
-
* idempotencyKey: `withdraw_${userId}_${Date.now()}`,
|
|
273
|
-
* })
|
|
274
|
-
*
|
|
275
|
-
* if (transfer.status === 'pending_verification') {
|
|
276
|
-
* // Show OTP input to user
|
|
277
|
-
* console.log(`Enter code sent via ${transfer.otpMethod}`)
|
|
278
|
-
* }
|
|
279
|
-
* ```
|
|
280
|
-
*/
|
|
281
155
|
async initiateTransfer(accessToken, options) {
|
|
156
|
+
this.validateTransferOptions(options);
|
|
282
157
|
const body = {
|
|
283
158
|
type: options.type,
|
|
284
159
|
amount: options.amount,
|
|
@@ -286,28 +161,32 @@ var EdgeConnectServer = class {
|
|
|
286
161
|
};
|
|
287
162
|
return this.apiRequest("POST", "/transfer", accessToken, body);
|
|
288
163
|
}
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
164
|
+
validateTransferOptions(options) {
|
|
165
|
+
const errors = {};
|
|
166
|
+
if (!options.type || !["debit", "credit"].includes(options.type)) {
|
|
167
|
+
errors.type = ['Must be "debit" or "credit"'];
|
|
168
|
+
}
|
|
169
|
+
if (!options.amount) {
|
|
170
|
+
errors.amount = ["Amount is required"];
|
|
171
|
+
} else {
|
|
172
|
+
const amount = parseFloat(options.amount);
|
|
173
|
+
if (isNaN(amount)) {
|
|
174
|
+
errors.amount = ["Must be a valid number"];
|
|
175
|
+
} else if (amount <= 0) {
|
|
176
|
+
errors.amount = ["Must be greater than 0"];
|
|
177
|
+
} else if (!/^\d+(\.\d{1,2})?$/.test(options.amount)) {
|
|
178
|
+
errors.amount = ["Must have at most 2 decimal places"];
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
if (!options.idempotencyKey || options.idempotencyKey.trim() === "") {
|
|
182
|
+
errors.idempotencyKey = ["idempotencyKey is required"];
|
|
183
|
+
} else if (options.idempotencyKey.length > 255) {
|
|
184
|
+
errors.idempotencyKey = ["Must be 255 characters or less"];
|
|
185
|
+
}
|
|
186
|
+
if (Object.keys(errors).length > 0) {
|
|
187
|
+
throw new import_connect.EdgeValidationError("Invalid transfer options", errors);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
311
190
|
async verifyTransfer(accessToken, transferId, otp) {
|
|
312
191
|
return this.apiRequest(
|
|
313
192
|
"POST",
|
|
@@ -316,21 +195,6 @@ var EdgeConnectServer = class {
|
|
|
316
195
|
{ otp }
|
|
317
196
|
);
|
|
318
197
|
}
|
|
319
|
-
/**
|
|
320
|
-
* Gets the status of a transfer.
|
|
321
|
-
*
|
|
322
|
-
* Use for polling after initiating a transfer.
|
|
323
|
-
*
|
|
324
|
-
* @param accessToken - Valid access token
|
|
325
|
-
* @param transferId - Transfer ID
|
|
326
|
-
* @returns Current transfer status
|
|
327
|
-
*
|
|
328
|
-
* @example
|
|
329
|
-
* ```typescript
|
|
330
|
-
* const transfer = await edge.getTransfer(accessToken, transferId)
|
|
331
|
-
* console.log(`Status: ${transfer.status}`)
|
|
332
|
-
* ```
|
|
333
|
-
*/
|
|
334
198
|
async getTransfer(accessToken, transferId) {
|
|
335
199
|
return this.apiRequest(
|
|
336
200
|
"GET",
|
|
@@ -338,27 +202,6 @@ var EdgeConnectServer = class {
|
|
|
338
202
|
accessToken
|
|
339
203
|
);
|
|
340
204
|
}
|
|
341
|
-
/**
|
|
342
|
-
* Lists transfers for the connected user.
|
|
343
|
-
*
|
|
344
|
-
* Useful for reconciliation and showing transfer history.
|
|
345
|
-
*
|
|
346
|
-
* @param accessToken - Valid access token
|
|
347
|
-
* @param params - Pagination and filter options
|
|
348
|
-
* @returns Paginated list of transfers
|
|
349
|
-
*
|
|
350
|
-
* @example
|
|
351
|
-
* ```typescript
|
|
352
|
-
* // Get first page of completed transfers
|
|
353
|
-
* const { transfers, total } = await edge.listTransfers(accessToken, {
|
|
354
|
-
* status: 'completed',
|
|
355
|
-
* limit: 10,
|
|
356
|
-
* offset: 0,
|
|
357
|
-
* })
|
|
358
|
-
*
|
|
359
|
-
* console.log(`Showing ${transfers.length} of ${total} transfers`)
|
|
360
|
-
* ```
|
|
361
|
-
*/
|
|
362
205
|
async listTransfers(accessToken, params) {
|
|
363
206
|
const queryParams = new URLSearchParams();
|
|
364
207
|
if (params?.limit) queryParams.set("limit", String(params.limit));
|
|
@@ -368,68 +211,65 @@ var EdgeConnectServer = class {
|
|
|
368
211
|
const path = query ? `/transfers?${query}` : "/transfers";
|
|
369
212
|
return this.apiRequest("GET", path, accessToken);
|
|
370
213
|
}
|
|
371
|
-
// ===========================================================================
|
|
372
|
-
// CONSENT
|
|
373
|
-
// ===========================================================================
|
|
374
|
-
/**
|
|
375
|
-
* Revokes the user's consent (disconnects their account).
|
|
376
|
-
*
|
|
377
|
-
* After revocation:
|
|
378
|
-
* - All API calls will fail with `consent_required` error
|
|
379
|
-
* - User must go through EdgeLink again to reconnect
|
|
380
|
-
* - Stored tokens become invalid
|
|
381
|
-
*
|
|
382
|
-
* Use this for "Disconnect" or "Unlink" features in your app.
|
|
383
|
-
*
|
|
384
|
-
* @param accessToken - Valid access token
|
|
385
|
-
* @returns Confirmation of revocation
|
|
386
|
-
*
|
|
387
|
-
* @example
|
|
388
|
-
* ```typescript
|
|
389
|
-
* // Disconnect user's EdgeBoost account
|
|
390
|
-
* await edge.revokeConsent(accessToken)
|
|
391
|
-
*
|
|
392
|
-
* // Clean up stored tokens
|
|
393
|
-
* await db.edgeConnections.delete(userId)
|
|
394
|
-
*
|
|
395
|
-
* console.log('EdgeBoost disconnected')
|
|
396
|
-
* ```
|
|
397
|
-
*/
|
|
398
214
|
async revokeConsent(accessToken) {
|
|
399
215
|
return this.apiRequest("DELETE", "/consent", accessToken);
|
|
400
216
|
}
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
217
|
+
getRetryDelay(attempt) {
|
|
218
|
+
const { backoff, baseDelayMs } = this.retryConfig;
|
|
219
|
+
if (backoff === "exponential") {
|
|
220
|
+
return baseDelayMs * Math.pow(2, attempt);
|
|
221
|
+
}
|
|
222
|
+
return baseDelayMs * (attempt + 1);
|
|
223
|
+
}
|
|
224
|
+
async sleep(ms) {
|
|
225
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
226
|
+
}
|
|
407
227
|
async apiRequest(method, path, accessToken, body) {
|
|
408
228
|
const url = `${this.apiBaseUrl}${path}`;
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
229
|
+
let lastError = null;
|
|
230
|
+
for (let attempt = 0; attempt <= this.retryConfig.maxRetries; attempt++) {
|
|
231
|
+
const startTime = Date.now();
|
|
232
|
+
if (attempt > 0) {
|
|
233
|
+
await this.sleep(this.getRetryDelay(attempt - 1));
|
|
234
|
+
}
|
|
235
|
+
this.config.onRequest?.({ method, url, body });
|
|
236
|
+
try {
|
|
237
|
+
const response = await this.fetchWithTimeout(url, {
|
|
238
|
+
method,
|
|
239
|
+
headers: {
|
|
240
|
+
Authorization: `Bearer ${accessToken}`,
|
|
241
|
+
"Content-Type": "application/json",
|
|
242
|
+
"User-Agent": USER_AGENT
|
|
243
|
+
},
|
|
244
|
+
body: body ? JSON.stringify(body) : void 0
|
|
245
|
+
});
|
|
246
|
+
const responseBody = await response.json().catch(() => ({}));
|
|
247
|
+
const durationMs = Date.now() - startTime;
|
|
248
|
+
this.config.onResponse?.({ status: response.status, body: responseBody, durationMs });
|
|
249
|
+
if (!response.ok) {
|
|
250
|
+
if (this.retryConfig.retryOn.includes(response.status) && attempt < this.retryConfig.maxRetries) {
|
|
251
|
+
lastError = await this.handleApiErrorFromBody(responseBody, response.status, path);
|
|
252
|
+
continue;
|
|
253
|
+
}
|
|
254
|
+
throw await this.handleApiErrorFromBody(responseBody, response.status, path);
|
|
255
|
+
}
|
|
256
|
+
return responseBody;
|
|
257
|
+
} catch (error) {
|
|
258
|
+
if (error instanceof import_connect.EdgeError) {
|
|
259
|
+
if (!(error instanceof import_connect.EdgeNetworkError) || attempt >= this.retryConfig.maxRetries) {
|
|
260
|
+
throw error;
|
|
261
|
+
}
|
|
262
|
+
lastError = error;
|
|
263
|
+
continue;
|
|
264
|
+
}
|
|
265
|
+
lastError = error;
|
|
266
|
+
if (attempt >= this.retryConfig.maxRetries) {
|
|
267
|
+
throw new import_connect.EdgeNetworkError(`API request failed: ${method} ${path}`, lastError);
|
|
268
|
+
}
|
|
423
269
|
}
|
|
424
|
-
return response.json();
|
|
425
|
-
} catch (error) {
|
|
426
|
-
if (error instanceof import_connect.EdgeError) throw error;
|
|
427
|
-
throw new import_connect.EdgeNetworkError(`API request failed: ${method} ${path}`, error);
|
|
428
270
|
}
|
|
271
|
+
throw new import_connect.EdgeNetworkError(`API request failed after ${this.retryConfig.maxRetries} retries: ${method} ${path}`, lastError);
|
|
429
272
|
}
|
|
430
|
-
/**
|
|
431
|
-
* Fetch with timeout support.
|
|
432
|
-
*/
|
|
433
273
|
async fetchWithTimeout(url, options) {
|
|
434
274
|
const controller = new AbortController();
|
|
435
275
|
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
|
|
@@ -442,9 +282,6 @@ var EdgeConnectServer = class {
|
|
|
442
282
|
clearTimeout(timeoutId);
|
|
443
283
|
}
|
|
444
284
|
}
|
|
445
|
-
/**
|
|
446
|
-
* Parses token response from Cognito.
|
|
447
|
-
*/
|
|
448
285
|
parseTokenResponse(data, existingRefreshToken) {
|
|
449
286
|
return {
|
|
450
287
|
accessToken: data.access_token,
|
|
@@ -455,35 +292,33 @@ var EdgeConnectServer = class {
|
|
|
455
292
|
scope: data.scope || ""
|
|
456
293
|
};
|
|
457
294
|
}
|
|
458
|
-
/**
|
|
459
|
-
* Handles token exchange errors.
|
|
460
|
-
*/
|
|
461
295
|
handleTokenError(error, status) {
|
|
462
296
|
const errorCode = error.error;
|
|
463
|
-
const
|
|
464
|
-
if (errorCode === "invalid_grant") {
|
|
297
|
+
const errorMessage = error.message || error.error_description;
|
|
298
|
+
if (errorCode === "invalid_grant" || errorMessage?.includes("Invalid or expired")) {
|
|
465
299
|
return new import_connect.EdgeTokenExchangeError(
|
|
466
300
|
"Authorization code is invalid, expired, or already used. Please try again.",
|
|
467
|
-
{
|
|
301
|
+
{ edgeBoostError: error }
|
|
468
302
|
);
|
|
469
303
|
}
|
|
470
|
-
if (errorCode === "invalid_client") {
|
|
304
|
+
if (errorCode === "invalid_client" || errorMessage?.includes("Invalid client")) {
|
|
471
305
|
return new import_connect.EdgeAuthenticationError(
|
|
472
306
|
"Invalid client credentials. Check your client ID and secret.",
|
|
473
|
-
{
|
|
307
|
+
{ edgeBoostError: error }
|
|
308
|
+
);
|
|
309
|
+
}
|
|
310
|
+
if (errorMessage?.includes("code_verifier") || errorMessage?.includes("PKCE")) {
|
|
311
|
+
return new import_connect.EdgeTokenExchangeError(
|
|
312
|
+
"PKCE verification failed. Please try again.",
|
|
313
|
+
{ edgeBoostError: error }
|
|
474
314
|
);
|
|
475
315
|
}
|
|
476
316
|
return new import_connect.EdgeTokenExchangeError(
|
|
477
|
-
|
|
478
|
-
{
|
|
317
|
+
errorMessage || "Failed to exchange authorization code",
|
|
318
|
+
{ edgeBoostError: error, statusCode: status }
|
|
479
319
|
);
|
|
480
320
|
}
|
|
481
|
-
|
|
482
|
-
* Handles API errors.
|
|
483
|
-
*/
|
|
484
|
-
async handleApiError(response, path) {
|
|
485
|
-
const error = await response.json().catch(() => ({}));
|
|
486
|
-
const status = response.status;
|
|
321
|
+
async handleApiErrorFromBody(error, status, path) {
|
|
487
322
|
if (status === 401) {
|
|
488
323
|
return new import_connect.EdgeAuthenticationError(
|
|
489
324
|
error.message || "Access token is invalid or expired",
|