@fenelabs/fene-sdk 0.7.3 → 0.8.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 +16 -7
- package/dist/index.d.mts +15 -3
- package/dist/index.d.ts +15 -3
- package/dist/index.js +85 -56
- package/dist/index.mjs +85 -56
- package/package.json +3 -11
package/README.md
CHANGED
|
@@ -40,14 +40,22 @@ const client = createResonanceClient({
|
|
|
40
40
|
});
|
|
41
41
|
|
|
42
42
|
// 1. Get nonce
|
|
43
|
-
const { message } = await client.getNonce("0x...");
|
|
43
|
+
const { nonce, message } = await client.getNonce("0x...");
|
|
44
44
|
|
|
45
45
|
// 2. Sign message (using viem or wagmi)
|
|
46
46
|
const signature = await signMessage({ message });
|
|
47
47
|
|
|
48
|
-
// 3. Verify
|
|
49
|
-
const auth = await client.verify(
|
|
50
|
-
|
|
48
|
+
// 3. Verify signature (API sets secure httpOnly cookies)
|
|
49
|
+
const auth = await client.verify({
|
|
50
|
+
address: "0x...",
|
|
51
|
+
signature,
|
|
52
|
+
nonce,
|
|
53
|
+
});
|
|
54
|
+
console.log(`Session expires at: ${auth.expires_at}`);
|
|
55
|
+
|
|
56
|
+
// 4. Optional: fetch current session metadata
|
|
57
|
+
const session = await client.getSession();
|
|
58
|
+
console.log(`Logged in as: ${session.role}`);
|
|
51
59
|
|
|
52
60
|
// Now authenticated requests work
|
|
53
61
|
await client.updateGeoLocation({ latitude: 0, longitude: 0, node_type: "rpc" });
|
|
@@ -120,16 +128,17 @@ The SDK uses a standard Web3 authentication flow:
|
|
|
120
128
|
1. Request a nonce from the API (`getNonce`)
|
|
121
129
|
2. Sign the message with your wallet (using viem, wagmi, etc.)
|
|
122
130
|
3. Submit signature for verification (`verify`)
|
|
123
|
-
4.
|
|
131
|
+
4. API stores auth in secure httpOnly cookies
|
|
124
132
|
|
|
125
|
-
The
|
|
133
|
+
The SDK sends credentials automatically (`credentials: include`) and handles refresh flow for 401 responses.
|
|
126
134
|
|
|
127
135
|
## API Reference
|
|
128
136
|
|
|
129
137
|
### Auth
|
|
130
138
|
|
|
131
139
|
- `getNonce(address)` - Get authentication nonce
|
|
132
|
-
- `verify(address, signature)` - Verify signature and
|
|
140
|
+
- `verify({ address, signature, nonce })` - Verify signature and establish cookie-based session
|
|
141
|
+
- `getSession()` - Get current session metadata
|
|
133
142
|
|
|
134
143
|
### Validators
|
|
135
144
|
|
package/dist/index.d.mts
CHANGED
|
@@ -18,13 +18,21 @@ interface AuthVerifyRequest {
|
|
|
18
18
|
nonce: string;
|
|
19
19
|
}
|
|
20
20
|
interface AuthResponse {
|
|
21
|
-
|
|
22
|
-
|
|
21
|
+
success: boolean;
|
|
22
|
+
address: string;
|
|
23
23
|
expires_at: number;
|
|
24
24
|
}
|
|
25
25
|
interface RefreshResponse {
|
|
26
26
|
success: boolean;
|
|
27
27
|
expires_at?: number;
|
|
28
|
+
error?: string;
|
|
29
|
+
requires_login?: boolean;
|
|
30
|
+
}
|
|
31
|
+
interface SessionResponse {
|
|
32
|
+
authenticated: boolean;
|
|
33
|
+
address: string;
|
|
34
|
+
role: "validator" | "delegator";
|
|
35
|
+
expires_at: number;
|
|
28
36
|
}
|
|
29
37
|
declare enum ValidatorStatus {
|
|
30
38
|
NOT_EXIST = 0,
|
|
@@ -298,6 +306,10 @@ declare class ResonanceClient {
|
|
|
298
306
|
private executeRequest;
|
|
299
307
|
getNonce(address: Address): Promise<NonceResponse>;
|
|
300
308
|
verify(params: AuthVerifyRequest): Promise<AuthResponse>;
|
|
309
|
+
/**
|
|
310
|
+
* Get current authenticated session metadata from server.
|
|
311
|
+
*/
|
|
312
|
+
getSession(): Promise<SessionResponse>;
|
|
301
313
|
/**
|
|
302
314
|
* Refresh the access token using the refresh token cookie.
|
|
303
315
|
* Returns { success: boolean, expires_at?: number }
|
|
@@ -446,4 +458,4 @@ declare class EventBus {
|
|
|
446
458
|
}
|
|
447
459
|
declare const eventBus: EventBus;
|
|
448
460
|
|
|
449
|
-
export { type APIErrorResponse, type Address, type AuthResponse, type AuthVerifyRequest, type AvatarResponse, type DailyBlockStats, type Delegator, type DelegatorRewards, type DelegatorStake, DelegatorStatus, type EpochInfo, ErrorCode, type ErrorCodeType, type GeoNode, type GeoStats, type GeoUpdateRequest, type NetworkAPR, type NetworkStats, type NonceResponse, type PaginatedResponse, type PriceResponse, type ReferralKey, type RefreshResponse, type RequestContext, ResonanceClient, type ResonanceClientConfig, ResonanceError, type ResponseContext, type RewardHistory, type Transaction, type TransactionStats, type TransactionsList, type TransactionsParams, type UploadResponse, type Validator, type ValidatorAPR, ValidatorStatus, type ValidatorSummary, type WhitelistCheckRequest, type WhitelistCheckResponse, createResonanceClient, eventBus, hasErrorCode, isAuthError, isNetworkError, isNotFoundError, isResonanceError };
|
|
461
|
+
export { type APIErrorResponse, type Address, type AuthResponse, type AuthVerifyRequest, type AvatarResponse, type DailyBlockStats, type Delegator, type DelegatorRewards, type DelegatorStake, DelegatorStatus, type EpochInfo, ErrorCode, type ErrorCodeType, type GeoNode, type GeoStats, type GeoUpdateRequest, type NetworkAPR, type NetworkStats, type NonceResponse, type PaginatedResponse, type PriceResponse, type ReferralKey, type RefreshResponse, type RequestContext, ResonanceClient, type ResonanceClientConfig, ResonanceError, type ResponseContext, type RewardHistory, type SessionResponse, type Transaction, type TransactionStats, type TransactionsList, type TransactionsParams, type UploadResponse, type Validator, type ValidatorAPR, ValidatorStatus, type ValidatorSummary, type WhitelistCheckRequest, type WhitelistCheckResponse, createResonanceClient, eventBus, hasErrorCode, isAuthError, isNetworkError, isNotFoundError, isResonanceError };
|
package/dist/index.d.ts
CHANGED
|
@@ -18,13 +18,21 @@ interface AuthVerifyRequest {
|
|
|
18
18
|
nonce: string;
|
|
19
19
|
}
|
|
20
20
|
interface AuthResponse {
|
|
21
|
-
|
|
22
|
-
|
|
21
|
+
success: boolean;
|
|
22
|
+
address: string;
|
|
23
23
|
expires_at: number;
|
|
24
24
|
}
|
|
25
25
|
interface RefreshResponse {
|
|
26
26
|
success: boolean;
|
|
27
27
|
expires_at?: number;
|
|
28
|
+
error?: string;
|
|
29
|
+
requires_login?: boolean;
|
|
30
|
+
}
|
|
31
|
+
interface SessionResponse {
|
|
32
|
+
authenticated: boolean;
|
|
33
|
+
address: string;
|
|
34
|
+
role: "validator" | "delegator";
|
|
35
|
+
expires_at: number;
|
|
28
36
|
}
|
|
29
37
|
declare enum ValidatorStatus {
|
|
30
38
|
NOT_EXIST = 0,
|
|
@@ -298,6 +306,10 @@ declare class ResonanceClient {
|
|
|
298
306
|
private executeRequest;
|
|
299
307
|
getNonce(address: Address): Promise<NonceResponse>;
|
|
300
308
|
verify(params: AuthVerifyRequest): Promise<AuthResponse>;
|
|
309
|
+
/**
|
|
310
|
+
* Get current authenticated session metadata from server.
|
|
311
|
+
*/
|
|
312
|
+
getSession(): Promise<SessionResponse>;
|
|
301
313
|
/**
|
|
302
314
|
* Refresh the access token using the refresh token cookie.
|
|
303
315
|
* Returns { success: boolean, expires_at?: number }
|
|
@@ -446,4 +458,4 @@ declare class EventBus {
|
|
|
446
458
|
}
|
|
447
459
|
declare const eventBus: EventBus;
|
|
448
460
|
|
|
449
|
-
export { type APIErrorResponse, type Address, type AuthResponse, type AuthVerifyRequest, type AvatarResponse, type DailyBlockStats, type Delegator, type DelegatorRewards, type DelegatorStake, DelegatorStatus, type EpochInfo, ErrorCode, type ErrorCodeType, type GeoNode, type GeoStats, type GeoUpdateRequest, type NetworkAPR, type NetworkStats, type NonceResponse, type PaginatedResponse, type PriceResponse, type ReferralKey, type RefreshResponse, type RequestContext, ResonanceClient, type ResonanceClientConfig, ResonanceError, type ResponseContext, type RewardHistory, type Transaction, type TransactionStats, type TransactionsList, type TransactionsParams, type UploadResponse, type Validator, type ValidatorAPR, ValidatorStatus, type ValidatorSummary, type WhitelistCheckRequest, type WhitelistCheckResponse, createResonanceClient, eventBus, hasErrorCode, isAuthError, isNetworkError, isNotFoundError, isResonanceError };
|
|
461
|
+
export { type APIErrorResponse, type Address, type AuthResponse, type AuthVerifyRequest, type AvatarResponse, type DailyBlockStats, type Delegator, type DelegatorRewards, type DelegatorStake, DelegatorStatus, type EpochInfo, ErrorCode, type ErrorCodeType, type GeoNode, type GeoStats, type GeoUpdateRequest, type NetworkAPR, type NetworkStats, type NonceResponse, type PaginatedResponse, type PriceResponse, type ReferralKey, type RefreshResponse, type RequestContext, ResonanceClient, type ResonanceClientConfig, ResonanceError, type ResponseContext, type RewardHistory, type SessionResponse, type Transaction, type TransactionStats, type TransactionsList, type TransactionsParams, type UploadResponse, type Validator, type ValidatorAPR, ValidatorStatus, type ValidatorSummary, type WhitelistCheckRequest, type WhitelistCheckResponse, createResonanceClient, eventBus, hasErrorCode, isAuthError, isNetworkError, isNotFoundError, isResonanceError };
|
package/dist/index.js
CHANGED
|
@@ -215,7 +215,7 @@ var ResonanceClient = class {
|
|
|
215
215
|
if (attempt >= this.retries) {
|
|
216
216
|
throw error;
|
|
217
217
|
}
|
|
218
|
-
const delay = this.retryDelay *
|
|
218
|
+
const delay = this.retryDelay * 2 ** attempt;
|
|
219
219
|
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
220
220
|
}
|
|
221
221
|
}
|
|
@@ -239,28 +239,53 @@ var ResonanceClient = class {
|
|
|
239
239
|
timestamp: startTime,
|
|
240
240
|
attemptNumber
|
|
241
241
|
};
|
|
242
|
+
const reportError = (error) => {
|
|
243
|
+
try {
|
|
244
|
+
this.onError?.(error, requestContext);
|
|
245
|
+
} catch (hookError) {
|
|
246
|
+
console.error("onError hook error:", hookError);
|
|
247
|
+
}
|
|
248
|
+
};
|
|
242
249
|
try {
|
|
243
250
|
this.onRequest?.(requestContext);
|
|
244
251
|
} catch (hookError) {
|
|
245
252
|
console.error("onRequest hook error:", hookError);
|
|
246
253
|
}
|
|
247
254
|
const controller = new AbortController();
|
|
248
|
-
|
|
255
|
+
let timeoutId = null;
|
|
256
|
+
const timeoutPromise = new Promise((_, reject) => {
|
|
257
|
+
timeoutId = setTimeout(() => {
|
|
258
|
+
controller.abort();
|
|
259
|
+
reject(
|
|
260
|
+
new ResonanceError(
|
|
261
|
+
ErrorCode.TIMEOUT,
|
|
262
|
+
`Request timed out after ${this.timeout}ms`,
|
|
263
|
+
{ url, timeout: this.timeout },
|
|
264
|
+
408
|
|
265
|
+
)
|
|
266
|
+
);
|
|
267
|
+
}, this.timeout);
|
|
268
|
+
});
|
|
249
269
|
try {
|
|
250
|
-
const response = await
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
270
|
+
const response = await Promise.race([
|
|
271
|
+
fetch(url, {
|
|
272
|
+
...options,
|
|
273
|
+
headers,
|
|
274
|
+
credentials: "include",
|
|
275
|
+
// Send httpOnly cookies automatically
|
|
276
|
+
signal: controller.signal
|
|
277
|
+
}),
|
|
278
|
+
timeoutPromise
|
|
279
|
+
]);
|
|
280
|
+
if (timeoutId) {
|
|
281
|
+
clearTimeout(timeoutId);
|
|
282
|
+
}
|
|
258
283
|
const duration = Date.now() - startTime;
|
|
259
284
|
if (response.status === 401 && !this.isRefreshing) {
|
|
260
285
|
this.isRefreshing = true;
|
|
261
|
-
const
|
|
286
|
+
const refreshResult = await this.refresh();
|
|
262
287
|
this.isRefreshing = false;
|
|
263
|
-
if (
|
|
288
|
+
if (refreshResult.success) {
|
|
264
289
|
return this.executeRequest(path, options, attemptNumber);
|
|
265
290
|
}
|
|
266
291
|
eventBus.emit("api:unauthorized", {
|
|
@@ -271,22 +296,13 @@ var ResonanceClient = class {
|
|
|
271
296
|
}
|
|
272
297
|
if (!response.ok) {
|
|
273
298
|
const error = await response.json().catch(() => null);
|
|
274
|
-
const resonanceError = error && typeof error === "object" && "code" in error && "message" in error ? new ResonanceError(
|
|
275
|
-
error.code,
|
|
276
|
-
error.message,
|
|
277
|
-
error.details,
|
|
278
|
-
response.status
|
|
279
|
-
) : new ResonanceError(
|
|
299
|
+
const resonanceError = error && typeof error === "object" && "code" in error && "message" in error ? new ResonanceError(error.code, error.message, error.details, response.status) : new ResonanceError(
|
|
280
300
|
"INTERNAL_ERROR",
|
|
281
301
|
error?.error || `HTTP ${response.status}`,
|
|
282
302
|
void 0,
|
|
283
303
|
response.status
|
|
284
304
|
);
|
|
285
|
-
|
|
286
|
-
this.onError?.(resonanceError, requestContext);
|
|
287
|
-
} catch (hookError) {
|
|
288
|
-
console.error("onError hook error:", hookError);
|
|
289
|
-
}
|
|
305
|
+
reportError(resonanceError);
|
|
290
306
|
throw resonanceError;
|
|
291
307
|
}
|
|
292
308
|
const data = await response.json();
|
|
@@ -304,28 +320,38 @@ var ResonanceClient = class {
|
|
|
304
320
|
}
|
|
305
321
|
return data;
|
|
306
322
|
} catch (error) {
|
|
307
|
-
|
|
323
|
+
if (timeoutId) {
|
|
324
|
+
clearTimeout(timeoutId);
|
|
325
|
+
}
|
|
326
|
+
if (error instanceof ResonanceError) {
|
|
327
|
+
if (error.code === ErrorCode.TIMEOUT) {
|
|
328
|
+
reportError(error);
|
|
329
|
+
}
|
|
330
|
+
throw error;
|
|
331
|
+
}
|
|
308
332
|
if (error instanceof Error && error.name === "AbortError") {
|
|
309
333
|
const timeoutError = new ResonanceError(
|
|
310
|
-
|
|
334
|
+
ErrorCode.TIMEOUT,
|
|
311
335
|
`Request timed out after ${this.timeout}ms`,
|
|
312
336
|
{ url, timeout: this.timeout },
|
|
313
337
|
408
|
|
314
338
|
);
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
339
|
+
reportError(timeoutError);
|
|
340
|
+
throw timeoutError;
|
|
341
|
+
}
|
|
342
|
+
if (controller.signal.aborted) {
|
|
343
|
+
const timeoutError = new ResonanceError(
|
|
344
|
+
ErrorCode.TIMEOUT,
|
|
345
|
+
`Request timed out after ${this.timeout}ms`,
|
|
346
|
+
{ url, timeout: this.timeout },
|
|
347
|
+
408
|
|
348
|
+
);
|
|
349
|
+
reportError(timeoutError);
|
|
320
350
|
throw timeoutError;
|
|
321
351
|
}
|
|
322
352
|
if (error instanceof Error) {
|
|
323
|
-
const networkError =
|
|
324
|
-
|
|
325
|
-
this.onError?.(networkError, requestContext);
|
|
326
|
-
} catch (hookError) {
|
|
327
|
-
console.error("onError hook error:", hookError);
|
|
328
|
-
}
|
|
353
|
+
const networkError = new ResonanceError(ErrorCode.NETWORK_ERROR, error.message, void 0, 0);
|
|
354
|
+
reportError(networkError);
|
|
329
355
|
throw networkError;
|
|
330
356
|
}
|
|
331
357
|
throw error;
|
|
@@ -347,6 +373,12 @@ var ResonanceClient = class {
|
|
|
347
373
|
})
|
|
348
374
|
});
|
|
349
375
|
}
|
|
376
|
+
/**
|
|
377
|
+
* Get current authenticated session metadata from server.
|
|
378
|
+
*/
|
|
379
|
+
async getSession() {
|
|
380
|
+
return this.request("/eth/v1/auth/session");
|
|
381
|
+
}
|
|
350
382
|
/**
|
|
351
383
|
* Refresh the access token using the refresh token cookie.
|
|
352
384
|
* Returns { success: boolean, expires_at?: number }
|
|
@@ -360,13 +392,18 @@ var ResonanceClient = class {
|
|
|
360
392
|
headers: { "Content-Type": "application/json" }
|
|
361
393
|
});
|
|
362
394
|
if (response.ok) {
|
|
363
|
-
const
|
|
395
|
+
const data2 = await response.json();
|
|
364
396
|
return {
|
|
365
|
-
success:
|
|
366
|
-
expires_at:
|
|
397
|
+
success: data2.success === true,
|
|
398
|
+
expires_at: data2.expires_at
|
|
367
399
|
};
|
|
368
400
|
}
|
|
369
|
-
|
|
401
|
+
const data = await response.json().catch(() => ({}));
|
|
402
|
+
return {
|
|
403
|
+
success: false,
|
|
404
|
+
error: typeof data.error === "string" ? data.error : "refresh_failed",
|
|
405
|
+
requires_login: data.requires_login === true
|
|
406
|
+
};
|
|
370
407
|
} catch {
|
|
371
408
|
return { success: false };
|
|
372
409
|
}
|
|
@@ -436,7 +473,7 @@ var ResonanceClient = class {
|
|
|
436
473
|
return this.request(`/eth/v1/referral/key/${key}`);
|
|
437
474
|
}
|
|
438
475
|
async getValidatorKeys(address) {
|
|
439
|
-
return this.request(`/eth/v1/referral/validator/${address}`);
|
|
476
|
+
return this.request(`/eth/v1/referral/validator/${address}/keys`);
|
|
440
477
|
}
|
|
441
478
|
async checkWhitelist(data) {
|
|
442
479
|
return this.request("/eth/v1/referral/whitelist", {
|
|
@@ -469,23 +506,15 @@ var ResonanceClient = class {
|
|
|
469
506
|
async uploadUserAvatar(address, file) {
|
|
470
507
|
const formData = new FormData();
|
|
471
508
|
formData.append("file", file);
|
|
472
|
-
const response = await fetch(
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
body: formData
|
|
479
|
-
}
|
|
480
|
-
);
|
|
509
|
+
const response = await fetch(`${this.baseUrl}/eth/v1/user/${address}/avatar`, {
|
|
510
|
+
method: "POST",
|
|
511
|
+
credentials: "include",
|
|
512
|
+
// Send httpOnly cookies
|
|
513
|
+
body: formData
|
|
514
|
+
});
|
|
481
515
|
if (!response.ok) {
|
|
482
516
|
const error = await response.json().catch(() => ({ error: "Upload failed" }));
|
|
483
|
-
throw new ResonanceError(
|
|
484
|
-
"UPLOAD_FAILED",
|
|
485
|
-
error.error || "Failed to upload avatar",
|
|
486
|
-
void 0,
|
|
487
|
-
response.status
|
|
488
|
-
);
|
|
517
|
+
throw new ResonanceError("UPLOAD_FAILED", error.error || "Failed to upload avatar", void 0, response.status);
|
|
489
518
|
}
|
|
490
519
|
return response.json();
|
|
491
520
|
}
|
package/dist/index.mjs
CHANGED
|
@@ -179,7 +179,7 @@ var ResonanceClient = class {
|
|
|
179
179
|
if (attempt >= this.retries) {
|
|
180
180
|
throw error;
|
|
181
181
|
}
|
|
182
|
-
const delay = this.retryDelay *
|
|
182
|
+
const delay = this.retryDelay * 2 ** attempt;
|
|
183
183
|
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
184
184
|
}
|
|
185
185
|
}
|
|
@@ -203,28 +203,53 @@ var ResonanceClient = class {
|
|
|
203
203
|
timestamp: startTime,
|
|
204
204
|
attemptNumber
|
|
205
205
|
};
|
|
206
|
+
const reportError = (error) => {
|
|
207
|
+
try {
|
|
208
|
+
this.onError?.(error, requestContext);
|
|
209
|
+
} catch (hookError) {
|
|
210
|
+
console.error("onError hook error:", hookError);
|
|
211
|
+
}
|
|
212
|
+
};
|
|
206
213
|
try {
|
|
207
214
|
this.onRequest?.(requestContext);
|
|
208
215
|
} catch (hookError) {
|
|
209
216
|
console.error("onRequest hook error:", hookError);
|
|
210
217
|
}
|
|
211
218
|
const controller = new AbortController();
|
|
212
|
-
|
|
219
|
+
let timeoutId = null;
|
|
220
|
+
const timeoutPromise = new Promise((_, reject) => {
|
|
221
|
+
timeoutId = setTimeout(() => {
|
|
222
|
+
controller.abort();
|
|
223
|
+
reject(
|
|
224
|
+
new ResonanceError(
|
|
225
|
+
ErrorCode.TIMEOUT,
|
|
226
|
+
`Request timed out after ${this.timeout}ms`,
|
|
227
|
+
{ url, timeout: this.timeout },
|
|
228
|
+
408
|
|
229
|
+
)
|
|
230
|
+
);
|
|
231
|
+
}, this.timeout);
|
|
232
|
+
});
|
|
213
233
|
try {
|
|
214
|
-
const response = await
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
234
|
+
const response = await Promise.race([
|
|
235
|
+
fetch(url, {
|
|
236
|
+
...options,
|
|
237
|
+
headers,
|
|
238
|
+
credentials: "include",
|
|
239
|
+
// Send httpOnly cookies automatically
|
|
240
|
+
signal: controller.signal
|
|
241
|
+
}),
|
|
242
|
+
timeoutPromise
|
|
243
|
+
]);
|
|
244
|
+
if (timeoutId) {
|
|
245
|
+
clearTimeout(timeoutId);
|
|
246
|
+
}
|
|
222
247
|
const duration = Date.now() - startTime;
|
|
223
248
|
if (response.status === 401 && !this.isRefreshing) {
|
|
224
249
|
this.isRefreshing = true;
|
|
225
|
-
const
|
|
250
|
+
const refreshResult = await this.refresh();
|
|
226
251
|
this.isRefreshing = false;
|
|
227
|
-
if (
|
|
252
|
+
if (refreshResult.success) {
|
|
228
253
|
return this.executeRequest(path, options, attemptNumber);
|
|
229
254
|
}
|
|
230
255
|
eventBus.emit("api:unauthorized", {
|
|
@@ -235,22 +260,13 @@ var ResonanceClient = class {
|
|
|
235
260
|
}
|
|
236
261
|
if (!response.ok) {
|
|
237
262
|
const error = await response.json().catch(() => null);
|
|
238
|
-
const resonanceError = error && typeof error === "object" && "code" in error && "message" in error ? new ResonanceError(
|
|
239
|
-
error.code,
|
|
240
|
-
error.message,
|
|
241
|
-
error.details,
|
|
242
|
-
response.status
|
|
243
|
-
) : new ResonanceError(
|
|
263
|
+
const resonanceError = error && typeof error === "object" && "code" in error && "message" in error ? new ResonanceError(error.code, error.message, error.details, response.status) : new ResonanceError(
|
|
244
264
|
"INTERNAL_ERROR",
|
|
245
265
|
error?.error || `HTTP ${response.status}`,
|
|
246
266
|
void 0,
|
|
247
267
|
response.status
|
|
248
268
|
);
|
|
249
|
-
|
|
250
|
-
this.onError?.(resonanceError, requestContext);
|
|
251
|
-
} catch (hookError) {
|
|
252
|
-
console.error("onError hook error:", hookError);
|
|
253
|
-
}
|
|
269
|
+
reportError(resonanceError);
|
|
254
270
|
throw resonanceError;
|
|
255
271
|
}
|
|
256
272
|
const data = await response.json();
|
|
@@ -268,28 +284,38 @@ var ResonanceClient = class {
|
|
|
268
284
|
}
|
|
269
285
|
return data;
|
|
270
286
|
} catch (error) {
|
|
271
|
-
|
|
287
|
+
if (timeoutId) {
|
|
288
|
+
clearTimeout(timeoutId);
|
|
289
|
+
}
|
|
290
|
+
if (error instanceof ResonanceError) {
|
|
291
|
+
if (error.code === ErrorCode.TIMEOUT) {
|
|
292
|
+
reportError(error);
|
|
293
|
+
}
|
|
294
|
+
throw error;
|
|
295
|
+
}
|
|
272
296
|
if (error instanceof Error && error.name === "AbortError") {
|
|
273
297
|
const timeoutError = new ResonanceError(
|
|
274
|
-
|
|
298
|
+
ErrorCode.TIMEOUT,
|
|
275
299
|
`Request timed out after ${this.timeout}ms`,
|
|
276
300
|
{ url, timeout: this.timeout },
|
|
277
301
|
408
|
|
278
302
|
);
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
303
|
+
reportError(timeoutError);
|
|
304
|
+
throw timeoutError;
|
|
305
|
+
}
|
|
306
|
+
if (controller.signal.aborted) {
|
|
307
|
+
const timeoutError = new ResonanceError(
|
|
308
|
+
ErrorCode.TIMEOUT,
|
|
309
|
+
`Request timed out after ${this.timeout}ms`,
|
|
310
|
+
{ url, timeout: this.timeout },
|
|
311
|
+
408
|
|
312
|
+
);
|
|
313
|
+
reportError(timeoutError);
|
|
284
314
|
throw timeoutError;
|
|
285
315
|
}
|
|
286
316
|
if (error instanceof Error) {
|
|
287
|
-
const networkError =
|
|
288
|
-
|
|
289
|
-
this.onError?.(networkError, requestContext);
|
|
290
|
-
} catch (hookError) {
|
|
291
|
-
console.error("onError hook error:", hookError);
|
|
292
|
-
}
|
|
317
|
+
const networkError = new ResonanceError(ErrorCode.NETWORK_ERROR, error.message, void 0, 0);
|
|
318
|
+
reportError(networkError);
|
|
293
319
|
throw networkError;
|
|
294
320
|
}
|
|
295
321
|
throw error;
|
|
@@ -311,6 +337,12 @@ var ResonanceClient = class {
|
|
|
311
337
|
})
|
|
312
338
|
});
|
|
313
339
|
}
|
|
340
|
+
/**
|
|
341
|
+
* Get current authenticated session metadata from server.
|
|
342
|
+
*/
|
|
343
|
+
async getSession() {
|
|
344
|
+
return this.request("/eth/v1/auth/session");
|
|
345
|
+
}
|
|
314
346
|
/**
|
|
315
347
|
* Refresh the access token using the refresh token cookie.
|
|
316
348
|
* Returns { success: boolean, expires_at?: number }
|
|
@@ -324,13 +356,18 @@ var ResonanceClient = class {
|
|
|
324
356
|
headers: { "Content-Type": "application/json" }
|
|
325
357
|
});
|
|
326
358
|
if (response.ok) {
|
|
327
|
-
const
|
|
359
|
+
const data2 = await response.json();
|
|
328
360
|
return {
|
|
329
|
-
success:
|
|
330
|
-
expires_at:
|
|
361
|
+
success: data2.success === true,
|
|
362
|
+
expires_at: data2.expires_at
|
|
331
363
|
};
|
|
332
364
|
}
|
|
333
|
-
|
|
365
|
+
const data = await response.json().catch(() => ({}));
|
|
366
|
+
return {
|
|
367
|
+
success: false,
|
|
368
|
+
error: typeof data.error === "string" ? data.error : "refresh_failed",
|
|
369
|
+
requires_login: data.requires_login === true
|
|
370
|
+
};
|
|
334
371
|
} catch {
|
|
335
372
|
return { success: false };
|
|
336
373
|
}
|
|
@@ -400,7 +437,7 @@ var ResonanceClient = class {
|
|
|
400
437
|
return this.request(`/eth/v1/referral/key/${key}`);
|
|
401
438
|
}
|
|
402
439
|
async getValidatorKeys(address) {
|
|
403
|
-
return this.request(`/eth/v1/referral/validator/${address}`);
|
|
440
|
+
return this.request(`/eth/v1/referral/validator/${address}/keys`);
|
|
404
441
|
}
|
|
405
442
|
async checkWhitelist(data) {
|
|
406
443
|
return this.request("/eth/v1/referral/whitelist", {
|
|
@@ -433,23 +470,15 @@ var ResonanceClient = class {
|
|
|
433
470
|
async uploadUserAvatar(address, file) {
|
|
434
471
|
const formData = new FormData();
|
|
435
472
|
formData.append("file", file);
|
|
436
|
-
const response = await fetch(
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
body: formData
|
|
443
|
-
}
|
|
444
|
-
);
|
|
473
|
+
const response = await fetch(`${this.baseUrl}/eth/v1/user/${address}/avatar`, {
|
|
474
|
+
method: "POST",
|
|
475
|
+
credentials: "include",
|
|
476
|
+
// Send httpOnly cookies
|
|
477
|
+
body: formData
|
|
478
|
+
});
|
|
445
479
|
if (!response.ok) {
|
|
446
480
|
const error = await response.json().catch(() => ({ error: "Upload failed" }));
|
|
447
|
-
throw new ResonanceError(
|
|
448
|
-
"UPLOAD_FAILED",
|
|
449
|
-
error.error || "Failed to upload avatar",
|
|
450
|
-
void 0,
|
|
451
|
-
response.status
|
|
452
|
-
);
|
|
481
|
+
throw new ResonanceError("UPLOAD_FAILED", error.error || "Failed to upload avatar", void 0, response.status);
|
|
453
482
|
}
|
|
454
483
|
return response.json();
|
|
455
484
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fenelabs/fene-sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"description": "TypeScript SDK for Fene API",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/index.mjs",
|
|
@@ -12,9 +12,7 @@
|
|
|
12
12
|
"types": "./dist/index.d.ts"
|
|
13
13
|
}
|
|
14
14
|
},
|
|
15
|
-
"files": [
|
|
16
|
-
"dist"
|
|
17
|
-
],
|
|
15
|
+
"files": ["dist"],
|
|
18
16
|
"scripts": {
|
|
19
17
|
"build": "tsup src/index.ts --format cjs,esm --dts --clean",
|
|
20
18
|
"dev": "tsup src/index.ts --format cjs,esm --dts --watch",
|
|
@@ -23,13 +21,7 @@
|
|
|
23
21
|
"watch": "esbuild src/app.ts --bundle --sourcemap --outdir=www --servedir=www --watch",
|
|
24
22
|
"prepublishOnly": "pnpm run build"
|
|
25
23
|
},
|
|
26
|
-
"keywords": [
|
|
27
|
-
"resonance",
|
|
28
|
-
"api",
|
|
29
|
-
"sdk",
|
|
30
|
-
"blockchain",
|
|
31
|
-
"fene"
|
|
32
|
-
],
|
|
24
|
+
"keywords": ["resonance", "api", "sdk", "blockchain", "fene"],
|
|
33
25
|
"author": "Fene Labs",
|
|
34
26
|
"license": "MIT",
|
|
35
27
|
"repository": {
|