@crosspost/sdk 0.1.3 → 0.1.4

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.
@@ -1,5 +1,305 @@
1
1
  import { ApiError, ApiErrorCode, Platform, PlatformError } from '@crosspost/types';
2
2
 
3
+ /**
4
+ * Error categories grouped by type
5
+ */
6
+ export const ERROR_CATEGORIES = {
7
+ AUTH: [
8
+ ApiErrorCode.UNAUTHORIZED,
9
+ ApiErrorCode.FORBIDDEN,
10
+ ],
11
+ VALIDATION: [
12
+ ApiErrorCode.VALIDATION_ERROR,
13
+ ApiErrorCode.INVALID_REQUEST,
14
+ ],
15
+ NETWORK: [
16
+ ApiErrorCode.NETWORK_ERROR,
17
+ ],
18
+ PLATFORM: [
19
+ ApiErrorCode.PLATFORM_ERROR,
20
+ ApiErrorCode.PLATFORM_UNAVAILABLE,
21
+ ],
22
+ CONTENT: [
23
+ ApiErrorCode.CONTENT_POLICY_VIOLATION,
24
+ ApiErrorCode.DUPLICATE_CONTENT,
25
+ ],
26
+ RATE_LIMIT: [
27
+ ApiErrorCode.RATE_LIMITED,
28
+ ],
29
+ POST: [
30
+ ApiErrorCode.POST_CREATION_FAILED,
31
+ ApiErrorCode.THREAD_CREATION_FAILED,
32
+ ApiErrorCode.POST_DELETION_FAILED,
33
+ ApiErrorCode.POST_INTERACTION_FAILED,
34
+ ],
35
+ MEDIA: [
36
+ ApiErrorCode.MEDIA_UPLOAD_FAILED,
37
+ ],
38
+ };
39
+
40
+ /**
41
+ * Check if an error belongs to a specific category
42
+ *
43
+ * @param error The error to check
44
+ * @param category The category to check against
45
+ * @returns True if the error belongs to the category, false otherwise
46
+ */
47
+ export function isErrorOfCategory(error: unknown, category: ApiErrorCode[]): boolean {
48
+ if (error instanceof ApiError) {
49
+ return category.includes(error.code);
50
+ }
51
+
52
+ if (error instanceof PlatformError) {
53
+ return category.includes(error.code);
54
+ }
55
+
56
+ // Fallback for error-like objects with code property
57
+ if (error && typeof error === 'object' && 'code' in error) {
58
+ return category.includes((error as any).code);
59
+ }
60
+
61
+ return false;
62
+ }
63
+
64
+ /**
65
+ * Check if an error is an authentication error
66
+ *
67
+ * @param error The error to check
68
+ * @returns True if the error is an authentication error, false otherwise
69
+ */
70
+ export function isAuthError(error: unknown): boolean {
71
+ return isErrorOfCategory(error, ERROR_CATEGORIES.AUTH);
72
+ }
73
+
74
+ /**
75
+ * Check if an error is a validation error
76
+ *
77
+ * @param error The error to check
78
+ * @returns True if the error is a validation error, false otherwise
79
+ */
80
+ export function isValidationError(error: unknown): boolean {
81
+ return isErrorOfCategory(error, ERROR_CATEGORIES.VALIDATION);
82
+ }
83
+
84
+ /**
85
+ * Check if an error is a network error
86
+ *
87
+ * @param error The error to check
88
+ * @returns True if the error is a network error, false otherwise
89
+ */
90
+ export function isNetworkError(error: unknown): boolean {
91
+ return isErrorOfCategory(error, ERROR_CATEGORIES.NETWORK);
92
+ }
93
+
94
+ /**
95
+ * Check if an error is a platform error
96
+ *
97
+ * @param error The error to check
98
+ * @returns True if the error is a platform error, false otherwise
99
+ */
100
+ export function isPlatformError(error: unknown): boolean {
101
+ return isErrorOfCategory(error, ERROR_CATEGORIES.PLATFORM) || error instanceof PlatformError;
102
+ }
103
+
104
+ /**
105
+ * Check if an error is a content policy error
106
+ *
107
+ * @param error The error to check
108
+ * @returns True if the error is a content policy error, false otherwise
109
+ */
110
+ export function isContentError(error: unknown): boolean {
111
+ return isErrorOfCategory(error, ERROR_CATEGORIES.CONTENT);
112
+ }
113
+
114
+ /**
115
+ * Check if an error is a rate limit error
116
+ *
117
+ * @param error The error to check
118
+ * @returns True if the error is a rate limit error, false otherwise
119
+ */
120
+ export function isRateLimitError(error: unknown): boolean {
121
+ return isErrorOfCategory(error, ERROR_CATEGORIES.RATE_LIMIT);
122
+ }
123
+
124
+ /**
125
+ * Check if an error is a post-related error
126
+ *
127
+ * @param error The error to check
128
+ * @returns True if the error is a post-related error, false otherwise
129
+ */
130
+ export function isPostError(error: unknown): boolean {
131
+ return isErrorOfCategory(error, ERROR_CATEGORIES.POST);
132
+ }
133
+
134
+ /**
135
+ * Check if an error is a media-related error
136
+ *
137
+ * @param error The error to check
138
+ * @returns True if the error is a media-related error, false otherwise
139
+ */
140
+ export function isMediaError(error: unknown): boolean {
141
+ return isErrorOfCategory(error, ERROR_CATEGORIES.MEDIA);
142
+ }
143
+
144
+ /**
145
+ * Check if an error is recoverable
146
+ *
147
+ * @param error The error to check
148
+ * @returns True if the error is recoverable, false otherwise
149
+ */
150
+ export function isRecoverableError(error: unknown): boolean {
151
+ if (error instanceof ApiError || error instanceof PlatformError) {
152
+ return error.recoverable;
153
+ }
154
+
155
+ return false;
156
+ }
157
+
158
+ /**
159
+ * Get a user-friendly error message
160
+ *
161
+ * @param error The error to get the message from
162
+ * @param defaultMessage The default message to return if no message is found
163
+ * @returns The error message
164
+ */
165
+ export function getErrorMessage(
166
+ error: unknown,
167
+ defaultMessage: string = 'An error occurred',
168
+ ): string {
169
+ if (error instanceof Error) {
170
+ return error.message || defaultMessage;
171
+ }
172
+
173
+ if (typeof error === 'string') {
174
+ return error;
175
+ }
176
+
177
+ if (error && typeof error === 'object' && 'message' in error) {
178
+ return (error as any).message || defaultMessage;
179
+ }
180
+
181
+ return defaultMessage;
182
+ }
183
+
184
+ /**
185
+ * Extract error details if available
186
+ *
187
+ * @param error The error to extract details from
188
+ * @returns The error details or undefined if none are found
189
+ */
190
+ export function getErrorDetails(error: unknown): Record<string, any> | undefined {
191
+ if (error instanceof ApiError || error instanceof PlatformError) {
192
+ return error.details;
193
+ }
194
+
195
+ if (error && typeof error === 'object' && 'details' in error) {
196
+ return (error as any).details;
197
+ }
198
+
199
+ return undefined;
200
+ }
201
+
202
+ /**
203
+ * Enrich an error with additional context
204
+ *
205
+ * @param error The error to enrich
206
+ * @param context The context to add to the error
207
+ * @returns The enriched error
208
+ */
209
+ export function enrichErrorWithContext(
210
+ error: unknown,
211
+ context: Record<string, any>,
212
+ ): Error {
213
+ if (error instanceof ApiError) {
214
+ // Create a new ApiError with the merged details since details is read-only
215
+ return new ApiError(
216
+ error.message,
217
+ error.code,
218
+ error.status,
219
+ { ...(error.details || {}), ...context },
220
+ error.recoverable,
221
+ );
222
+ }
223
+
224
+ if (error instanceof PlatformError) {
225
+ // Create a new PlatformError with the merged details since details is read-only
226
+ return new PlatformError(
227
+ error.message,
228
+ error.platform,
229
+ error.code,
230
+ error.recoverable,
231
+ error.originalError,
232
+ error.status,
233
+ error.userId,
234
+ { ...(error.details || {}), ...context },
235
+ );
236
+ }
237
+
238
+ // For regular errors or non-Error objects, create a new ApiError with the context
239
+ const errorMessage = error instanceof Error ? error.message : String(error);
240
+ return new ApiError(
241
+ errorMessage || 'An error occurred',
242
+ ApiErrorCode.INTERNAL_ERROR,
243
+ 500,
244
+ { originalError: error, ...context },
245
+ false,
246
+ );
247
+ }
248
+
249
+ /**
250
+ * Wrapper for API calls with consistent error handling
251
+ *
252
+ * @param apiCall The API call to wrap
253
+ * @param context Optional context to add to any errors
254
+ * @returns The result of the API call
255
+ * @throws An enriched error if the API call fails
256
+ */
257
+ export async function apiWrapper<T>(
258
+ apiCall: () => Promise<T>,
259
+ context?: Record<string, any>,
260
+ ): Promise<T> {
261
+ try {
262
+ return await apiCall();
263
+ } catch (error) {
264
+ // If it's a Response object, use handleErrorResponse
265
+ if (error instanceof Response) {
266
+ try {
267
+ const errorData = await error.json();
268
+ throw enrichErrorWithContext(
269
+ handleErrorResponse(errorData, error.status),
270
+ context || {},
271
+ );
272
+ } catch (jsonError) {
273
+ // If JSON parsing fails, create a generic error
274
+ if (jsonError instanceof Error && jsonError.name === 'SyntaxError') {
275
+ throw enrichErrorWithContext(
276
+ new ApiError(
277
+ `API request failed with status ${error.status} and non-JSON response`,
278
+ ApiErrorCode.NETWORK_ERROR,
279
+ error.status as any,
280
+ { originalResponse: error.statusText },
281
+ ),
282
+ context || {},
283
+ );
284
+ }
285
+ // If it's already an enriched error from handleErrorResponse, just throw it
286
+ throw jsonError;
287
+ }
288
+ }
289
+
290
+ // If it's already an ApiError or PlatformError, just add context
291
+ if (error instanceof ApiError || error instanceof PlatformError) {
292
+ throw enrichErrorWithContext(error, context || {});
293
+ }
294
+
295
+ // Otherwise wrap it in an ApiError
296
+ throw enrichErrorWithContext(
297
+ error instanceof Error ? error : new Error(String(error)),
298
+ context || {},
299
+ );
300
+ }
301
+ }
302
+
3
303
  /**
4
304
  * Handles error responses from the API and converts them to appropriate error objects.
5
305
  *
@@ -1,91 +0,0 @@
1
- import Cookies from 'js-cookie';
2
- import type { NearAuthData } from 'near-sign-verify';
3
-
4
- export const AUTH_COOKIE_NAME = '__crosspost_auth';
5
- export const CSRF_COOKIE_NAME = 'XSRF-TOKEN';
6
- export const CSRF_HEADER_NAME = 'X-CSRF-Token';
7
-
8
- /**
9
- * Checks if the code is running in a Deno environment
10
- */
11
- export const isDeno = (): boolean => { // monorepo builds primarily for Deno
12
- // we could expect that frontends in Deno environment will use this package,
13
- // and then we can determine auth solution there (ValTown, Smallweb, etc)
14
- return typeof (globalThis as any).Deno !== 'undefined';
15
- };
16
-
17
- /**
18
- * Checks if the code is running in a browser environment
19
- */
20
- export const isBrowser = (): boolean => {
21
- return !isDeno() && typeof globalThis.window !== 'undefined';
22
- };
23
-
24
- export const AUTH_COOKIE_OPTIONS: Cookies.CookieAttributes = {
25
- secure: true,
26
- sameSite: 'lax', // Restrict to same-site and top-level navigation
27
- path: '/',
28
- expires: 30, // 30 days
29
- };
30
-
31
- /**
32
- * Gets authentication data from the cookie
33
- * @returns The NearAuthData object or undefined if not found
34
- */
35
- export function getAuthFromCookie(): NearAuthData | undefined {
36
- try {
37
- if (!isBrowser()) {
38
- return undefined;
39
- }
40
-
41
- const cookieValue = Cookies.get(AUTH_COOKIE_NAME);
42
- if (!cookieValue) {
43
- return undefined;
44
- }
45
-
46
- return JSON.parse(cookieValue) as NearAuthData;
47
- } catch (error) {
48
- console.error('Failed to parse auth cookie:', error);
49
- return undefined;
50
- }
51
- }
52
-
53
- /**
54
- * Stores authentication data in a secure cookie
55
- * @param authData The NearAuthData object to store
56
- */
57
- export function storeAuthInCookie(authData: NearAuthData): void {
58
- try {
59
- if (!isBrowser()) {
60
- return;
61
- }
62
-
63
- const cookieValue = JSON.stringify(authData);
64
- Cookies.set(AUTH_COOKIE_NAME, cookieValue, AUTH_COOKIE_OPTIONS);
65
- } catch (error) {
66
- console.error('Failed to store auth cookie:', error);
67
- }
68
- }
69
-
70
- /**
71
- * Clears the authentication cookie
72
- */
73
- export function clearAuthCookie(): void {
74
- if (!isBrowser()) {
75
- return;
76
- }
77
-
78
- Cookies.remove(AUTH_COOKIE_NAME, { path: AUTH_COOKIE_OPTIONS.path });
79
- }
80
-
81
- /**
82
- * Gets the CSRF token from the cookie
83
- * @returns The CSRF token or undefined if not found
84
- */
85
- export function getCsrfToken(): string | undefined {
86
- if (!isBrowser()) {
87
- return undefined;
88
- }
89
-
90
- return Cookies.get(CSRF_COOKIE_NAME);
91
- }
@@ -1,310 +0,0 @@
1
- import { ApiError, ApiErrorCode, Platform, PlatformError } from '@crosspost/types';
2
- import { handleErrorResponse as baseHandleErrorResponse } from './error.ts';
3
-
4
- /**
5
- * Error categories grouped by type for better organization and usage
6
- */
7
- export const ERROR_CATEGORIES = {
8
- AUTH: [
9
- ApiErrorCode.UNAUTHORIZED,
10
- ApiErrorCode.FORBIDDEN,
11
- ],
12
- VALIDATION: [
13
- ApiErrorCode.VALIDATION_ERROR,
14
- ApiErrorCode.INVALID_REQUEST,
15
- ],
16
- NETWORK: [
17
- ApiErrorCode.NETWORK_ERROR,
18
- ],
19
- PLATFORM: [
20
- ApiErrorCode.PLATFORM_ERROR,
21
- ApiErrorCode.PLATFORM_UNAVAILABLE,
22
- ],
23
- CONTENT: [
24
- ApiErrorCode.CONTENT_POLICY_VIOLATION,
25
- ApiErrorCode.DUPLICATE_CONTENT,
26
- ],
27
- RATE_LIMIT: [
28
- ApiErrorCode.RATE_LIMITED,
29
- ],
30
- POST: [
31
- ApiErrorCode.POST_CREATION_FAILED,
32
- ApiErrorCode.THREAD_CREATION_FAILED,
33
- ApiErrorCode.POST_DELETION_FAILED,
34
- ApiErrorCode.POST_INTERACTION_FAILED,
35
- ],
36
- MEDIA: [
37
- ApiErrorCode.MEDIA_UPLOAD_FAILED,
38
- ],
39
- };
40
-
41
- /**
42
- * Check if an error belongs to a specific category
43
- *
44
- * @param error The error to check
45
- * @param category The category to check against
46
- * @returns True if the error belongs to the category, false otherwise
47
- */
48
- export function isErrorOfCategory(error: unknown, category: ApiErrorCode[]): boolean {
49
- if (error instanceof ApiError) {
50
- return category.includes(error.code);
51
- }
52
-
53
- if (error instanceof PlatformError) {
54
- return category.includes(error.code);
55
- }
56
-
57
- // Fallback for error-like objects with code property
58
- if (error && typeof error === 'object' && 'code' in error) {
59
- return category.includes((error as any).code);
60
- }
61
-
62
- return false;
63
- }
64
-
65
- /**
66
- * Check if an error is an authentication error
67
- *
68
- * @param error The error to check
69
- * @returns True if the error is an authentication error, false otherwise
70
- */
71
- export function isAuthError(error: unknown): boolean {
72
- return isErrorOfCategory(error, ERROR_CATEGORIES.AUTH);
73
- }
74
-
75
- /**
76
- * Check if an error is a validation error
77
- *
78
- * @param error The error to check
79
- * @returns True if the error is a validation error, false otherwise
80
- */
81
- export function isValidationError(error: unknown): boolean {
82
- return isErrorOfCategory(error, ERROR_CATEGORIES.VALIDATION);
83
- }
84
-
85
- /**
86
- * Check if an error is a network error
87
- *
88
- * @param error The error to check
89
- * @returns True if the error is a network error, false otherwise
90
- */
91
- export function isNetworkError(error: unknown): boolean {
92
- return isErrorOfCategory(error, ERROR_CATEGORIES.NETWORK);
93
- }
94
-
95
- /**
96
- * Check if an error is a platform error
97
- *
98
- * @param error The error to check
99
- * @returns True if the error is a platform error, false otherwise
100
- */
101
- export function isPlatformError(error: unknown): boolean {
102
- return isErrorOfCategory(error, ERROR_CATEGORIES.PLATFORM) || error instanceof PlatformError;
103
- }
104
-
105
- /**
106
- * Check if an error is a content policy error
107
- *
108
- * @param error The error to check
109
- * @returns True if the error is a content policy error, false otherwise
110
- */
111
- export function isContentError(error: unknown): boolean {
112
- return isErrorOfCategory(error, ERROR_CATEGORIES.CONTENT);
113
- }
114
-
115
- /**
116
- * Check if an error is a rate limit error
117
- *
118
- * @param error The error to check
119
- * @returns True if the error is a rate limit error, false otherwise
120
- */
121
- export function isRateLimitError(error: unknown): boolean {
122
- return isErrorOfCategory(error, ERROR_CATEGORIES.RATE_LIMIT);
123
- }
124
-
125
- /**
126
- * Check if an error is a post-related error
127
- *
128
- * @param error The error to check
129
- * @returns True if the error is a post-related error, false otherwise
130
- */
131
- export function isPostError(error: unknown): boolean {
132
- return isErrorOfCategory(error, ERROR_CATEGORIES.POST);
133
- }
134
-
135
- /**
136
- * Check if an error is a media-related error
137
- *
138
- * @param error The error to check
139
- * @returns True if the error is a media-related error, false otherwise
140
- */
141
- export function isMediaError(error: unknown): boolean {
142
- return isErrorOfCategory(error, ERROR_CATEGORIES.MEDIA);
143
- }
144
-
145
- /**
146
- * Check if an error is recoverable
147
- *
148
- * @param error The error to check
149
- * @returns True if the error is recoverable, false otherwise
150
- */
151
- export function isRecoverableError(error: unknown): boolean {
152
- if (error instanceof ApiError || error instanceof PlatformError) {
153
- return error.recoverable;
154
- }
155
-
156
- return false;
157
- }
158
-
159
- /**
160
- * Get a user-friendly error message
161
- *
162
- * @param error The error to get the message from
163
- * @param defaultMessage The default message to return if no message is found
164
- * @returns The error message
165
- */
166
- export function getErrorMessage(
167
- error: unknown,
168
- defaultMessage: string = 'An error occurred',
169
- ): string {
170
- if (error instanceof Error) {
171
- return error.message || defaultMessage;
172
- }
173
-
174
- if (typeof error === 'string') {
175
- return error;
176
- }
177
-
178
- if (error && typeof error === 'object' && 'message' in error) {
179
- return (error as any).message || defaultMessage;
180
- }
181
-
182
- return defaultMessage;
183
- }
184
-
185
- /**
186
- * Extract error details if available
187
- *
188
- * @param error The error to extract details from
189
- * @returns The error details or undefined if none are found
190
- */
191
- export function getErrorDetails(error: unknown): Record<string, any> | undefined {
192
- if (error instanceof ApiError || error instanceof PlatformError) {
193
- return error.details;
194
- }
195
-
196
- if (error && typeof error === 'object' && 'details' in error) {
197
- return (error as any).details;
198
- }
199
-
200
- return undefined;
201
- }
202
-
203
- /**
204
- * Enrich an error with additional context
205
- *
206
- * @param error The error to enrich
207
- * @param context The context to add to the error
208
- * @returns The enriched error
209
- */
210
- export function enrichErrorWithContext(
211
- error: unknown,
212
- context: Record<string, any>,
213
- ): Error {
214
- if (error instanceof ApiError) {
215
- // Create a new ApiError with the merged details since details is read-only
216
- return new ApiError(
217
- error.message,
218
- error.code,
219
- error.status,
220
- { ...(error.details || {}), ...context },
221
- error.recoverable,
222
- );
223
- }
224
-
225
- if (error instanceof PlatformError) {
226
- // Create a new PlatformError with the merged details since details is read-only
227
- return new PlatformError(
228
- error.message,
229
- error.platform,
230
- error.code,
231
- error.recoverable,
232
- error.originalError,
233
- error.status,
234
- error.userId,
235
- { ...(error.details || {}), ...context },
236
- );
237
- }
238
-
239
- // For regular errors or non-Error objects, create a new ApiError with the context
240
- const errorMessage = error instanceof Error ? error.message : String(error);
241
- return new ApiError(
242
- errorMessage || 'An error occurred',
243
- ApiErrorCode.INTERNAL_ERROR,
244
- 500,
245
- { originalError: error, ...context },
246
- false,
247
- );
248
- }
249
-
250
- /**
251
- * Re-export the handleErrorResponse function from error.ts
252
- * for backward compatibility
253
- */
254
- export function handleErrorResponse(data: any, status: number): ApiError | PlatformError {
255
- return baseHandleErrorResponse(data, status);
256
- }
257
-
258
- /**
259
- * Wrapper for API calls with consistent error handling
260
- *
261
- * @param apiCall The API call to wrap
262
- * @param context Optional context to add to any errors
263
- * @returns The result of the API call
264
- * @throws An enriched error if the API call fails
265
- */
266
- export async function apiWrapper<T>(
267
- apiCall: () => Promise<T>,
268
- context?: Record<string, any>,
269
- ): Promise<T> {
270
- try {
271
- return await apiCall();
272
- } catch (error) {
273
- // If it's a Response object, use handleErrorResponse
274
- if (error instanceof Response) {
275
- try {
276
- const errorData = await error.json();
277
- throw enrichErrorWithContext(
278
- handleErrorResponse(errorData, error.status),
279
- context || {},
280
- );
281
- } catch (jsonError) {
282
- // If JSON parsing fails, create a generic error
283
- if (jsonError instanceof Error && jsonError.name === 'SyntaxError') {
284
- throw enrichErrorWithContext(
285
- new ApiError(
286
- `API request failed with status ${error.status} and non-JSON response`,
287
- ApiErrorCode.NETWORK_ERROR,
288
- error.status as any,
289
- { originalResponse: error.statusText },
290
- ),
291
- context || {},
292
- );
293
- }
294
- // If it's already an enriched error from handleErrorResponse, just throw it
295
- throw jsonError;
296
- }
297
- }
298
-
299
- // If it's already an ApiError or PlatformError, just add context
300
- if (error instanceof ApiError || error instanceof PlatformError) {
301
- throw enrichErrorWithContext(error, context || {});
302
- }
303
-
304
- // Otherwise wrap it in an ApiError
305
- throw enrichErrorWithContext(
306
- error instanceof Error ? error : new Error(String(error)),
307
- context || {},
308
- );
309
- }
310
- }