@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.
Files changed (137) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +96 -0
  3. package/dist/cjs/client-base.js +134 -0
  4. package/dist/cjs/client-base.js.map +1 -0
  5. package/dist/cjs/config.js +30 -0
  6. package/dist/cjs/config.js.map +1 -0
  7. package/dist/cjs/errors/authentication.js +35 -0
  8. package/dist/cjs/errors/authentication.js.map +1 -0
  9. package/dist/cjs/errors/base.js +76 -0
  10. package/dist/cjs/errors/base.js.map +1 -0
  11. package/dist/cjs/errors/index.js +16 -0
  12. package/dist/cjs/errors/index.js.map +1 -0
  13. package/dist/cjs/errors/network.js +45 -0
  14. package/dist/cjs/errors/network.js.map +1 -0
  15. package/dist/cjs/errors/not-found.js +38 -0
  16. package/dist/cjs/errors/not-found.js.map +1 -0
  17. package/dist/cjs/errors/rate-limit.js +53 -0
  18. package/dist/cjs/errors/rate-limit.js.map +1 -0
  19. package/dist/cjs/errors/validation.js +49 -0
  20. package/dist/cjs/errors/validation.js.map +1 -0
  21. package/dist/cjs/http/client.js +393 -0
  22. package/dist/cjs/http/client.js.map +1 -0
  23. package/dist/cjs/http/index.js +11 -0
  24. package/dist/cjs/http/index.js.map +1 -0
  25. package/dist/cjs/http/retry.js +43 -0
  26. package/dist/cjs/http/retry.js.map +1 -0
  27. package/dist/cjs/index.js +52 -0
  28. package/dist/cjs/index.js.map +1 -0
  29. package/dist/cjs/package.json +1 -0
  30. package/dist/cjs/resources/base.js +43 -0
  31. package/dist/cjs/resources/base.js.map +1 -0
  32. package/dist/cjs/resources/index.js +6 -0
  33. package/dist/cjs/resources/index.js.map +1 -0
  34. package/dist/cjs/types/chain.js +114 -0
  35. package/dist/cjs/types/chain.js.map +1 -0
  36. package/dist/cjs/types/common.js +49 -0
  37. package/dist/cjs/types/common.js.map +1 -0
  38. package/dist/cjs/types/index.js +18 -0
  39. package/dist/cjs/types/index.js.map +1 -0
  40. package/dist/cjs/types/modules.js +64 -0
  41. package/dist/cjs/types/modules.js.map +1 -0
  42. package/dist/cjs/utils/address-validation.js +124 -0
  43. package/dist/cjs/utils/address-validation.js.map +1 -0
  44. package/dist/cjs/utils/index.js +16 -0
  45. package/dist/cjs/utils/index.js.map +1 -0
  46. package/dist/cjs/utils/url-validation.js +31 -0
  47. package/dist/cjs/utils/url-validation.js.map +1 -0
  48. package/dist/esm/client-base.js +130 -0
  49. package/dist/esm/client-base.js.map +1 -0
  50. package/dist/esm/config.js +26 -0
  51. package/dist/esm/config.js.map +1 -0
  52. package/dist/esm/errors/authentication.js +31 -0
  53. package/dist/esm/errors/authentication.js.map +1 -0
  54. package/dist/esm/errors/base.js +72 -0
  55. package/dist/esm/errors/base.js.map +1 -0
  56. package/dist/esm/errors/index.js +7 -0
  57. package/dist/esm/errors/index.js.map +1 -0
  58. package/dist/esm/errors/network.js +41 -0
  59. package/dist/esm/errors/network.js.map +1 -0
  60. package/dist/esm/errors/not-found.js +34 -0
  61. package/dist/esm/errors/not-found.js.map +1 -0
  62. package/dist/esm/errors/rate-limit.js +49 -0
  63. package/dist/esm/errors/rate-limit.js.map +1 -0
  64. package/dist/esm/errors/validation.js +45 -0
  65. package/dist/esm/errors/validation.js.map +1 -0
  66. package/dist/esm/http/client.js +389 -0
  67. package/dist/esm/http/client.js.map +1 -0
  68. package/dist/esm/http/index.js +3 -0
  69. package/dist/esm/http/index.js.map +1 -0
  70. package/dist/esm/http/retry.js +37 -0
  71. package/dist/esm/http/retry.js.map +1 -0
  72. package/dist/esm/index.js +15 -0
  73. package/dist/esm/index.js.map +1 -0
  74. package/dist/esm/package.json +1 -0
  75. package/dist/esm/resources/base.js +39 -0
  76. package/dist/esm/resources/base.js.map +1 -0
  77. package/dist/esm/resources/index.js +2 -0
  78. package/dist/esm/resources/index.js.map +1 -0
  79. package/dist/esm/types/chain.js +109 -0
  80. package/dist/esm/types/chain.js.map +1 -0
  81. package/dist/esm/types/common.js +46 -0
  82. package/dist/esm/types/common.js.map +1 -0
  83. package/dist/esm/types/index.js +4 -0
  84. package/dist/esm/types/index.js.map +1 -0
  85. package/dist/esm/types/modules.js +61 -0
  86. package/dist/esm/types/modules.js.map +1 -0
  87. package/dist/esm/utils/address-validation.js +113 -0
  88. package/dist/esm/utils/address-validation.js.map +1 -0
  89. package/dist/esm/utils/index.js +3 -0
  90. package/dist/esm/utils/index.js.map +1 -0
  91. package/dist/esm/utils/url-validation.js +28 -0
  92. package/dist/esm/utils/url-validation.js.map +1 -0
  93. package/dist/types/client-base.d.ts +90 -0
  94. package/dist/types/client-base.d.ts.map +1 -0
  95. package/dist/types/config.d.ts +124 -0
  96. package/dist/types/config.d.ts.map +1 -0
  97. package/dist/types/errors/authentication.d.ts +24 -0
  98. package/dist/types/errors/authentication.d.ts.map +1 -0
  99. package/dist/types/errors/base.d.ts +53 -0
  100. package/dist/types/errors/base.d.ts.map +1 -0
  101. package/dist/types/errors/index.d.ts +7 -0
  102. package/dist/types/errors/index.d.ts.map +1 -0
  103. package/dist/types/errors/network.d.ts +30 -0
  104. package/dist/types/errors/network.d.ts.map +1 -0
  105. package/dist/types/errors/not-found.d.ts +27 -0
  106. package/dist/types/errors/not-found.d.ts.map +1 -0
  107. package/dist/types/errors/rate-limit.d.ts +37 -0
  108. package/dist/types/errors/rate-limit.d.ts.map +1 -0
  109. package/dist/types/errors/validation.d.ts +34 -0
  110. package/dist/types/errors/validation.d.ts.map +1 -0
  111. package/dist/types/http/client.d.ts +160 -0
  112. package/dist/types/http/client.d.ts.map +1 -0
  113. package/dist/types/http/index.d.ts +3 -0
  114. package/dist/types/http/index.d.ts.map +1 -0
  115. package/dist/types/http/retry.d.ts +32 -0
  116. package/dist/types/http/retry.d.ts.map +1 -0
  117. package/dist/types/index.d.ts +8 -0
  118. package/dist/types/index.d.ts.map +1 -0
  119. package/dist/types/resources/base.d.ts +26 -0
  120. package/dist/types/resources/base.d.ts.map +1 -0
  121. package/dist/types/resources/index.d.ts +2 -0
  122. package/dist/types/resources/index.d.ts.map +1 -0
  123. package/dist/types/types/chain.d.ts +60 -0
  124. package/dist/types/types/chain.d.ts.map +1 -0
  125. package/dist/types/types/common.d.ts +251 -0
  126. package/dist/types/types/common.d.ts.map +1 -0
  127. package/dist/types/types/index.d.ts +5 -0
  128. package/dist/types/types/index.d.ts.map +1 -0
  129. package/dist/types/types/modules.d.ts +51 -0
  130. package/dist/types/types/modules.d.ts.map +1 -0
  131. package/dist/types/utils/address-validation.d.ts +50 -0
  132. package/dist/types/utils/address-validation.d.ts.map +1 -0
  133. package/dist/types/utils/index.d.ts +3 -0
  134. package/dist/types/utils/index.d.ts.map +1 -0
  135. package/dist/types/utils/url-validation.d.ts +16 -0
  136. package/dist/types/utils/url-validation.d.ts.map +1 -0
  137. 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