@webacy-xyz/sdk-core 1.0.1
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/LICENSE +21 -0
- package/README.md +96 -0
- package/dist/cjs/client-base.js +134 -0
- package/dist/cjs/client-base.js.map +1 -0
- package/dist/cjs/config.js +30 -0
- package/dist/cjs/config.js.map +1 -0
- package/dist/cjs/errors/authentication.js +35 -0
- package/dist/cjs/errors/authentication.js.map +1 -0
- package/dist/cjs/errors/base.js +76 -0
- package/dist/cjs/errors/base.js.map +1 -0
- package/dist/cjs/errors/index.js +16 -0
- package/dist/cjs/errors/index.js.map +1 -0
- package/dist/cjs/errors/network.js +45 -0
- package/dist/cjs/errors/network.js.map +1 -0
- package/dist/cjs/errors/not-found.js +38 -0
- package/dist/cjs/errors/not-found.js.map +1 -0
- package/dist/cjs/errors/rate-limit.js +53 -0
- package/dist/cjs/errors/rate-limit.js.map +1 -0
- package/dist/cjs/errors/validation.js +49 -0
- package/dist/cjs/errors/validation.js.map +1 -0
- package/dist/cjs/http/client.js +393 -0
- package/dist/cjs/http/client.js.map +1 -0
- package/dist/cjs/http/index.js +11 -0
- package/dist/cjs/http/index.js.map +1 -0
- package/dist/cjs/http/retry.js +43 -0
- package/dist/cjs/http/retry.js.map +1 -0
- package/dist/cjs/index.js +52 -0
- package/dist/cjs/index.js.map +1 -0
- package/dist/cjs/package.json +1 -0
- package/dist/cjs/resources/base.js +43 -0
- package/dist/cjs/resources/base.js.map +1 -0
- package/dist/cjs/resources/index.js +6 -0
- package/dist/cjs/resources/index.js.map +1 -0
- package/dist/cjs/types/chain.js +114 -0
- package/dist/cjs/types/chain.js.map +1 -0
- package/dist/cjs/types/common.js +49 -0
- package/dist/cjs/types/common.js.map +1 -0
- package/dist/cjs/types/index.js +18 -0
- package/dist/cjs/types/index.js.map +1 -0
- package/dist/cjs/types/modules.js +64 -0
- package/dist/cjs/types/modules.js.map +1 -0
- package/dist/cjs/utils/address-validation.js +124 -0
- package/dist/cjs/utils/address-validation.js.map +1 -0
- package/dist/cjs/utils/index.js +16 -0
- package/dist/cjs/utils/index.js.map +1 -0
- package/dist/cjs/utils/url-validation.js +31 -0
- package/dist/cjs/utils/url-validation.js.map +1 -0
- package/dist/esm/client-base.js +130 -0
- package/dist/esm/client-base.js.map +1 -0
- package/dist/esm/config.js +26 -0
- package/dist/esm/config.js.map +1 -0
- package/dist/esm/errors/authentication.js +31 -0
- package/dist/esm/errors/authentication.js.map +1 -0
- package/dist/esm/errors/base.js +72 -0
- package/dist/esm/errors/base.js.map +1 -0
- package/dist/esm/errors/index.js +7 -0
- package/dist/esm/errors/index.js.map +1 -0
- package/dist/esm/errors/network.js +41 -0
- package/dist/esm/errors/network.js.map +1 -0
- package/dist/esm/errors/not-found.js +34 -0
- package/dist/esm/errors/not-found.js.map +1 -0
- package/dist/esm/errors/rate-limit.js +49 -0
- package/dist/esm/errors/rate-limit.js.map +1 -0
- package/dist/esm/errors/validation.js +45 -0
- package/dist/esm/errors/validation.js.map +1 -0
- package/dist/esm/http/client.js +389 -0
- package/dist/esm/http/client.js.map +1 -0
- package/dist/esm/http/index.js +3 -0
- package/dist/esm/http/index.js.map +1 -0
- package/dist/esm/http/retry.js +37 -0
- package/dist/esm/http/retry.js.map +1 -0
- package/dist/esm/index.js +15 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/package.json +1 -0
- package/dist/esm/resources/base.js +39 -0
- package/dist/esm/resources/base.js.map +1 -0
- package/dist/esm/resources/index.js +2 -0
- package/dist/esm/resources/index.js.map +1 -0
- package/dist/esm/types/chain.js +109 -0
- package/dist/esm/types/chain.js.map +1 -0
- package/dist/esm/types/common.js +46 -0
- package/dist/esm/types/common.js.map +1 -0
- package/dist/esm/types/index.js +4 -0
- package/dist/esm/types/index.js.map +1 -0
- package/dist/esm/types/modules.js +61 -0
- package/dist/esm/types/modules.js.map +1 -0
- package/dist/esm/utils/address-validation.js +113 -0
- package/dist/esm/utils/address-validation.js.map +1 -0
- package/dist/esm/utils/index.js +3 -0
- package/dist/esm/utils/index.js.map +1 -0
- package/dist/esm/utils/url-validation.js +28 -0
- package/dist/esm/utils/url-validation.js.map +1 -0
- package/dist/types/client-base.d.ts +90 -0
- package/dist/types/client-base.d.ts.map +1 -0
- package/dist/types/config.d.ts +124 -0
- package/dist/types/config.d.ts.map +1 -0
- package/dist/types/errors/authentication.d.ts +24 -0
- package/dist/types/errors/authentication.d.ts.map +1 -0
- package/dist/types/errors/base.d.ts +53 -0
- package/dist/types/errors/base.d.ts.map +1 -0
- package/dist/types/errors/index.d.ts +7 -0
- package/dist/types/errors/index.d.ts.map +1 -0
- package/dist/types/errors/network.d.ts +30 -0
- package/dist/types/errors/network.d.ts.map +1 -0
- package/dist/types/errors/not-found.d.ts +27 -0
- package/dist/types/errors/not-found.d.ts.map +1 -0
- package/dist/types/errors/rate-limit.d.ts +37 -0
- package/dist/types/errors/rate-limit.d.ts.map +1 -0
- package/dist/types/errors/validation.d.ts +34 -0
- package/dist/types/errors/validation.d.ts.map +1 -0
- package/dist/types/http/client.d.ts +160 -0
- package/dist/types/http/client.d.ts.map +1 -0
- package/dist/types/http/index.d.ts +3 -0
- package/dist/types/http/index.d.ts.map +1 -0
- package/dist/types/http/retry.d.ts +32 -0
- package/dist/types/http/retry.d.ts.map +1 -0
- package/dist/types/index.d.ts +8 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/resources/base.d.ts +26 -0
- package/dist/types/resources/base.d.ts.map +1 -0
- package/dist/types/resources/index.d.ts +2 -0
- package/dist/types/resources/index.d.ts.map +1 -0
- package/dist/types/types/chain.d.ts +60 -0
- package/dist/types/types/chain.d.ts.map +1 -0
- package/dist/types/types/common.d.ts +251 -0
- package/dist/types/types/common.d.ts.map +1 -0
- package/dist/types/types/index.d.ts +5 -0
- package/dist/types/types/index.d.ts.map +1 -0
- package/dist/types/types/modules.d.ts +51 -0
- package/dist/types/types/modules.d.ts.map +1 -0
- package/dist/types/utils/address-validation.d.ts +50 -0
- package/dist/types/utils/address-validation.d.ts.map +1 -0
- package/dist/types/utils/index.d.ts +3 -0
- package/dist/types/utils/index.d.ts.map +1 -0
- package/dist/types/utils/url-validation.d.ts +16 -0
- package/dist/types/utils/url-validation.d.ts.map +1 -0
- package/package.json +64 -0
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Base error class for all Webacy SDK errors
|
|
3
|
+
*
|
|
4
|
+
* @example
|
|
5
|
+
* ```typescript
|
|
6
|
+
* try {
|
|
7
|
+
* await client.addresses.analyze(address, { chain: Chain.ETH });
|
|
8
|
+
* } catch (error) {
|
|
9
|
+
* if (error instanceof WebacyError) {
|
|
10
|
+
* console.error(`Error: ${error.message}`);
|
|
11
|
+
* console.error(`Code: ${error.code}`);
|
|
12
|
+
* if (error.endpoint) {
|
|
13
|
+
* console.error(`Endpoint: ${error.endpoint}`);
|
|
14
|
+
* }
|
|
15
|
+
* if (error.requestId) {
|
|
16
|
+
* console.error(`Request ID: ${error.requestId} (include this when contacting support)`);
|
|
17
|
+
* }
|
|
18
|
+
* }
|
|
19
|
+
* }
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
export class WebacyError extends Error {
|
|
23
|
+
/** HTTP status code if applicable */
|
|
24
|
+
status;
|
|
25
|
+
/** Error code for programmatic handling */
|
|
26
|
+
code;
|
|
27
|
+
/** Original error if wrapped */
|
|
28
|
+
cause;
|
|
29
|
+
/** Request ID for support inquiries */
|
|
30
|
+
requestId;
|
|
31
|
+
/** API endpoint that failed (for debugging) */
|
|
32
|
+
endpoint;
|
|
33
|
+
constructor(message, options) {
|
|
34
|
+
super(message);
|
|
35
|
+
this.name = 'WebacyError';
|
|
36
|
+
this.status = options.status;
|
|
37
|
+
this.code = options.code;
|
|
38
|
+
this.cause = options.cause;
|
|
39
|
+
this.requestId = options.requestId;
|
|
40
|
+
this.endpoint = options.endpoint;
|
|
41
|
+
// Maintain proper stack trace
|
|
42
|
+
if (Error.captureStackTrace) {
|
|
43
|
+
Error.captureStackTrace(this, this.constructor);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Check if this error is retryable
|
|
48
|
+
*/
|
|
49
|
+
isRetryable() {
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Convert to JSON for logging
|
|
54
|
+
*/
|
|
55
|
+
toJSON() {
|
|
56
|
+
return {
|
|
57
|
+
name: this.name,
|
|
58
|
+
message: this.message,
|
|
59
|
+
code: this.code,
|
|
60
|
+
status: this.status,
|
|
61
|
+
requestId: this.requestId,
|
|
62
|
+
endpoint: this.endpoint,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Get a user-friendly description of how to resolve this error
|
|
67
|
+
*/
|
|
68
|
+
getRecoverySuggestion() {
|
|
69
|
+
return undefined;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
//# sourceMappingURL=base.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"base.js","sourceRoot":"","sources":["../../../src/errors/base.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,MAAM,OAAO,WAAY,SAAQ,KAAK;IACpC,qCAAqC;IACrB,MAAM,CAAU;IAEhC,2CAA2C;IAC3B,IAAI,CAAS;IAE7B,gCAAgC;IAChB,KAAK,CAAS;IAE9B,uCAAuC;IACvB,SAAS,CAAU;IAEnC,+CAA+C;IAC/B,QAAQ,CAAU;IAElC,YACE,OAAe,EACf,OAMC;QAED,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,aAAa,CAAC;QAC1B,IAAI,CAAC,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;QAC7B,IAAI,CAAC,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;QACzB,IAAI,CAAC,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC;QAC3B,IAAI,CAAC,SAAS,GAAG,OAAO,CAAC,SAAS,CAAC;QACnC,IAAI,CAAC,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC;QAEjC,8BAA8B;QAC9B,IAAI,KAAK,CAAC,iBAAiB,EAAE,CAAC;YAC5B,KAAK,CAAC,iBAAiB,CAAC,IAAI,EAAE,IAAI,CAAC,WAAW,CAAC,CAAC;QAClD,CAAC;IACH,CAAC;IAED;;OAEG;IACH,WAAW;QACT,OAAO,KAAK,CAAC;IACf,CAAC;IAED;;OAEG;IACH,MAAM;QACJ,OAAO;YACL,IAAI,EAAE,IAAI,CAAC,IAAI;YACf,OAAO,EAAE,IAAI,CAAC,OAAO;YACrB,IAAI,EAAE,IAAI,CAAC,IAAI;YACf,MAAM,EAAE,IAAI,CAAC,MAAM;YACnB,SAAS,EAAE,IAAI,CAAC,SAAS;YACzB,QAAQ,EAAE,IAAI,CAAC,QAAQ;SACxB,CAAC;IACJ,CAAC;IAED;;OAEG;IACH,qBAAqB;QACnB,OAAO,SAAS,CAAC;IACnB,CAAC;CACF"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { WebacyError } from './base.js';
|
|
2
|
+
export { AuthenticationError } from './authentication.js';
|
|
3
|
+
export { RateLimitError } from './rate-limit.js';
|
|
4
|
+
export { ValidationError } from './validation.js';
|
|
5
|
+
export { NotFoundError } from './not-found.js';
|
|
6
|
+
export { NetworkError } from './network.js';
|
|
7
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/errors/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,QAAQ,CAAC;AACrC,OAAO,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AACvD,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAC9C,OAAO,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAC/C,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { WebacyError } from './base.js';
|
|
2
|
+
/**
|
|
3
|
+
* Thrown when a network error occurs
|
|
4
|
+
*
|
|
5
|
+
* This includes timeouts, connection failures, and other transport-level errors.
|
|
6
|
+
* The SDK automatically retries network errors with exponential backoff.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```typescript
|
|
10
|
+
* try {
|
|
11
|
+
* await client.addresses.analyze(address, { chain: Chain.ETH });
|
|
12
|
+
* } catch (error) {
|
|
13
|
+
* if (error instanceof NetworkError) {
|
|
14
|
+
* console.error('Network error:', error.message);
|
|
15
|
+
* if (error.cause) {
|
|
16
|
+
* console.error('Cause:', error.cause.message);
|
|
17
|
+
* }
|
|
18
|
+
* }
|
|
19
|
+
* }
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
export class NetworkError extends WebacyError {
|
|
23
|
+
constructor(message = 'Network request failed', options = {}) {
|
|
24
|
+
super(message, {
|
|
25
|
+
code: 'NETWORK_ERROR',
|
|
26
|
+
cause: options.cause,
|
|
27
|
+
endpoint: options.endpoint,
|
|
28
|
+
});
|
|
29
|
+
this.name = 'NetworkError';
|
|
30
|
+
}
|
|
31
|
+
isRetryable() {
|
|
32
|
+
return true;
|
|
33
|
+
}
|
|
34
|
+
getRecoverySuggestion() {
|
|
35
|
+
if (this.message.toLowerCase().includes('timed out')) {
|
|
36
|
+
return 'The request timed out. Try increasing the timeout option or check your network connection.';
|
|
37
|
+
}
|
|
38
|
+
return 'Check your network connection and try again. If the problem persists, the Webacy API may be temporarily unavailable.';
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
//# sourceMappingURL=network.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"network.js","sourceRoot":"","sources":["../../../src/errors/network.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,QAAQ,CAAC;AAErC;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,OAAO,YAAa,SAAQ,WAAW;IAC3C,YACE,OAAO,GAAG,wBAAwB,EAClC,UAAgD,EAAE;QAElD,KAAK,CAAC,OAAO,EAAE;YACb,IAAI,EAAE,eAAe;YACrB,KAAK,EAAE,OAAO,CAAC,KAAK;YACpB,QAAQ,EAAE,OAAO,CAAC,QAAQ;SAC3B,CAAC,CAAC;QACH,IAAI,CAAC,IAAI,GAAG,cAAc,CAAC;IAC7B,CAAC;IAEQ,WAAW;QAClB,OAAO,IAAI,CAAC;IACd,CAAC;IAEQ,qBAAqB;QAC5B,IAAI,IAAI,CAAC,OAAO,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC,WAAW,CAAC,EAAE,CAAC;YACrD,OAAO,4FAA4F,CAAC;QACtG,CAAC;QACD,OAAO,sHAAsH,CAAC;IAChI,CAAC;CACF"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { WebacyError } from './base.js';
|
|
2
|
+
/**
|
|
3
|
+
* Thrown when a resource is not found
|
|
4
|
+
*
|
|
5
|
+
* This typically means the address or resource doesn't exist on the specified chain,
|
|
6
|
+
* or hasn't been indexed yet.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```typescript
|
|
10
|
+
* try {
|
|
11
|
+
* await client.addresses.analyze(address, { chain: Chain.ETH });
|
|
12
|
+
* } catch (error) {
|
|
13
|
+
* if (error instanceof NotFoundError) {
|
|
14
|
+
* console.error('Resource not found:', error.message);
|
|
15
|
+
* // The address may not exist or may not have any activity
|
|
16
|
+
* }
|
|
17
|
+
* }
|
|
18
|
+
* ```
|
|
19
|
+
*/
|
|
20
|
+
export class NotFoundError extends WebacyError {
|
|
21
|
+
constructor(message = 'Resource not found', options = {}) {
|
|
22
|
+
super(message, {
|
|
23
|
+
status: 404,
|
|
24
|
+
code: 'NOT_FOUND_ERROR',
|
|
25
|
+
requestId: options.requestId,
|
|
26
|
+
endpoint: options.endpoint,
|
|
27
|
+
});
|
|
28
|
+
this.name = 'NotFoundError';
|
|
29
|
+
}
|
|
30
|
+
getRecoverySuggestion() {
|
|
31
|
+
return 'Verify the address exists and has activity on the specified chain. For new addresses, data may take a few minutes to become available.';
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
//# sourceMappingURL=not-found.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"not-found.js","sourceRoot":"","sources":["../../../src/errors/not-found.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,QAAQ,CAAC;AAErC;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,OAAO,aAAc,SAAQ,WAAW;IAC5C,YACE,OAAO,GAAG,oBAAoB,EAC9B,UAAqD,EAAE;QAEvD,KAAK,CAAC,OAAO,EAAE;YACb,MAAM,EAAE,GAAG;YACX,IAAI,EAAE,iBAAiB;YACvB,SAAS,EAAE,OAAO,CAAC,SAAS;YAC5B,QAAQ,EAAE,OAAO,CAAC,QAAQ;SAC3B,CAAC,CAAC;QACH,IAAI,CAAC,IAAI,GAAG,eAAe,CAAC;IAC9B,CAAC;IAEQ,qBAAqB;QAC5B,OAAO,wIAAwI,CAAC;IAClJ,CAAC;CACF"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { WebacyError } from './base.js';
|
|
2
|
+
/**
|
|
3
|
+
* Thrown when rate limit is exceeded
|
|
4
|
+
*
|
|
5
|
+
* The SDK automatically retries rate-limited requests with exponential backoff.
|
|
6
|
+
* If you're seeing this error frequently, consider reducing request frequency
|
|
7
|
+
* or upgrading your API plan.
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* ```typescript
|
|
11
|
+
* try {
|
|
12
|
+
* await client.addresses.analyze(address, { chain: Chain.ETH });
|
|
13
|
+
* } catch (error) {
|
|
14
|
+
* if (error instanceof RateLimitError) {
|
|
15
|
+
* console.error('Rate limited:', error.message);
|
|
16
|
+
* if (error.retryAfter) {
|
|
17
|
+
* console.log(`Retry after ${error.retryAfter} seconds`);
|
|
18
|
+
* }
|
|
19
|
+
* }
|
|
20
|
+
* }
|
|
21
|
+
* ```
|
|
22
|
+
*/
|
|
23
|
+
export class RateLimitError extends WebacyError {
|
|
24
|
+
/** When the rate limit resets (Unix timestamp) */
|
|
25
|
+
resetAt;
|
|
26
|
+
/** Number of seconds until reset */
|
|
27
|
+
retryAfter;
|
|
28
|
+
constructor(message = 'Rate limit exceeded', options = {}) {
|
|
29
|
+
super(message, {
|
|
30
|
+
status: 429,
|
|
31
|
+
code: 'RATE_LIMIT_ERROR',
|
|
32
|
+
requestId: options.requestId,
|
|
33
|
+
endpoint: options.endpoint,
|
|
34
|
+
});
|
|
35
|
+
this.name = 'RateLimitError';
|
|
36
|
+
this.resetAt = options.resetAt;
|
|
37
|
+
this.retryAfter = options.retryAfter;
|
|
38
|
+
}
|
|
39
|
+
isRetryable() {
|
|
40
|
+
return true;
|
|
41
|
+
}
|
|
42
|
+
getRecoverySuggestion() {
|
|
43
|
+
if (this.retryAfter) {
|
|
44
|
+
return `Wait ${this.retryAfter} seconds before retrying. Consider implementing request throttling or upgrading your API plan for higher limits.`;
|
|
45
|
+
}
|
|
46
|
+
return 'Wait a moment before retrying. Consider implementing request throttling or upgrading your API plan for higher limits.';
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
//# sourceMappingURL=rate-limit.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rate-limit.js","sourceRoot":"","sources":["../../../src/errors/rate-limit.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,QAAQ,CAAC;AAErC;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,MAAM,OAAO,cAAe,SAAQ,WAAW;IAC7C,kDAAkD;IAClC,OAAO,CAAU;IAEjC,oCAAoC;IACpB,UAAU,CAAU;IAEpC,YACE,OAAO,GAAG,qBAAqB,EAC/B,UAA4F,EAAE;QAE9F,KAAK,CAAC,OAAO,EAAE;YACb,MAAM,EAAE,GAAG;YACX,IAAI,EAAE,kBAAkB;YACxB,SAAS,EAAE,OAAO,CAAC,SAAS;YAC5B,QAAQ,EAAE,OAAO,CAAC,QAAQ;SAC3B,CAAC,CAAC;QACH,IAAI,CAAC,IAAI,GAAG,gBAAgB,CAAC;QAC7B,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC;QAC/B,IAAI,CAAC,UAAU,GAAG,OAAO,CAAC,UAAU,CAAC;IACvC,CAAC;IAEQ,WAAW;QAClB,OAAO,IAAI,CAAC;IACd,CAAC;IAEQ,qBAAqB;QAC5B,IAAI,IAAI,CAAC,UAAU,EAAE,CAAC;YACpB,OAAO,QAAQ,IAAI,CAAC,UAAU,kHAAkH,CAAC;QACnJ,CAAC;QACD,OAAO,uHAAuH,CAAC;IACjI,CAAC;CACF"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { WebacyError } from './base.js';
|
|
2
|
+
/**
|
|
3
|
+
* Thrown when request validation fails
|
|
4
|
+
*
|
|
5
|
+
* This error occurs when the provided input doesn't meet the API requirements.
|
|
6
|
+
* Common causes include invalid addresses, unsupported chains, or missing parameters.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```typescript
|
|
10
|
+
* try {
|
|
11
|
+
* await client.addresses.analyze(address, { chain: Chain.ETH });
|
|
12
|
+
* } catch (error) {
|
|
13
|
+
* if (error instanceof ValidationError) {
|
|
14
|
+
* console.error('Validation failed:', error.message);
|
|
15
|
+
* if (error.errors) {
|
|
16
|
+
* for (const [field, messages] of Object.entries(error.errors)) {
|
|
17
|
+
* console.error(` ${field}: ${messages.join(', ')}`);
|
|
18
|
+
* }
|
|
19
|
+
* }
|
|
20
|
+
* }
|
|
21
|
+
* }
|
|
22
|
+
* ```
|
|
23
|
+
*/
|
|
24
|
+
export class ValidationError extends WebacyError {
|
|
25
|
+
/** Field-level validation errors */
|
|
26
|
+
errors;
|
|
27
|
+
constructor(message = 'Validation failed', options = {}) {
|
|
28
|
+
super(message, {
|
|
29
|
+
status: 400,
|
|
30
|
+
code: 'VALIDATION_ERROR',
|
|
31
|
+
requestId: options.requestId,
|
|
32
|
+
endpoint: options.endpoint,
|
|
33
|
+
});
|
|
34
|
+
this.name = 'ValidationError';
|
|
35
|
+
this.errors = options.errors;
|
|
36
|
+
}
|
|
37
|
+
getRecoverySuggestion() {
|
|
38
|
+
if (this.errors && Object.keys(this.errors).length > 0) {
|
|
39
|
+
const fields = Object.keys(this.errors).join(', ');
|
|
40
|
+
return `Check the following fields: ${fields}. Ensure address formats match the specified blockchain and all required parameters are provided.`;
|
|
41
|
+
}
|
|
42
|
+
return 'Check your input parameters. Ensure address formats match the specified blockchain (e.g., 0x... for EVM chains, base58 for Solana).';
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
//# sourceMappingURL=validation.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validation.js","sourceRoot":"","sources":["../../../src/errors/validation.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,QAAQ,CAAC;AAErC;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,MAAM,OAAO,eAAgB,SAAQ,WAAW;IAC9C,oCAAoC;IACpB,MAAM,CAA4B;IAElD,YACE,OAAO,GAAG,mBAAmB,EAC7B,UAAwF,EAAE;QAE1F,KAAK,CAAC,OAAO,EAAE;YACb,MAAM,EAAE,GAAG;YACX,IAAI,EAAE,kBAAkB;YACxB,SAAS,EAAE,OAAO,CAAC,SAAS;YAC5B,QAAQ,EAAE,OAAO,CAAC,QAAQ;SAC3B,CAAC,CAAC;QACH,IAAI,CAAC,IAAI,GAAG,iBAAiB,CAAC;QAC9B,IAAI,CAAC,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAC/B,CAAC;IAEQ,qBAAqB;QAC5B,IAAI,IAAI,CAAC,MAAM,IAAI,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACvD,MAAM,MAAM,GAAG,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACnD,OAAO,+BAA+B,MAAM,mGAAmG,CAAC;QAClJ,CAAC;QACD,OAAO,qIAAqI,CAAC;IAC/I,CAAC;CACF"}
|
|
@@ -0,0 +1,389 @@
|
|
|
1
|
+
import { WebacyError, AuthenticationError, RateLimitError, ValidationError, NotFoundError, NetworkError, } from '../errors/index.js';
|
|
2
|
+
import { DEFAULT_RETRY_CONFIG, calculateRetryDelay, isRetryableStatusCode, sleep, } from './retry.js';
|
|
3
|
+
import { defaultLogger } from '../config.js';
|
|
4
|
+
/**
|
|
5
|
+
* HTTP client with retry support and interceptors
|
|
6
|
+
*/
|
|
7
|
+
export class HttpClient {
|
|
8
|
+
baseUrl;
|
|
9
|
+
defaultHeaders;
|
|
10
|
+
defaultTimeout;
|
|
11
|
+
retryConfig;
|
|
12
|
+
requestInterceptors = [];
|
|
13
|
+
responseInterceptors = [];
|
|
14
|
+
errorInterceptors = [];
|
|
15
|
+
debug;
|
|
16
|
+
logger;
|
|
17
|
+
constructor(config) {
|
|
18
|
+
this.baseUrl = config.baseUrl.replace(/\/$/, ''); // Remove trailing slash
|
|
19
|
+
this.defaultHeaders = {
|
|
20
|
+
'Content-Type': 'application/json',
|
|
21
|
+
Accept: 'application/json',
|
|
22
|
+
...config.headers,
|
|
23
|
+
};
|
|
24
|
+
this.defaultTimeout = config.timeout ?? 30000;
|
|
25
|
+
this.retryConfig = { ...DEFAULT_RETRY_CONFIG, ...config.retry };
|
|
26
|
+
this.debug = config.debug ?? false;
|
|
27
|
+
this.logger = config.logger ?? defaultLogger;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Check if request logging is enabled
|
|
31
|
+
*/
|
|
32
|
+
shouldLogRequests() {
|
|
33
|
+
return this.debug === true || this.debug === 'all' || this.debug === 'requests';
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Check if response logging is enabled
|
|
37
|
+
*/
|
|
38
|
+
shouldLogResponses() {
|
|
39
|
+
return this.debug === true || this.debug === 'all' || this.debug === 'responses';
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Check if error logging is enabled
|
|
43
|
+
*/
|
|
44
|
+
shouldLogErrors() {
|
|
45
|
+
return this.debug === true || this.debug === 'all' || this.debug === 'errors';
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Log a request
|
|
49
|
+
*/
|
|
50
|
+
logRequest(method, url, body) {
|
|
51
|
+
if (this.shouldLogRequests()) {
|
|
52
|
+
this.logger.debug(`→ ${method} ${url}`, body ? { body: this.sanitizeBody(body) } : undefined);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Log a response
|
|
57
|
+
*/
|
|
58
|
+
logResponse(method, url, status, duration) {
|
|
59
|
+
if (this.shouldLogResponses()) {
|
|
60
|
+
this.logger.debug(`← ${method} ${url} ${status} (${duration}ms)`);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Log an error
|
|
65
|
+
*/
|
|
66
|
+
logError(method, url, error) {
|
|
67
|
+
if (this.shouldLogErrors()) {
|
|
68
|
+
this.logger.error(`✗ ${method} ${url} - ${error.code}: ${error.message}`, {
|
|
69
|
+
code: error.code,
|
|
70
|
+
status: error.status,
|
|
71
|
+
requestId: error.requestId,
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Sanitize request body for logging (remove sensitive data)
|
|
77
|
+
* Recursively processes nested objects and arrays
|
|
78
|
+
*/
|
|
79
|
+
sanitizeBody(body) {
|
|
80
|
+
if (typeof body !== 'object' || body === null) {
|
|
81
|
+
return body;
|
|
82
|
+
}
|
|
83
|
+
// Handle arrays recursively
|
|
84
|
+
if (Array.isArray(body)) {
|
|
85
|
+
return body.map((item) => this.sanitizeBody(item));
|
|
86
|
+
}
|
|
87
|
+
// Remove potentially sensitive fields from logs
|
|
88
|
+
const sanitized = {};
|
|
89
|
+
const sensitiveKeys = [
|
|
90
|
+
'apikey',
|
|
91
|
+
'api_key',
|
|
92
|
+
'secret',
|
|
93
|
+
'password',
|
|
94
|
+
'token',
|
|
95
|
+
'authorization',
|
|
96
|
+
'auth',
|
|
97
|
+
'credentials',
|
|
98
|
+
'bearer',
|
|
99
|
+
];
|
|
100
|
+
for (const [key, value] of Object.entries(body)) {
|
|
101
|
+
if (sensitiveKeys.includes(key.toLowerCase())) {
|
|
102
|
+
sanitized[key] = '[REDACTED]';
|
|
103
|
+
}
|
|
104
|
+
else if (typeof value === 'object' && value !== null) {
|
|
105
|
+
sanitized[key] = this.sanitizeBody(value);
|
|
106
|
+
}
|
|
107
|
+
else {
|
|
108
|
+
sanitized[key] = value;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
return sanitized;
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Add a request interceptor
|
|
115
|
+
*/
|
|
116
|
+
addRequestInterceptor(interceptor) {
|
|
117
|
+
this.requestInterceptors.push(interceptor);
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Add a response interceptor
|
|
121
|
+
*/
|
|
122
|
+
addResponseInterceptor(interceptor) {
|
|
123
|
+
this.responseInterceptors.push(interceptor);
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Add an error interceptor
|
|
127
|
+
*/
|
|
128
|
+
addErrorInterceptor(interceptor) {
|
|
129
|
+
this.errorInterceptors.push(interceptor);
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Make a GET request
|
|
133
|
+
*/
|
|
134
|
+
async get(path, config) {
|
|
135
|
+
return this.request(path, { ...config, method: 'GET' });
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Make a POST request
|
|
139
|
+
*/
|
|
140
|
+
async post(path, body, config) {
|
|
141
|
+
return this.request(path, { ...config, method: 'POST', body });
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Make a PUT request
|
|
145
|
+
*/
|
|
146
|
+
async put(path, body, config) {
|
|
147
|
+
return this.request(path, { ...config, method: 'PUT', body });
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Make a PATCH request
|
|
151
|
+
*/
|
|
152
|
+
async patch(path, body, config) {
|
|
153
|
+
return this.request(path, { ...config, method: 'PATCH', body });
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Make a DELETE request
|
|
157
|
+
*/
|
|
158
|
+
async delete(path, config) {
|
|
159
|
+
return this.request(path, { ...config, method: 'DELETE' });
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Make an HTTP request with retry support
|
|
163
|
+
*/
|
|
164
|
+
async request(path, config = {}) {
|
|
165
|
+
const url = `${this.baseUrl}${path.startsWith('/') ? path : `/${path}`}`;
|
|
166
|
+
// Apply request interceptors
|
|
167
|
+
let finalConfig = { ...config };
|
|
168
|
+
for (const interceptor of this.requestInterceptors) {
|
|
169
|
+
finalConfig = await interceptor(url, finalConfig);
|
|
170
|
+
}
|
|
171
|
+
let lastError;
|
|
172
|
+
let attempt = 0;
|
|
173
|
+
while (attempt <= this.retryConfig.maxRetries) {
|
|
174
|
+
try {
|
|
175
|
+
const response = await this.executeRequest(url, finalConfig);
|
|
176
|
+
// Apply response interceptors
|
|
177
|
+
let finalResponse = response;
|
|
178
|
+
for (const interceptor of this.responseInterceptors) {
|
|
179
|
+
finalResponse = (await interceptor(finalResponse));
|
|
180
|
+
}
|
|
181
|
+
return finalResponse;
|
|
182
|
+
}
|
|
183
|
+
catch (error) {
|
|
184
|
+
lastError = error instanceof WebacyError ? error : this.wrapError(error);
|
|
185
|
+
// Apply error interceptors
|
|
186
|
+
for (const interceptor of this.errorInterceptors) {
|
|
187
|
+
lastError = await interceptor(lastError);
|
|
188
|
+
}
|
|
189
|
+
// Check if we should retry
|
|
190
|
+
if (!lastError.isRetryable() || attempt >= this.retryConfig.maxRetries) {
|
|
191
|
+
throw lastError;
|
|
192
|
+
}
|
|
193
|
+
// Calculate retry delay
|
|
194
|
+
const retryAfter = lastError instanceof RateLimitError ? lastError.retryAfter : undefined;
|
|
195
|
+
const delay = calculateRetryDelay(attempt, this.retryConfig, retryAfter);
|
|
196
|
+
await sleep(delay);
|
|
197
|
+
attempt++;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
throw lastError ?? new NetworkError('Request failed after all retries');
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* Execute a single HTTP request
|
|
204
|
+
*/
|
|
205
|
+
async executeRequest(url, config) {
|
|
206
|
+
const method = config.method ?? 'GET';
|
|
207
|
+
const timeout = config.timeout ?? this.defaultTimeout;
|
|
208
|
+
const controller = new AbortController();
|
|
209
|
+
let didTimeout = false;
|
|
210
|
+
const timeoutId = setTimeout(() => {
|
|
211
|
+
didTimeout = true;
|
|
212
|
+
controller.abort();
|
|
213
|
+
}, timeout);
|
|
214
|
+
const startTime = Date.now();
|
|
215
|
+
// Log the outgoing request
|
|
216
|
+
this.logRequest(method, url, config.body);
|
|
217
|
+
// Combine abort signals if user provided one
|
|
218
|
+
const signal = config.signal
|
|
219
|
+
? this.combineAbortSignals(config.signal, controller.signal)
|
|
220
|
+
: controller.signal;
|
|
221
|
+
try {
|
|
222
|
+
const response = await fetch(url, {
|
|
223
|
+
method,
|
|
224
|
+
headers: {
|
|
225
|
+
...this.defaultHeaders,
|
|
226
|
+
...config.headers,
|
|
227
|
+
},
|
|
228
|
+
body: config.body ? JSON.stringify(config.body) : undefined,
|
|
229
|
+
signal,
|
|
230
|
+
});
|
|
231
|
+
clearTimeout(timeoutId);
|
|
232
|
+
const duration = Date.now() - startTime;
|
|
233
|
+
const requestId = response.headers.get('x-request-id') ?? undefined;
|
|
234
|
+
// Handle non-OK responses
|
|
235
|
+
if (!response.ok) {
|
|
236
|
+
const error = await this.createErrorFromResponse(response, requestId, url);
|
|
237
|
+
this.logError(method, url, error);
|
|
238
|
+
throw error;
|
|
239
|
+
}
|
|
240
|
+
// Log successful response
|
|
241
|
+
this.logResponse(method, url, response.status, duration);
|
|
242
|
+
// Parse response body
|
|
243
|
+
const contentType = response.headers.get('content-type');
|
|
244
|
+
let data;
|
|
245
|
+
if (contentType?.includes('application/json')) {
|
|
246
|
+
data = (await response.json());
|
|
247
|
+
}
|
|
248
|
+
else {
|
|
249
|
+
data = (await response.text());
|
|
250
|
+
}
|
|
251
|
+
return {
|
|
252
|
+
data,
|
|
253
|
+
status: response.status,
|
|
254
|
+
headers: response.headers,
|
|
255
|
+
requestId,
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
catch (error) {
|
|
259
|
+
clearTimeout(timeoutId);
|
|
260
|
+
if (error instanceof WebacyError) {
|
|
261
|
+
throw error;
|
|
262
|
+
}
|
|
263
|
+
// Handle fetch errors
|
|
264
|
+
let networkError;
|
|
265
|
+
if (error instanceof Error) {
|
|
266
|
+
if (error.name === 'AbortError') {
|
|
267
|
+
// Distinguish between timeout and user-cancelled requests
|
|
268
|
+
if (didTimeout) {
|
|
269
|
+
networkError = new NetworkError('Request timed out', { cause: error, endpoint: url });
|
|
270
|
+
}
|
|
271
|
+
else {
|
|
272
|
+
networkError = new NetworkError('Request was cancelled by the caller', {
|
|
273
|
+
cause: error,
|
|
274
|
+
endpoint: url,
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
else {
|
|
279
|
+
networkError = new NetworkError(error.message, { cause: error, endpoint: url });
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
else {
|
|
283
|
+
networkError = new NetworkError('An unknown error occurred', { endpoint: url });
|
|
284
|
+
}
|
|
285
|
+
this.logError(method, url, networkError);
|
|
286
|
+
throw networkError;
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
/**
|
|
290
|
+
* Create an appropriate error from an HTTP response
|
|
291
|
+
*/
|
|
292
|
+
async createErrorFromResponse(response, requestId, endpoint) {
|
|
293
|
+
let errorBody = {};
|
|
294
|
+
try {
|
|
295
|
+
errorBody = (await response.json());
|
|
296
|
+
}
|
|
297
|
+
catch {
|
|
298
|
+
// Ignore JSON parse errors
|
|
299
|
+
}
|
|
300
|
+
const message = errorBody.message ?? errorBody.error ?? response.statusText;
|
|
301
|
+
switch (response.status) {
|
|
302
|
+
case 401:
|
|
303
|
+
case 403:
|
|
304
|
+
return new AuthenticationError(message, { requestId, endpoint });
|
|
305
|
+
case 404:
|
|
306
|
+
return new NotFoundError(message, { requestId, endpoint });
|
|
307
|
+
case 429: {
|
|
308
|
+
const retryAfter = response.headers.get('retry-after');
|
|
309
|
+
const resetAt = response.headers.get('x-ratelimit-reset');
|
|
310
|
+
return new RateLimitError(message, {
|
|
311
|
+
retryAfter: this.parseRetryAfter(retryAfter),
|
|
312
|
+
resetAt: this.parseRetryAfter(resetAt),
|
|
313
|
+
requestId,
|
|
314
|
+
endpoint,
|
|
315
|
+
});
|
|
316
|
+
}
|
|
317
|
+
case 400:
|
|
318
|
+
return new ValidationError(message, {
|
|
319
|
+
errors: errorBody.errors,
|
|
320
|
+
requestId,
|
|
321
|
+
endpoint,
|
|
322
|
+
});
|
|
323
|
+
default:
|
|
324
|
+
if (isRetryableStatusCode(response.status, this.retryConfig)) {
|
|
325
|
+
return new NetworkError(message, {
|
|
326
|
+
cause: new Error(`HTTP ${response.status}`),
|
|
327
|
+
endpoint,
|
|
328
|
+
});
|
|
329
|
+
}
|
|
330
|
+
return new WebacyError(message, {
|
|
331
|
+
status: response.status,
|
|
332
|
+
code: 'API_ERROR',
|
|
333
|
+
requestId,
|
|
334
|
+
endpoint,
|
|
335
|
+
});
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
/**
|
|
339
|
+
* Wrap an unknown error in a WebacyError
|
|
340
|
+
*/
|
|
341
|
+
wrapError(error) {
|
|
342
|
+
if (error instanceof WebacyError) {
|
|
343
|
+
return error;
|
|
344
|
+
}
|
|
345
|
+
if (error instanceof Error) {
|
|
346
|
+
return new NetworkError(error.message, { cause: error });
|
|
347
|
+
}
|
|
348
|
+
return new NetworkError('An unknown error occurred');
|
|
349
|
+
}
|
|
350
|
+
/**
|
|
351
|
+
* Parse and validate Retry-After header value
|
|
352
|
+
*
|
|
353
|
+
* Handles edge cases:
|
|
354
|
+
* - NaN from non-numeric strings (e.g., HTTP-date format)
|
|
355
|
+
* - Negative values
|
|
356
|
+
* - Extremely large values (capped at 5 minutes)
|
|
357
|
+
*
|
|
358
|
+
* @param value - Raw header value
|
|
359
|
+
* @returns Validated retry delay in seconds, or undefined if invalid
|
|
360
|
+
*/
|
|
361
|
+
parseRetryAfter(value) {
|
|
362
|
+
if (!value)
|
|
363
|
+
return undefined;
|
|
364
|
+
const parsed = parseInt(value, 10);
|
|
365
|
+
// Handle NaN (e.g., from HTTP-date format like "Wed, 21 Oct 2015 07:28:00 GMT")
|
|
366
|
+
// and negative values
|
|
367
|
+
if (Number.isNaN(parsed) || parsed < 0) {
|
|
368
|
+
return undefined;
|
|
369
|
+
}
|
|
370
|
+
// Cap at 5 minutes to prevent excessive delays from malformed headers
|
|
371
|
+
const MAX_RETRY_AFTER_SECONDS = 300;
|
|
372
|
+
return Math.min(parsed, MAX_RETRY_AFTER_SECONDS);
|
|
373
|
+
}
|
|
374
|
+
/**
|
|
375
|
+
* Combine multiple abort signals
|
|
376
|
+
*/
|
|
377
|
+
combineAbortSignals(...signals) {
|
|
378
|
+
const controller = new AbortController();
|
|
379
|
+
for (const signal of signals) {
|
|
380
|
+
if (signal.aborted) {
|
|
381
|
+
controller.abort(signal.reason);
|
|
382
|
+
break;
|
|
383
|
+
}
|
|
384
|
+
signal.addEventListener('abort', () => controller.abort(signal.reason), { once: true });
|
|
385
|
+
}
|
|
386
|
+
return controller.signal;
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
//# sourceMappingURL=client.js.map
|