@crosspost/sdk 0.1.7 → 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/src/api/auth.ts CHANGED
@@ -1,9 +1,16 @@
1
1
  import type {
2
- ApiResponse,
2
+ AuthCallbackResponse,
3
+ AuthInitRequest,
3
4
  AuthRevokeResponse,
5
+ AuthStatusParams,
4
6
  AuthStatusResponse,
7
+ AuthTokenRequest,
8
+ AuthUrlResponse,
9
+ ConnectedAccount,
5
10
  ConnectedAccountsResponse,
11
+ NearAuthorizationRequest,
6
12
  NearAuthorizationResponse,
13
+ NearUnauthorizationResponse,
7
14
  Platform,
8
15
  } from '@crosspost/types';
9
16
  import { makeRequest, type RequestOptions } from '../core/request.ts';
@@ -27,7 +34,7 @@ export class AuthApi {
27
34
  * @returns A promise resolving with the authorization response.
28
35
  */
29
36
  async authorizeNearAccount(): Promise<NearAuthorizationResponse> {
30
- return makeRequest<NearAuthorizationResponse>(
37
+ return makeRequest<NearAuthorizationResponse, NearAuthorizationRequest>(
31
38
  'POST',
32
39
  '/auth/authorize/near',
33
40
  this.options,
@@ -40,7 +47,7 @@ export class AuthApi {
40
47
  * @returns A promise resolving with the authorization status response.
41
48
  */
42
49
  async getNearAuthorizationStatus(): Promise<NearAuthorizationResponse> {
43
- return makeRequest<NearAuthorizationResponse>(
50
+ return makeRequest<NearAuthorizationResponse, never>(
44
51
  'GET',
45
52
  '/auth/authorize/near/status',
46
53
  this.options,
@@ -56,9 +63,9 @@ export class AuthApi {
56
63
  */
57
64
  async loginToPlatform(
58
65
  platform: Platform,
59
- options?: { successUrl?: string; errorUrl?: string },
60
- ): Promise<ApiResponse<any>> { // TODO: Refine response type based on actual API
61
- return makeRequest<ApiResponse<any>>(
66
+ options?: AuthInitRequest,
67
+ ): Promise<AuthUrlResponse> {
68
+ return makeRequest<AuthUrlResponse, AuthInitRequest>(
62
69
  'POST',
63
70
  `/auth/${platform}/login`,
64
71
  this.options,
@@ -69,26 +76,29 @@ export class AuthApi {
69
76
  /**
70
77
  * Refreshes the authentication token for the specified platform.
71
78
  * @param platform The target platform.
72
- * @returns A promise resolving with the refresh response.
79
+ * @returns A promise resolving with the refresh response containing updated auth details.
73
80
  */
74
- async refreshToken(platform: Platform): Promise<ApiResponse<any>> { // TODO: Refine response type
75
- return makeRequest<ApiResponse<any>>(
81
+ async refreshToken(platform: Platform, userId: string): Promise<AuthCallbackResponse> {
82
+ return makeRequest<AuthCallbackResponse, AuthTokenRequest>(
76
83
  'POST',
77
84
  `/auth/${platform}/refresh`,
78
85
  this.options,
86
+ { userId },
79
87
  );
80
88
  }
81
89
 
82
90
  /**
83
91
  * Refreshes the user's profile information from the specified platform.
84
92
  * @param platform The target platform.
85
- * @returns A promise resolving with the profile refresh response.
93
+ * @param userId The user ID on the platform
94
+ * @returns A promise resolving with the updated account profile information.
86
95
  */
87
- async refreshProfile(platform: Platform): Promise<ApiResponse<any>> { // TODO: Refine response type
88
- return makeRequest<ApiResponse<any>>(
96
+ async refreshProfile(platform: Platform, userId: string): Promise<ConnectedAccount> {
97
+ return makeRequest<ConnectedAccount, AuthTokenRequest>(
89
98
  'POST',
90
99
  `/auth/${platform}/refresh-profile`,
91
100
  this.options,
101
+ { userId },
92
102
  );
93
103
  }
94
104
 
@@ -97,11 +107,26 @@ export class AuthApi {
97
107
  * @param platform The target platform.
98
108
  * @returns A promise resolving with the authentication status response.
99
109
  */
100
- async getAuthStatus(platform: Platform): Promise<AuthStatusResponse> {
101
- return makeRequest<AuthStatusResponse>(
110
+ async getAuthStatus(platform: Platform, userId: string): Promise<AuthStatusResponse> {
111
+ return makeRequest<AuthStatusResponse, never, AuthStatusParams>(
102
112
  'GET',
103
- `/auth/${platform}/status`,
113
+ `/auth/${platform}/status/${userId}`,
104
114
  this.options,
115
+ undefined,
116
+ { platform, userId },
117
+ );
118
+ }
119
+
120
+ /**
121
+ * Unauthorizes a NEAR account from using the service
122
+ * @returns A promise resolving with the unauthorized response
123
+ */
124
+ async unauthorizeNear(): Promise<NearUnauthorizationResponse> {
125
+ return makeRequest<NearUnauthorizationResponse, NearAuthorizationRequest>(
126
+ 'DELETE',
127
+ '/auth/unauthorize/near',
128
+ this.options,
129
+ {},
105
130
  );
106
131
  }
107
132
 
@@ -110,11 +135,12 @@ export class AuthApi {
110
135
  * @param platform The target platform.
111
136
  * @returns A promise resolving with the revocation response.
112
137
  */
113
- async revokeAuth(platform: Platform): Promise<AuthRevokeResponse> {
114
- return makeRequest<AuthRevokeResponse>(
138
+ async revokeAuth(platform: Platform, userId: string): Promise<AuthRevokeResponse> {
139
+ return makeRequest<AuthRevokeResponse, AuthTokenRequest>(
115
140
  'DELETE',
116
141
  `/auth/${platform}/revoke`,
117
142
  this.options,
143
+ { userId },
118
144
  );
119
145
  }
120
146
 
@@ -123,7 +149,7 @@ export class AuthApi {
123
149
  * @returns A promise resolving with the list of connected accounts.
124
150
  */
125
151
  async getConnectedAccounts(): Promise<ConnectedAccountsResponse> {
126
- return makeRequest<ConnectedAccountsResponse>(
152
+ return makeRequest<ConnectedAccountsResponse, never>(
127
153
  'GET',
128
154
  '/auth/accounts',
129
155
  this.options,
package/src/api/post.ts CHANGED
@@ -36,7 +36,7 @@ export class PostApi {
36
36
  * @returns A promise resolving with the post creation response.
37
37
  */
38
38
  async createPost(request: CreatePostRequest): Promise<CreatePostResponse> {
39
- return makeRequest<CreatePostResponse>(
39
+ return makeRequest<CreatePostResponse, CreatePostRequest>(
40
40
  'POST',
41
41
  '/api/post',
42
42
  this.options,
@@ -50,7 +50,7 @@ export class PostApi {
50
50
  * @returns A promise resolving with the repost response.
51
51
  */
52
52
  async repost(request: RepostRequest): Promise<RepostResponse> {
53
- return makeRequest<RepostResponse>(
53
+ return makeRequest<RepostResponse, RepostRequest>(
54
54
  'POST',
55
55
  '/api/post/repost',
56
56
  this.options,
@@ -64,7 +64,7 @@ export class PostApi {
64
64
  * @returns A promise resolving with the quote post response.
65
65
  */
66
66
  async quotePost(request: QuotePostRequest): Promise<QuotePostResponse> {
67
- return makeRequest<QuotePostResponse>(
67
+ return makeRequest<QuotePostResponse, QuotePostRequest>(
68
68
  'POST',
69
69
  '/api/post/quote',
70
70
  this.options,
@@ -78,7 +78,7 @@ export class PostApi {
78
78
  * @returns A promise resolving with the reply response.
79
79
  */
80
80
  async replyToPost(request: ReplyToPostRequest): Promise<ReplyToPostResponse> {
81
- return makeRequest<ReplyToPostResponse>(
81
+ return makeRequest<ReplyToPostResponse, ReplyToPostRequest>(
82
82
  'POST',
83
83
  '/api/post/reply',
84
84
  this.options,
@@ -92,7 +92,7 @@ export class PostApi {
92
92
  * @returns A promise resolving with the like response.
93
93
  */
94
94
  async likePost(request: LikePostRequest): Promise<LikePostResponse> {
95
- return makeRequest<LikePostResponse>(
95
+ return makeRequest<LikePostResponse, LikePostRequest>(
96
96
  'POST',
97
97
  `/api/post/like`,
98
98
  this.options,
@@ -106,7 +106,7 @@ export class PostApi {
106
106
  * @returns A promise resolving with the unlike response.
107
107
  */
108
108
  async unlikePost(request: UnlikePostRequest): Promise<UnlikePostResponse> {
109
- return makeRequest<UnlikePostResponse>(
109
+ return makeRequest<UnlikePostResponse, UnlikePostRequest>(
110
110
  'DELETE',
111
111
  `/api/post/like`,
112
112
  this.options,
@@ -120,7 +120,7 @@ export class PostApi {
120
120
  * @returns A promise resolving with the delete response.
121
121
  */
122
122
  async deletePost(request: DeletePostRequest): Promise<DeletePostResponse> {
123
- return makeRequest<DeletePostResponse>(
123
+ return makeRequest<DeletePostResponse, DeletePostRequest>(
124
124
  'DELETE',
125
125
  `/api/post`,
126
126
  this.options,
package/src/api/system.ts CHANGED
@@ -1,4 +1,9 @@
1
- import type { ApiResponse, EndpointRateLimitResponse, RateLimitResponse } from '@crosspost/types';
1
+ import type {
2
+ EndpointRateLimitResponse,
3
+ HealthStatus,
4
+ RateLimitEndpointParam,
5
+ RateLimitResponse,
6
+ } from '@crosspost/types';
2
7
  import { makeRequest, type RequestOptions } from '../core/request.ts';
3
8
 
4
9
  /**
@@ -21,7 +26,7 @@ export class SystemApi {
21
26
  * @returns A promise resolving with the rate limit response
22
27
  */
23
28
  async getRateLimits(): Promise<RateLimitResponse> {
24
- return makeRequest<RateLimitResponse>(
29
+ return makeRequest<RateLimitResponse, never>(
25
30
  'GET',
26
31
  '/api/rate-limit',
27
32
  this.options,
@@ -34,10 +39,12 @@ export class SystemApi {
34
39
  * @returns A promise resolving with the endpoint rate limit response
35
40
  */
36
41
  async getEndpointRateLimit(endpoint: string): Promise<EndpointRateLimitResponse> {
37
- return makeRequest<EndpointRateLimitResponse>(
42
+ return makeRequest<EndpointRateLimitResponse, never, RateLimitEndpointParam>(
38
43
  'GET',
39
44
  `/api/rate-limit/${endpoint}`,
40
45
  this.options,
46
+ undefined,
47
+ { endpoint },
41
48
  );
42
49
  }
43
50
 
@@ -45,8 +52,8 @@ export class SystemApi {
45
52
  * Gets the health status of the API
46
53
  * @returns A promise resolving with the health status
47
54
  */
48
- async getHealthStatus(): Promise<ApiResponse<{ status: string }>> {
49
- return makeRequest<ApiResponse<{ status: string }>>(
55
+ async getHealthStatus(): Promise<HealthStatus> {
56
+ return makeRequest<HealthStatus, never>(
50
57
  'GET',
51
58
  '/health',
52
59
  this.options,
@@ -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
 
@@ -43,17 +41,56 @@ export class CrosspostClient {
43
41
 
44
42
  /**
45
43
  * Sets the authentication data (signature) for the client
44
+ * Required for non-GET requests
46
45
  * @param nearAuthData The NEAR authentication data
47
46
  */
48
47
  public setAuthentication(nearAuthData: NearAuthData): void {
49
48
  this.options.nearAuthData = nearAuthData;
49
+ // Also set nearAccount from nearAuthData if not already set
50
+ if (!this.options.nearAccount) {
51
+ this.options.nearAccount = nearAuthData.account_id;
52
+ }
53
+ }
54
+
55
+ /**
56
+ * Sets the NEAR account ID for simplified GET request authentication
57
+ * If not set, will use account_id from nearAuthData
58
+ * @param nearAccount The NEAR account ID
59
+ */
60
+ public setNearAccount(nearAccount: string): void {
61
+ this.options.nearAccount = nearAccount;
62
+ }
63
+
64
+ /**
65
+ * Gets the current NEAR account ID being used for authentication
66
+ * @returns The NEAR account ID from nearAccount or nearAuthData
67
+ */
68
+ public getNearAccount(): string | undefined {
69
+ return this.options.nearAccount || this.options.nearAuthData?.account_id;
50
70
  }
51
71
 
52
72
  /**
53
73
  * Checks if authentication data (signature) exists on client
54
- * @param signature The NEAR authentication data
74
+ * @returns true if nearAuthData is set (required for non-GET requests)
55
75
  */
56
76
  public isAuthenticated(): boolean {
57
77
  return !!this.options.nearAuthData;
58
78
  }
79
+
80
+ /**
81
+ * Checks if a NEAR account is set for GET request authentication
82
+ * @returns true if either nearAccount or nearAuthData.account_id is set
83
+ */
84
+ public hasNearAccount(): boolean {
85
+ return !!(this.options.nearAccount || this.options.nearAuthData?.account_id);
86
+ }
87
+
88
+ /**
89
+ * Clears all authentication data from the client
90
+ * This will prevent all requests from working until new authentication is set
91
+ */
92
+ public clear(): void {
93
+ this.options.nearAuthData = undefined;
94
+ this.options.nearAccount = undefined;
95
+ }
59
96
  }
@@ -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
 
@@ -17,21 +17,22 @@ export interface RequestOptions {
17
17
  baseUrl: string;
18
18
  /**
19
19
  * NEAR authentication data for generating auth tokens
20
- * Can be undefined if not authorize yet
20
+ * Required for non-GET requests, optional for GET requests
21
21
  */
22
22
  nearAuthData?: NearAuthData;
23
23
  /**
24
- * Request timeout in milliseconds
24
+ * NEAR account ID for simplified GET request authentication
25
+ * If not provided, will use account_id from nearAuthData
25
26
  */
26
- timeout: number;
27
+ nearAccount?: string;
27
28
  /**
28
- * Number of retries for failed requests
29
+ * Request timeout in milliseconds
29
30
  */
30
- retries: number;
31
+ timeout: number;
31
32
  }
32
33
 
33
34
  /**
34
- * Makes a request to the API with retry and error handling
35
+ * Makes a request to the API with error handling
35
36
  *
36
37
  * @param method The HTTP method
37
38
  * @param path The API path
@@ -39,17 +40,21 @@ export interface RequestOptions {
39
40
  * @param data Optional request data
40
41
  * @returns A promise resolving with the response data
41
42
  */
42
- export async function makeRequest<T>(
43
+ export async function makeRequest<
44
+ TResponse,
45
+ TRequest = unknown,
46
+ TQuery = unknown,
47
+ >(
43
48
  method: string,
44
49
  path: string,
45
50
  options: RequestOptions,
46
- data?: any,
47
- query?: Record<string, any>,
48
- ): Promise<T> {
51
+ data?: TRequest,
52
+ query?: TQuery,
53
+ ): Promise<TResponse> {
49
54
  let url = `${options.baseUrl}${path.startsWith('/') ? path : `/${path}`}`;
50
55
 
51
56
  // Add query parameters if provided
52
- if (query && Object.keys(query).length > 0) {
57
+ if (query && typeof query === 'object' && Object.keys(query).length > 0) {
53
58
  const queryParams = new URLSearchParams();
54
59
  for (const [key, value] of Object.entries(query)) {
55
60
  if (value !== undefined && value !== null) {
@@ -67,109 +72,118 @@ export async function makeRequest<T>(
67
72
  method,
68
73
  path,
69
74
  url,
70
- retries: options.retries,
71
75
  };
72
76
 
73
- return apiWrapper(async () => {
74
- let lastError: Error | null = null;
75
-
76
- for (let attempt = 0; attempt <= options.retries; attempt++) {
77
- const controller = new AbortController();
78
- 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
+ }
79
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;
80
125
  try {
81
- const headers: Record<string, string> = {
82
- 'Content-Type': 'application/json',
83
- 'Accept': 'application/json',
84
- 'Authorization': `Bearer ${createAuthToken(options.nearAuthData!)}`,
85
- };
86
-
87
- const requestOptions: RequestInit = {
88
- method,
89
- headers,
90
- body: method !== 'GET' && data ? JSON.stringify(data) : undefined,
91
- signal: controller.signal,
92
- };
93
-
94
- const response = await fetch(url, requestOptions);
95
- clearTimeout(timeoutId); // Clear timeout if fetch completes
96
-
97
- let responseData: any;
98
- try {
99
- responseData = await response.json();
100
- } catch (jsonError) {
101
- // If JSON parsing fails, did API throw an error?
102
- if (!response.ok) {
103
- throw new CrosspostError(
104
- `API request failed with status ${response.status} and non-JSON response`,
105
- ApiErrorCode.NETWORK_ERROR,
106
- response.status as StatusCode,
107
- { originalStatusText: response.statusText },
108
- );
109
- }
110
- // Otherwise, throw a custom error
111
- throw new CrosspostError(
112
- `Failed to parse JSON response: ${
113
- jsonError instanceof Error ? jsonError.message : String(jsonError)
114
- }`,
115
- ApiErrorCode.INTERNAL_ERROR,
116
- response.status as StatusCode,
117
- );
118
- }
119
-
120
- if (!response.ok) {
121
- lastError = handleErrorResponse(responseData, response.status);
122
- // Only retry rate limit errors
123
- const shouldRetry = lastError instanceof CrosspostError &&
124
- lastError.code === ApiErrorCode.RATE_LIMITED;
125
- if (shouldRetry && attempt < options.retries) {
126
- await new Promise((resolve) => setTimeout(resolve, 1000 * Math.pow(2, attempt))); // Exponential backoff
127
- continue; // Retry
128
- }
129
- throw lastError; // Throw error if not retrying or retries exhausted
130
- }
131
-
132
- // Handle response based on success flag
133
- if (responseData && typeof responseData === 'object' && 'success' in responseData) {
134
- if (responseData.success) {
135
- // Success response - return the data
136
- return responseData.data as T;
137
- } else {
138
- // Error response - handle with our error utilities
139
- lastError = handleErrorResponse(responseData, response.status);
140
- // Only retry rate limit errors
141
- const shouldRetry = lastError instanceof CrosspostError &&
142
- lastError.code === ApiErrorCode.RATE_LIMITED;
143
- if (shouldRetry && attempt < options.retries) {
144
- await new Promise((resolve) => setTimeout(resolve, 1000 * Math.pow(2, attempt))); // Exponential backoff
145
- continue; // Retry
146
- }
147
- throw lastError;
148
- }
149
- }
150
- } catch (error) {
151
- clearTimeout(timeoutId); // Clear timeout on error
152
- lastError = error instanceof Error ? error : new Error(String(error)); // Store the error
153
-
154
- // Handle fetch/network errors specifically for retries
155
- const isNetworkError = error instanceof TypeError ||
156
- (error instanceof DOMException && error.name === 'AbortError');
157
- if (isNetworkError && attempt < options.retries) {
158
- await new Promise((resolve) => setTimeout(resolve, 1000 * Math.pow(2, attempt))); // Exponential backoff
159
- continue; // Retry network error
160
- }
161
-
162
- // If it's not a known ApiError/PlatformError, wrap it
163
- if (!(error instanceof CrosspostError)) {
164
- throw createNetworkError(error, url, options.timeout);
165
- }
166
-
167
- throw error; // Re-throw known ApiError or final network error
168
- }
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
+ );
139
+ }
140
+
141
+ // Handle non-ok responses (4xx/5xx)
142
+ if (!response.ok) {
143
+ throw handleErrorResponse(responseData, response.status);
169
144
  }
170
145
 
171
- // Should not be reachable if retries >= 0, but needed for type safety
172
- throw lastError ||
173
- new CrosspostError('Request failed after multiple retries', ApiErrorCode.INTERNAL_ERROR, 500);
174
- }, context);
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
+ }
175
189
  }