@crosspost/sdk 0.1.2 → 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,75 +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
- export const AUTH_COOKIE_OPTIONS: Cookies.CookieAttributes = {
9
- secure: true,
10
- sameSite: 'lax', // how could we make this none?
11
- path: '/',
12
- expires: 30, // 30 days
13
- };
14
-
15
- /**
16
- * Gets authentication data from the cookie
17
- * @returns The NearAuthData object or undefined if not found
18
- */
19
- export function getAuthFromCookie(): NearAuthData | undefined {
20
- try {
21
- if (typeof document === 'undefined') {
22
- return undefined;
23
- }
24
-
25
- const cookieValue = Cookies.get(AUTH_COOKIE_NAME);
26
- if (!cookieValue) {
27
- return undefined;
28
- }
29
-
30
- return JSON.parse(cookieValue) as NearAuthData;
31
- } catch (error) {
32
- console.error('Failed to parse auth cookie:', error);
33
- return undefined;
34
- }
35
- }
36
-
37
- /**
38
- * Stores authentication data in a secure cookie
39
- * @param authData The NearAuthData object to store
40
- */
41
- export function storeAuthInCookie(authData: NearAuthData): void {
42
- try {
43
- if (typeof document === 'undefined') {
44
- return;
45
- }
46
-
47
- const cookieValue = JSON.stringify(authData);
48
- Cookies.set(AUTH_COOKIE_NAME, cookieValue, AUTH_COOKIE_OPTIONS);
49
- } catch (error) {
50
- console.error('Failed to store auth cookie:', error);
51
- }
52
- }
53
-
54
- /**
55
- * Clears the authentication cookie
56
- */
57
- export function clearAuthCookie(): void {
58
- if (typeof document === 'undefined') {
59
- return;
60
- }
61
-
62
- Cookies.remove(AUTH_COOKIE_NAME, { path: AUTH_COOKIE_OPTIONS.path });
63
- }
64
-
65
- /**
66
- * Gets the CSRF token from the cookie
67
- * @returns The CSRF token or undefined if not found
68
- */
69
- export function getCsrfToken(): string | undefined {
70
- if (typeof document === 'undefined') {
71
- return undefined;
72
- }
73
-
74
- return Cookies.get(CSRF_COOKIE_NAME);
75
- }