@crosspost/sdk 0.1.8 → 0.1.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -4215,6 +4215,7 @@ var ApiErrorCode = /* @__PURE__ */ ((ApiErrorCode2) => {
4215
4215
  ApiErrorCode2["POST_DELETION_FAILED"] = "POST_DELETION_FAILED";
4216
4216
  ApiErrorCode2["POST_INTERACTION_FAILED"] = "POST_INTERACTION_FAILED";
4217
4217
  ApiErrorCode2["NETWORK_ERROR"] = "NETWORK_ERROR";
4218
+ ApiErrorCode2["INVALID_RESPONSE"] = "INVALID_RESPONSE";
4218
4219
  return ApiErrorCode2;
4219
4220
  })(ApiErrorCode || {});
4220
4221
  var ApiErrorCodeSchema = z.enum(Object.values(ApiErrorCode));
@@ -4294,7 +4295,11 @@ var errorCodeToStatusCode = {
4294
4295
  [
4295
4296
  "NETWORK_ERROR"
4296
4297
  /* NETWORK_ERROR */
4297
- ]: 503
4298
+ ]: 503,
4299
+ [
4300
+ "INVALID_RESPONSE"
4301
+ /* INVALID_RESPONSE */
4302
+ ]: 500
4298
4303
  };
4299
4304
  var ErrorDetailSchema = z.object({
4300
4305
  message: z.string().describe("Human-readable error message"),
@@ -4983,103 +4988,96 @@ async function makeRequest(method, path, options, data, query) {
4983
4988
  const context = {
4984
4989
  method,
4985
4990
  path,
4986
- url,
4987
- retries: options.retries
4991
+ url
4988
4992
  };
4989
- return apiWrapper(async () => {
4990
- let lastError = null;
4991
- for (let attempt = 0; attempt <= options.retries; attempt++) {
4992
- const controller = new AbortController();
4993
- const timeoutId = setTimeout(() => controller.abort(), options.timeout);
4993
+ const controller = new AbortController();
4994
+ const timeoutId = setTimeout(() => controller.abort(), options.timeout);
4995
+ try {
4996
+ const headers = {
4997
+ "Content-Type": "application/json",
4998
+ "Accept": "application/json"
4999
+ };
5000
+ if (method === "GET") {
5001
+ const nearAccount = options.nearAccount || options.nearAuthData?.account_id;
5002
+ if (!nearAccount) {
5003
+ throw new CrosspostError(
5004
+ "No NEAR account provided for GET request",
5005
+ ApiErrorCode.UNAUTHORIZED,
5006
+ 401
5007
+ );
5008
+ }
5009
+ headers["X-Near-Account"] = nearAccount;
5010
+ } else {
5011
+ if (!options.nearAuthData) {
5012
+ throw new CrosspostError(
5013
+ "NEAR authentication data required for non-GET request",
5014
+ ApiErrorCode.UNAUTHORIZED,
5015
+ 401
5016
+ );
5017
+ }
5018
+ headers["Authorization"] = `Bearer ${(0, import_near_sign_verify.createAuthToken)(options.nearAuthData)}`;
5019
+ }
5020
+ const requestOptions = {
5021
+ method,
5022
+ headers,
5023
+ body: method !== "GET" && data ? JSON.stringify(data) : void 0,
5024
+ signal: controller.signal
5025
+ };
5026
+ const response = await fetch(url, requestOptions);
5027
+ clearTimeout(timeoutId);
5028
+ let responseData;
5029
+ try {
5030
+ responseData = await response.json();
5031
+ } catch (jsonError) {
5032
+ let responseText;
4994
5033
  try {
4995
- const headers = {
4996
- "Content-Type": "application/json",
4997
- "Accept": "application/json"
4998
- };
4999
- if (method === "GET") {
5000
- const nearAccount = options.nearAccount || options.nearAuthData?.account_id;
5001
- if (!nearAccount) {
5002
- throw new CrosspostError(
5003
- "No NEAR account provided for GET request",
5004
- ApiErrorCode.UNAUTHORIZED,
5005
- 401
5006
- );
5007
- }
5008
- headers["X-Near-Account"] = nearAccount;
5009
- } else {
5010
- if (!options.nearAuthData) {
5011
- throw new CrosspostError(
5012
- "NEAR authentication data required for non-GET request",
5013
- ApiErrorCode.UNAUTHORIZED,
5014
- 401
5015
- );
5016
- }
5017
- headers["Authorization"] = `Bearer ${(0, import_near_sign_verify.createAuthToken)(options.nearAuthData)}`;
5018
- }
5019
- const requestOptions = {
5020
- method,
5021
- headers,
5022
- body: method !== "GET" && data ? JSON.stringify(data) : void 0,
5023
- signal: controller.signal
5024
- };
5025
- const response = await fetch(url, requestOptions);
5026
- clearTimeout(timeoutId);
5027
- let responseData;
5028
- try {
5029
- responseData = await response.json();
5030
- } catch (jsonError) {
5031
- if (!response.ok) {
5032
- throw new CrosspostError(
5033
- `API request failed with status ${response.status} and non-JSON response`,
5034
- ApiErrorCode.NETWORK_ERROR,
5035
- response.status,
5036
- { originalStatusText: response.statusText }
5037
- );
5038
- }
5039
- throw new CrosspostError(
5040
- `Failed to parse JSON response: ${jsonError instanceof Error ? jsonError.message : String(jsonError)}`,
5041
- ApiErrorCode.INTERNAL_ERROR,
5042
- response.status
5043
- );
5044
- }
5045
- if (!response.ok) {
5046
- lastError = handleErrorResponse(responseData, response.status);
5047
- const shouldRetry = lastError instanceof CrosspostError && lastError.code === ApiErrorCode.RATE_LIMITED;
5048
- if (shouldRetry && attempt < options.retries) {
5049
- await new Promise((resolve) => setTimeout(resolve, 1e3 * Math.pow(2, attempt)));
5050
- continue;
5051
- }
5052
- throw lastError;
5053
- }
5054
- if (responseData && typeof responseData === "object" && "success" in responseData) {
5055
- if (responseData.success) {
5056
- return responseData.data;
5057
- } else {
5058
- lastError = handleErrorResponse(responseData, response.status);
5059
- const shouldRetry = lastError instanceof CrosspostError && lastError.code === ApiErrorCode.RATE_LIMITED;
5060
- if (shouldRetry && attempt < options.retries) {
5061
- await new Promise((resolve) => setTimeout(resolve, 1e3 * Math.pow(2, attempt)));
5062
- continue;
5063
- }
5064
- throw lastError;
5065
- }
5066
- }
5067
- } catch (error) {
5068
- clearTimeout(timeoutId);
5069
- lastError = error instanceof Error ? error : new Error(String(error));
5070
- const isNetworkError2 = error instanceof TypeError || error instanceof DOMException && error.name === "AbortError";
5071
- if (isNetworkError2 && attempt < options.retries) {
5072
- await new Promise((resolve) => setTimeout(resolve, 1e3 * Math.pow(2, attempt)));
5073
- continue;
5074
- }
5075
- if (!(error instanceof CrosspostError)) {
5076
- throw createNetworkError(error, url, options.timeout);
5077
- }
5078
- throw error;
5034
+ responseText = await response.text();
5035
+ } catch (_) {
5079
5036
  }
5037
+ throw new CrosspostError(
5038
+ `API request failed with status ${response.status} and non-JSON response`,
5039
+ ApiErrorCode.INVALID_RESPONSE,
5040
+ response.status,
5041
+ {
5042
+ originalStatusText: response.statusText,
5043
+ originalError: jsonError instanceof Error ? jsonError.message : String(jsonError),
5044
+ responseText
5045
+ }
5046
+ );
5047
+ }
5048
+ if (!response.ok) {
5049
+ throw handleErrorResponse(responseData, response.status);
5080
5050
  }
5081
- throw lastError || new CrosspostError("Request failed after multiple retries", ApiErrorCode.INTERNAL_ERROR, 500);
5082
- }, context);
5051
+ if (!responseData || typeof responseData !== "object" || !("success" in responseData)) {
5052
+ throw new CrosspostError(
5053
+ "Invalid success response format from API",
5054
+ ApiErrorCode.INVALID_RESPONSE,
5055
+ response.status,
5056
+ { responseData }
5057
+ );
5058
+ }
5059
+ if (responseData.success) {
5060
+ return responseData.data;
5061
+ }
5062
+ throw handleErrorResponse(responseData, response.status);
5063
+ } catch (error) {
5064
+ clearTimeout(timeoutId);
5065
+ if (error instanceof CrosspostError) {
5066
+ throw enrichErrorWithContext(error, context);
5067
+ }
5068
+ if (error instanceof TypeError || error instanceof DOMException && error.name === "AbortError") {
5069
+ throw enrichErrorWithContext(createNetworkError(error, url, options.timeout), context);
5070
+ }
5071
+ throw enrichErrorWithContext(
5072
+ new CrosspostError(
5073
+ error instanceof Error ? error.message : String(error),
5074
+ ApiErrorCode.INTERNAL_ERROR,
5075
+ 500,
5076
+ { originalError: String(error) }
5077
+ ),
5078
+ context
5079
+ );
5080
+ }
5083
5081
  }
5084
5082
 
5085
5083
  // src/api/activity.ts
@@ -5415,8 +5413,7 @@ var SystemApi = class {
5415
5413
  // src/core/config.ts
5416
5414
  var DEFAULT_CONFIG = {
5417
5415
  baseUrl: "https://open-crosspost-proxy.deno.dev/",
5418
- timeout: 3e4,
5419
- retries: 2
5416
+ timeout: 3e4
5420
5417
  };
5421
5418
 
5422
5419
  // src/core/client.ts
@@ -5428,12 +5425,10 @@ var CrosspostClient = class {
5428
5425
  constructor(config = {}) {
5429
5426
  const baseUrl = config.baseUrl || DEFAULT_CONFIG.baseUrl;
5430
5427
  const timeout = config.timeout || DEFAULT_CONFIG.timeout;
5431
- const retries = config.retries ?? DEFAULT_CONFIG.retries;
5432
5428
  const nearAuthData = config.nearAuthData;
5433
5429
  this.options = {
5434
5430
  baseUrl,
5435
5431
  timeout,
5436
- retries,
5437
5432
  nearAuthData
5438
5433
  };
5439
5434
  this.auth = new AuthApi(this.options);
package/dist/index.d.cts CHANGED
@@ -24,10 +24,6 @@ interface RequestOptions {
24
24
  * Request timeout in milliseconds
25
25
  */
26
26
  timeout: number;
27
- /**
28
- * Number of retries for failed requests
29
- */
30
- retries: number;
31
27
  }
32
28
 
33
29
  /**
@@ -228,11 +224,6 @@ interface CrosspostClientConfig {
228
224
  * @default 30000
229
225
  */
230
226
  timeout?: number;
231
- /**
232
- * Number of retries for failed requests (specifically for network errors or 5xx status codes)
233
- * @default 2
234
- */
235
- retries?: number;
236
227
  }
237
228
 
238
229
  /**
package/dist/index.d.ts CHANGED
@@ -24,10 +24,6 @@ interface RequestOptions {
24
24
  * Request timeout in milliseconds
25
25
  */
26
26
  timeout: number;
27
- /**
28
- * Number of retries for failed requests
29
- */
30
- retries: number;
31
27
  }
32
28
 
33
29
  /**
@@ -228,11 +224,6 @@ interface CrosspostClientConfig {
228
224
  * @default 30000
229
225
  */
230
226
  timeout?: number;
231
- /**
232
- * Number of retries for failed requests (specifically for network errors or 5xx status codes)
233
- * @default 2
234
- */
235
- retries?: number;
236
227
  }
237
228
 
238
229
  /**
package/dist/index.js CHANGED
@@ -4095,6 +4095,7 @@ var ApiErrorCode = /* @__PURE__ */ ((ApiErrorCode2) => {
4095
4095
  ApiErrorCode2["POST_DELETION_FAILED"] = "POST_DELETION_FAILED";
4096
4096
  ApiErrorCode2["POST_INTERACTION_FAILED"] = "POST_INTERACTION_FAILED";
4097
4097
  ApiErrorCode2["NETWORK_ERROR"] = "NETWORK_ERROR";
4098
+ ApiErrorCode2["INVALID_RESPONSE"] = "INVALID_RESPONSE";
4098
4099
  return ApiErrorCode2;
4099
4100
  })(ApiErrorCode || {});
4100
4101
  var ApiErrorCodeSchema = z.enum(Object.values(ApiErrorCode));
@@ -4174,7 +4175,11 @@ var errorCodeToStatusCode = {
4174
4175
  [
4175
4176
  "NETWORK_ERROR"
4176
4177
  /* NETWORK_ERROR */
4177
- ]: 503
4178
+ ]: 503,
4179
+ [
4180
+ "INVALID_RESPONSE"
4181
+ /* INVALID_RESPONSE */
4182
+ ]: 500
4178
4183
  };
4179
4184
  var ErrorDetailSchema = z.object({
4180
4185
  message: z.string().describe("Human-readable error message"),
@@ -4863,103 +4868,96 @@ async function makeRequest(method, path, options, data, query) {
4863
4868
  const context = {
4864
4869
  method,
4865
4870
  path,
4866
- url,
4867
- retries: options.retries
4871
+ url
4868
4872
  };
4869
- return apiWrapper(async () => {
4870
- let lastError = null;
4871
- for (let attempt = 0; attempt <= options.retries; attempt++) {
4872
- const controller = new AbortController();
4873
- const timeoutId = setTimeout(() => controller.abort(), options.timeout);
4873
+ const controller = new AbortController();
4874
+ const timeoutId = setTimeout(() => controller.abort(), options.timeout);
4875
+ try {
4876
+ const headers = {
4877
+ "Content-Type": "application/json",
4878
+ "Accept": "application/json"
4879
+ };
4880
+ if (method === "GET") {
4881
+ const nearAccount = options.nearAccount || options.nearAuthData?.account_id;
4882
+ if (!nearAccount) {
4883
+ throw new CrosspostError(
4884
+ "No NEAR account provided for GET request",
4885
+ ApiErrorCode.UNAUTHORIZED,
4886
+ 401
4887
+ );
4888
+ }
4889
+ headers["X-Near-Account"] = nearAccount;
4890
+ } else {
4891
+ if (!options.nearAuthData) {
4892
+ throw new CrosspostError(
4893
+ "NEAR authentication data required for non-GET request",
4894
+ ApiErrorCode.UNAUTHORIZED,
4895
+ 401
4896
+ );
4897
+ }
4898
+ headers["Authorization"] = `Bearer ${createAuthToken(options.nearAuthData)}`;
4899
+ }
4900
+ const requestOptions = {
4901
+ method,
4902
+ headers,
4903
+ body: method !== "GET" && data ? JSON.stringify(data) : void 0,
4904
+ signal: controller.signal
4905
+ };
4906
+ const response = await fetch(url, requestOptions);
4907
+ clearTimeout(timeoutId);
4908
+ let responseData;
4909
+ try {
4910
+ responseData = await response.json();
4911
+ } catch (jsonError) {
4912
+ let responseText;
4874
4913
  try {
4875
- const headers = {
4876
- "Content-Type": "application/json",
4877
- "Accept": "application/json"
4878
- };
4879
- if (method === "GET") {
4880
- const nearAccount = options.nearAccount || options.nearAuthData?.account_id;
4881
- if (!nearAccount) {
4882
- throw new CrosspostError(
4883
- "No NEAR account provided for GET request",
4884
- ApiErrorCode.UNAUTHORIZED,
4885
- 401
4886
- );
4887
- }
4888
- headers["X-Near-Account"] = nearAccount;
4889
- } else {
4890
- if (!options.nearAuthData) {
4891
- throw new CrosspostError(
4892
- "NEAR authentication data required for non-GET request",
4893
- ApiErrorCode.UNAUTHORIZED,
4894
- 401
4895
- );
4896
- }
4897
- headers["Authorization"] = `Bearer ${createAuthToken(options.nearAuthData)}`;
4898
- }
4899
- const requestOptions = {
4900
- method,
4901
- headers,
4902
- body: method !== "GET" && data ? JSON.stringify(data) : void 0,
4903
- signal: controller.signal
4904
- };
4905
- const response = await fetch(url, requestOptions);
4906
- clearTimeout(timeoutId);
4907
- let responseData;
4908
- try {
4909
- responseData = await response.json();
4910
- } catch (jsonError) {
4911
- if (!response.ok) {
4912
- throw new CrosspostError(
4913
- `API request failed with status ${response.status} and non-JSON response`,
4914
- ApiErrorCode.NETWORK_ERROR,
4915
- response.status,
4916
- { originalStatusText: response.statusText }
4917
- );
4918
- }
4919
- throw new CrosspostError(
4920
- `Failed to parse JSON response: ${jsonError instanceof Error ? jsonError.message : String(jsonError)}`,
4921
- ApiErrorCode.INTERNAL_ERROR,
4922
- response.status
4923
- );
4924
- }
4925
- if (!response.ok) {
4926
- lastError = handleErrorResponse(responseData, response.status);
4927
- const shouldRetry = lastError instanceof CrosspostError && lastError.code === ApiErrorCode.RATE_LIMITED;
4928
- if (shouldRetry && attempt < options.retries) {
4929
- await new Promise((resolve) => setTimeout(resolve, 1e3 * Math.pow(2, attempt)));
4930
- continue;
4931
- }
4932
- throw lastError;
4933
- }
4934
- if (responseData && typeof responseData === "object" && "success" in responseData) {
4935
- if (responseData.success) {
4936
- return responseData.data;
4937
- } else {
4938
- lastError = handleErrorResponse(responseData, response.status);
4939
- const shouldRetry = lastError instanceof CrosspostError && lastError.code === ApiErrorCode.RATE_LIMITED;
4940
- if (shouldRetry && attempt < options.retries) {
4941
- await new Promise((resolve) => setTimeout(resolve, 1e3 * Math.pow(2, attempt)));
4942
- continue;
4943
- }
4944
- throw lastError;
4945
- }
4946
- }
4947
- } catch (error) {
4948
- clearTimeout(timeoutId);
4949
- lastError = error instanceof Error ? error : new Error(String(error));
4950
- const isNetworkError2 = error instanceof TypeError || error instanceof DOMException && error.name === "AbortError";
4951
- if (isNetworkError2 && attempt < options.retries) {
4952
- await new Promise((resolve) => setTimeout(resolve, 1e3 * Math.pow(2, attempt)));
4953
- continue;
4954
- }
4955
- if (!(error instanceof CrosspostError)) {
4956
- throw createNetworkError(error, url, options.timeout);
4957
- }
4958
- throw error;
4914
+ responseText = await response.text();
4915
+ } catch (_) {
4959
4916
  }
4917
+ throw new CrosspostError(
4918
+ `API request failed with status ${response.status} and non-JSON response`,
4919
+ ApiErrorCode.INVALID_RESPONSE,
4920
+ response.status,
4921
+ {
4922
+ originalStatusText: response.statusText,
4923
+ originalError: jsonError instanceof Error ? jsonError.message : String(jsonError),
4924
+ responseText
4925
+ }
4926
+ );
4927
+ }
4928
+ if (!response.ok) {
4929
+ throw handleErrorResponse(responseData, response.status);
4960
4930
  }
4961
- throw lastError || new CrosspostError("Request failed after multiple retries", ApiErrorCode.INTERNAL_ERROR, 500);
4962
- }, context);
4931
+ if (!responseData || typeof responseData !== "object" || !("success" in responseData)) {
4932
+ throw new CrosspostError(
4933
+ "Invalid success response format from API",
4934
+ ApiErrorCode.INVALID_RESPONSE,
4935
+ response.status,
4936
+ { responseData }
4937
+ );
4938
+ }
4939
+ if (responseData.success) {
4940
+ return responseData.data;
4941
+ }
4942
+ throw handleErrorResponse(responseData, response.status);
4943
+ } catch (error) {
4944
+ clearTimeout(timeoutId);
4945
+ if (error instanceof CrosspostError) {
4946
+ throw enrichErrorWithContext(error, context);
4947
+ }
4948
+ if (error instanceof TypeError || error instanceof DOMException && error.name === "AbortError") {
4949
+ throw enrichErrorWithContext(createNetworkError(error, url, options.timeout), context);
4950
+ }
4951
+ throw enrichErrorWithContext(
4952
+ new CrosspostError(
4953
+ error instanceof Error ? error.message : String(error),
4954
+ ApiErrorCode.INTERNAL_ERROR,
4955
+ 500,
4956
+ { originalError: String(error) }
4957
+ ),
4958
+ context
4959
+ );
4960
+ }
4963
4961
  }
4964
4962
 
4965
4963
  // src/api/activity.ts
@@ -5295,8 +5293,7 @@ var SystemApi = class {
5295
5293
  // src/core/config.ts
5296
5294
  var DEFAULT_CONFIG = {
5297
5295
  baseUrl: "https://open-crosspost-proxy.deno.dev/",
5298
- timeout: 3e4,
5299
- retries: 2
5296
+ timeout: 3e4
5300
5297
  };
5301
5298
 
5302
5299
  // src/core/client.ts
@@ -5308,12 +5305,10 @@ var CrosspostClient = class {
5308
5305
  constructor(config = {}) {
5309
5306
  const baseUrl = config.baseUrl || DEFAULT_CONFIG.baseUrl;
5310
5307
  const timeout = config.timeout || DEFAULT_CONFIG.timeout;
5311
- const retries = config.retries ?? DEFAULT_CONFIG.retries;
5312
5308
  const nearAuthData = config.nearAuthData;
5313
5309
  this.options = {
5314
5310
  baseUrl,
5315
5311
  timeout,
5316
- retries,
5317
5312
  nearAuthData
5318
5313
  };
5319
5314
  this.auth = new AuthApi(this.options);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@crosspost/sdk",
3
- "version": "0.1.8",
3
+ "version": "0.1.9",
4
4
  "description": "SDK for interacting with the Crosspost API",
5
5
  "type": "module",
6
6
  "main": "dist/index.cjs",
@@ -24,14 +24,12 @@ export class CrosspostClient {
24
24
  constructor(config: CrosspostClientConfig = {}) {
25
25
  const baseUrl = config.baseUrl || DEFAULT_CONFIG.baseUrl; // you can deploy your own
26
26
  const timeout = config.timeout || DEFAULT_CONFIG.timeout;
27
- const retries = config.retries ?? DEFAULT_CONFIG.retries;
28
27
 
29
28
  const nearAuthData = config.nearAuthData;
30
29
 
31
30
  this.options = {
32
31
  baseUrl,
33
32
  timeout,
34
- retries,
35
33
  nearAuthData,
36
34
  };
37
35
 
@@ -18,11 +18,6 @@ export interface CrosspostClientConfig {
18
18
  * @default 30000
19
19
  */
20
20
  timeout?: number;
21
- /**
22
- * Number of retries for failed requests (specifically for network errors or 5xx status codes)
23
- * @default 2
24
- */
25
- retries?: number;
26
21
  }
27
22
 
28
23
  /**
@@ -31,5 +26,4 @@ export interface CrosspostClientConfig {
31
26
  export const DEFAULT_CONFIG: Required<Omit<CrosspostClientConfig, 'nearAuthData'>> = {
32
27
  baseUrl: 'https://open-crosspost-proxy.deno.dev/',
33
28
  timeout: 30000,
34
- retries: 2,
35
29
  };
@@ -1,9 +1,9 @@
1
1
  import { ApiErrorCode, type StatusCode } from '@crosspost/types';
2
2
  import { createAuthToken, type NearAuthData } from 'near-sign-verify';
3
3
  import {
4
- apiWrapper,
5
4
  createNetworkError,
6
5
  CrosspostError,
6
+ enrichErrorWithContext,
7
7
  handleErrorResponse,
8
8
  } from '../utils/error.ts';
9
9
 
@@ -29,14 +29,10 @@ export interface RequestOptions {
29
29
  * Request timeout in milliseconds
30
30
  */
31
31
  timeout: number;
32
- /**
33
- * Number of retries for failed requests
34
- */
35
- retries: number;
36
32
  }
37
33
 
38
34
  /**
39
- * Makes a request to the API with retry and error handling
35
+ * Makes a request to the API with error handling
40
36
  *
41
37
  * @param method The HTTP method
42
38
  * @param path The API path
@@ -76,131 +72,118 @@ export async function makeRequest<
76
72
  method,
77
73
  path,
78
74
  url,
79
- retries: options.retries,
80
75
  };
81
76
 
82
- return apiWrapper(async () => {
83
- let lastError: Error | null = null;
84
-
85
- for (let attempt = 0; attempt <= options.retries; attempt++) {
86
- const controller = new AbortController();
87
- const timeoutId = setTimeout(() => controller.abort(), options.timeout);
77
+ const controller = new AbortController();
78
+ const timeoutId = setTimeout(() => controller.abort(), options.timeout);
79
+
80
+ try {
81
+ const headers: Record<string, string> = {
82
+ 'Content-Type': 'application/json',
83
+ 'Accept': 'application/json',
84
+ };
85
+
86
+ // For GET requests, use X-Near-Account header if available
87
+ if (method === 'GET') {
88
+ const nearAccount = options.nearAccount || options.nearAuthData?.account_id;
89
+ if (!nearAccount) {
90
+ throw new CrosspostError(
91
+ 'No NEAR account provided for GET request',
92
+ ApiErrorCode.UNAUTHORIZED,
93
+ 401,
94
+ );
95
+ }
96
+ headers['X-Near-Account'] = nearAccount;
97
+ } else {
98
+ // For non-GET requests, require nearAuthData
99
+ if (!options.nearAuthData) {
100
+ throw new CrosspostError(
101
+ 'NEAR authentication data required for non-GET request',
102
+ ApiErrorCode.UNAUTHORIZED,
103
+ 401,
104
+ );
105
+ }
106
+ headers['Authorization'] = `Bearer ${createAuthToken(options.nearAuthData)}`;
107
+ }
88
108
 
109
+ const requestOptions: RequestInit = {
110
+ method,
111
+ headers,
112
+ body: method !== 'GET' && data ? JSON.stringify(data) : undefined,
113
+ signal: controller.signal,
114
+ };
115
+
116
+ const response = await fetch(url, requestOptions);
117
+ clearTimeout(timeoutId);
118
+
119
+ let responseData: any;
120
+ try {
121
+ responseData = await response.json();
122
+ } catch (jsonError) {
123
+ // JSON parsing failed - try to get response text for context
124
+ let responseText: string | undefined;
89
125
  try {
90
- const headers: Record<string, string> = {
91
- 'Content-Type': 'application/json',
92
- 'Accept': 'application/json',
93
- };
94
-
95
- // For GET requests, use X-Near-Account header if available
96
- if (method === 'GET') {
97
- const nearAccount = options.nearAccount || options.nearAuthData?.account_id;
98
- if (!nearAccount) {
99
- throw new CrosspostError(
100
- 'No NEAR account provided for GET request',
101
- ApiErrorCode.UNAUTHORIZED,
102
- 401,
103
- );
104
- }
105
- headers['X-Near-Account'] = nearAccount;
106
- } else {
107
- // For non-GET requests, require nearAuthData
108
- if (!options.nearAuthData) {
109
- throw new CrosspostError(
110
- 'NEAR authentication data required for non-GET request',
111
- ApiErrorCode.UNAUTHORIZED,
112
- 401,
113
- );
114
- }
115
- headers['Authorization'] = `Bearer ${createAuthToken(options.nearAuthData)}`;
116
- }
117
-
118
- const requestOptions: RequestInit = {
119
- method,
120
- headers,
121
- body: method !== 'GET' && data ? JSON.stringify(data) : undefined,
122
- signal: controller.signal,
123
- };
124
-
125
- const response = await fetch(url, requestOptions);
126
- clearTimeout(timeoutId); // Clear timeout if fetch completes
127
-
128
- let responseData: any;
129
- try {
130
- responseData = await response.json();
131
- } catch (jsonError) {
132
- // If JSON parsing fails, did API throw an error?
133
- if (!response.ok) {
134
- throw new CrosspostError(
135
- `API request failed with status ${response.status} and non-JSON response`,
136
- ApiErrorCode.NETWORK_ERROR,
137
- response.status as StatusCode,
138
- { originalStatusText: response.statusText },
139
- );
140
- }
141
- // Otherwise, throw a custom error
142
- throw new CrosspostError(
143
- `Failed to parse JSON response: ${
144
- jsonError instanceof Error ? jsonError.message : String(jsonError)
145
- }`,
146
- ApiErrorCode.INTERNAL_ERROR,
147
- response.status as StatusCode,
148
- );
149
- }
150
-
151
- if (!response.ok) {
152
- lastError = handleErrorResponse(responseData, response.status);
153
- // Only retry rate limit errors
154
- const shouldRetry = lastError instanceof CrosspostError &&
155
- lastError.code === ApiErrorCode.RATE_LIMITED;
156
- if (shouldRetry && attempt < options.retries) {
157
- await new Promise((resolve) => setTimeout(resolve, 1000 * Math.pow(2, attempt))); // Exponential backoff
158
- continue; // Retry
159
- }
160
- throw lastError; // Throw error if not retrying or retries exhausted
161
- }
162
-
163
- // Handle response based on success flag
164
- if (responseData && typeof responseData === 'object' && 'success' in responseData) {
165
- if (responseData.success) {
166
- // Success response - return the data
167
- return responseData.data as TResponse;
168
- } else {
169
- // Error response - handle with our error utilities
170
- lastError = handleErrorResponse(responseData, response.status);
171
- // Only retry rate limit errors
172
- const shouldRetry = lastError instanceof CrosspostError &&
173
- lastError.code === ApiErrorCode.RATE_LIMITED;
174
- if (shouldRetry && attempt < options.retries) {
175
- await new Promise((resolve) => setTimeout(resolve, 1000 * Math.pow(2, attempt))); // Exponential backoff
176
- continue; // Retry
177
- }
178
- throw lastError;
179
- }
180
- }
181
- } catch (error) {
182
- clearTimeout(timeoutId); // Clear timeout on error
183
- lastError = error instanceof Error ? error : new Error(String(error)); // Store the error
184
-
185
- // Handle fetch/network errors specifically for retries
186
- const isNetworkError = error instanceof TypeError ||
187
- (error instanceof DOMException && error.name === 'AbortError');
188
- if (isNetworkError && attempt < options.retries) {
189
- await new Promise((resolve) => setTimeout(resolve, 1000 * Math.pow(2, attempt))); // Exponential backoff
190
- continue; // Retry network error
191
- }
192
-
193
- // If it's not a known ApiError/PlatformError, wrap it
194
- if (!(error instanceof CrosspostError)) {
195
- throw createNetworkError(error, url, options.timeout);
196
- }
197
-
198
- throw error; // Re-throw known ApiError or final network error
199
- }
126
+ responseText = await response.text();
127
+ } catch (_) { /* ignore */ }
128
+
129
+ throw new CrosspostError(
130
+ `API request failed with status ${response.status} and non-JSON response`,
131
+ ApiErrorCode.INVALID_RESPONSE,
132
+ response.status as StatusCode,
133
+ {
134
+ originalStatusText: response.statusText,
135
+ originalError: jsonError instanceof Error ? jsonError.message : String(jsonError),
136
+ responseText,
137
+ },
138
+ );
200
139
  }
201
140
 
202
- // Should not be reachable if retries >= 0, but needed for type safety
203
- throw lastError ||
204
- new CrosspostError('Request failed after multiple retries', ApiErrorCode.INTERNAL_ERROR, 500);
205
- }, context);
141
+ // Handle non-ok responses (4xx/5xx)
142
+ if (!response.ok) {
143
+ throw handleErrorResponse(responseData, response.status);
144
+ }
145
+
146
+ // Validate success response structure
147
+ if (!responseData || typeof responseData !== 'object' || !('success' in responseData)) {
148
+ throw new CrosspostError(
149
+ 'Invalid success response format from API',
150
+ ApiErrorCode.INVALID_RESPONSE,
151
+ response.status as StatusCode,
152
+ { responseData },
153
+ );
154
+ }
155
+
156
+ if (responseData.success) {
157
+ return responseData.data as TResponse;
158
+ }
159
+
160
+ // If we get here, we have response.ok but success: false
161
+ // This is unexpected - treat as an error
162
+ throw handleErrorResponse(responseData, response.status);
163
+ } catch (error) {
164
+ clearTimeout(timeoutId);
165
+
166
+ if (error instanceof CrosspostError) {
167
+ // Enrich CrosspostError with request context
168
+ throw enrichErrorWithContext(error, context);
169
+ }
170
+
171
+ // Handle network errors (including timeouts)
172
+ if (
173
+ error instanceof TypeError || (error instanceof DOMException && error.name === 'AbortError')
174
+ ) {
175
+ throw enrichErrorWithContext(createNetworkError(error, url, options.timeout), context);
176
+ }
177
+
178
+ // For any other errors, wrap them with context
179
+ throw enrichErrorWithContext(
180
+ new CrosspostError(
181
+ error instanceof Error ? error.message : String(error),
182
+ ApiErrorCode.INTERNAL_ERROR,
183
+ 500,
184
+ { originalError: String(error) },
185
+ ),
186
+ context,
187
+ );
188
+ }
206
189
  }