@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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@crosspost/sdk",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "description": "SDK for interacting with the Crosspost API",
5
5
  "type": "module",
6
6
  "main": "dist/index.cjs",
@@ -36,8 +36,6 @@
36
36
  "author": "crosspost.near",
37
37
  "license": "MIT",
38
38
  "dependencies": {
39
- "@types/js-cookie": "^3.0.6",
40
- "js-cookie": "^3.0.5",
41
39
  "near-sign-verify": "^0.1.1"
42
40
  },
43
41
  "devDependencies": {
@@ -0,0 +1,77 @@
1
+ import type {
2
+ AccountActivityQuery,
3
+ AccountActivityResponse,
4
+ AccountPostsQuery,
5
+ AccountPostsResponse,
6
+ ActivityLeaderboardQuery,
7
+ ActivityLeaderboardResponse,
8
+ } from '@crosspost/types';
9
+ import { makeRequest, type RequestOptions } from '../core/request.ts';
10
+
11
+ /**
12
+ * Activity-related API operations
13
+ */
14
+ export class ActivityApi {
15
+ private options: RequestOptions;
16
+
17
+ /**
18
+ * Creates an instance of ActivityApi
19
+ * @param options Request options
20
+ */
21
+ constructor(options: RequestOptions) {
22
+ this.options = options;
23
+ }
24
+
25
+ /**
26
+ * Gets the global activity leaderboard
27
+ * @param query Optional query parameters
28
+ * @returns A promise resolving with the leaderboard response
29
+ */
30
+ async getLeaderboard(query?: ActivityLeaderboardQuery): Promise<ActivityLeaderboardResponse> {
31
+ return makeRequest<ActivityLeaderboardResponse>(
32
+ 'GET',
33
+ '/api/activity',
34
+ this.options,
35
+ undefined,
36
+ query,
37
+ );
38
+ }
39
+
40
+ /**
41
+ * Gets activity for a specific account
42
+ * @param signerId The NEAR account ID
43
+ * @param query Optional query parameters
44
+ * @returns A promise resolving with the account activity response
45
+ */
46
+ async getAccountActivity(
47
+ signerId: string,
48
+ query?: AccountActivityQuery,
49
+ ): Promise<AccountActivityResponse> {
50
+ return makeRequest<AccountActivityResponse>(
51
+ 'GET',
52
+ `/api/activity/${signerId}`,
53
+ this.options,
54
+ undefined,
55
+ query,
56
+ );
57
+ }
58
+
59
+ /**
60
+ * Gets posts for a specific account
61
+ * @param signerId The NEAR account ID
62
+ * @param query Optional query parameters
63
+ * @returns A promise resolving with the account posts response
64
+ */
65
+ async getAccountPosts(
66
+ signerId: string,
67
+ query?: AccountPostsQuery,
68
+ ): Promise<AccountPostsResponse> {
69
+ return makeRequest<AccountPostsResponse>(
70
+ 'GET',
71
+ `/api/activity/${signerId}/posts`,
72
+ this.options,
73
+ undefined,
74
+ query,
75
+ );
76
+ }
77
+ }
@@ -0,0 +1,55 @@
1
+ import type { EndpointRateLimitResponse, RateLimitResponse } from '@crosspost/types';
2
+ import { makeRequest, type RequestOptions } from '../core/request.ts';
3
+
4
+ /**
5
+ * System-related API operations
6
+ * Includes rate limits, health checks, and other system-related functionality
7
+ */
8
+ export class SystemApi {
9
+ private options: RequestOptions;
10
+
11
+ /**
12
+ * Creates an instance of SystemApi
13
+ * @param options Request options
14
+ */
15
+ constructor(options: RequestOptions) {
16
+ this.options = options;
17
+ }
18
+
19
+ /**
20
+ * Gets the current rate limit status
21
+ * @returns A promise resolving with the rate limit response
22
+ */
23
+ async getRateLimits(): Promise<RateLimitResponse> {
24
+ return makeRequest<RateLimitResponse>(
25
+ 'GET',
26
+ '/api/rate-limit',
27
+ this.options,
28
+ );
29
+ }
30
+
31
+ /**
32
+ * Gets the rate limit status for a specific endpoint
33
+ * @param endpoint The endpoint to get rate limit for
34
+ * @returns A promise resolving with the endpoint rate limit response
35
+ */
36
+ async getEndpointRateLimit(endpoint: string): Promise<EndpointRateLimitResponse> {
37
+ return makeRequest<EndpointRateLimitResponse>(
38
+ 'GET',
39
+ `/api/rate-limit/${endpoint}`,
40
+ this.options,
41
+ );
42
+ }
43
+
44
+ /**
45
+ * Gets the health status of the API
46
+ * @returns A promise resolving with the health status
47
+ */
48
+ async getHealthStatus(): Promise<{ status: string }> {
49
+ return makeRequest<{ status: string }>(
50
+ 'GET',
51
+ '/health',
52
+ this.options,
53
+ );
54
+ }
55
+ }
@@ -1,23 +1,19 @@
1
- import type { NearAuthData as NearSignatureData } from 'near-sign-verify';
1
+ import type { NearAuthData } from 'near-sign-verify';
2
+ import { ActivityApi } from '../api/activity.ts';
2
3
  import { AuthApi } from '../api/auth.ts';
3
4
  import { PostApi } from '../api/post.ts';
5
+ import { SystemApi } from '../api/system.ts';
4
6
  import { type CrosspostClientConfig, DEFAULT_CONFIG } from './config.ts';
5
7
  import type { RequestOptions } from './request.ts';
6
- import { getAuthFromCookie, storeAuthInCookie } from '../utils/cookie.ts';
7
8
 
8
9
  /**
9
10
  * Main client for interacting with the Crosspost API service.
10
11
  */
11
12
  export class CrosspostClient {
12
- /**
13
- * Authentication-related API operations
14
- */
15
13
  public readonly auth: AuthApi;
16
-
17
- /**
18
- * Post-related API operations
19
- */
20
14
  public readonly post: PostApi;
15
+ public readonly activity: ActivityApi;
16
+ public readonly system: SystemApi;
21
17
 
22
18
  private readonly options: RequestOptions;
23
19
 
@@ -30,29 +26,34 @@ export class CrosspostClient {
30
26
  const timeout = config.timeout || DEFAULT_CONFIG.timeout;
31
27
  const retries = config.retries ?? DEFAULT_CONFIG.retries;
32
28
 
33
- // Try to get auth data from config or cookie
34
- const signature = config.signature || getAuthFromCookie();
29
+ const nearAuthData = config.nearAuthData;
35
30
 
36
31
  this.options = {
37
32
  baseUrl,
38
33
  timeout,
39
34
  retries,
40
- signature,
35
+ nearAuthData,
41
36
  };
42
37
 
43
38
  this.auth = new AuthApi(this.options);
44
39
  this.post = new PostApi(this.options);
40
+ this.activity = new ActivityApi(this.options);
41
+ this.system = new SystemApi(this.options);
45
42
  }
46
43
 
47
44
  /**
48
- * Sets the authentication data (signature) for the client and stores it in a cookie
49
- * @param signature The NEAR authentication data
45
+ * Sets the authentication data (signature) for the client
46
+ * @param nearAuthData The NEAR authentication data
50
47
  */
51
- public async setAuthentication(signature: NearSignatureData): Promise<void> {
52
- // Update the client's auth data
53
- this.options.signature = signature;
48
+ public setAuthentication(nearAuthData: NearAuthData): void {
49
+ this.options.nearAuthData = nearAuthData;
50
+ }
54
51
 
55
- // Store in cookie for persistence
56
- storeAuthInCookie(signature);
52
+ /**
53
+ * Checks if authentication data (signature) exists on client
54
+ * @param signature The NEAR authentication data
55
+ */
56
+ public isAuthenticated(): boolean {
57
+ return !!this.options.nearAuthData;
57
58
  }
58
59
  }
@@ -1,4 +1,4 @@
1
- import type { NearAuthData as NearSignatureData } from 'near-sign-verify';
1
+ import type { NearAuthData } from 'near-sign-verify';
2
2
 
3
3
  /**
4
4
  * Configuration options for the CrosspostClient
@@ -6,13 +6,13 @@ import type { NearAuthData as NearSignatureData } from 'near-sign-verify';
6
6
  export interface CrosspostClientConfig {
7
7
  /**
8
8
  * Base URL for the Crosspost API
9
- * @default 'https://api.opencrosspost.com'
9
+ * @default 'https://open-crosspost-proxy.deno.dev'
10
10
  */
11
11
  baseUrl?: string;
12
12
  /**
13
13
  * NEAR authentication data obtained from near-sign-verify
14
14
  */
15
- signature?: NearSignatureData;
15
+ nearAuthData?: NearAuthData;
16
16
  /**
17
17
  * Request timeout in milliseconds
18
18
  * @default 30000
@@ -28,7 +28,7 @@ export interface CrosspostClientConfig {
28
28
  /**
29
29
  * Default configuration values for the CrosspostClient
30
30
  */
31
- export const DEFAULT_CONFIG: Required<Omit<CrosspostClientConfig, 'signature'>> = {
31
+ export const DEFAULT_CONFIG: Required<Omit<CrosspostClientConfig, 'nearAuthData'>> = {
32
32
  baseUrl: 'https://open-crosspost-proxy.deno.dev/',
33
33
  timeout: 30000,
34
34
  retries: 2,
@@ -1,7 +1,6 @@
1
1
  import { ApiError, ApiErrorCode } from '@crosspost/types';
2
- import { createAuthToken, type NearAuthData as NearSignatureData } from 'near-sign-verify';
3
- import { createNetworkError, handleErrorResponse } from '../utils/error.ts';
4
- import { CSRF_HEADER_NAME, getCsrfToken } from '../utils/cookie.ts';
2
+ import { createAuthToken, type NearAuthData } from 'near-sign-verify';
3
+ import { apiWrapper, createNetworkError, handleErrorResponse } from '../utils/error.ts';
5
4
 
6
5
  /**
7
6
  * Options for making a request to the API
@@ -15,7 +14,7 @@ export interface RequestOptions {
15
14
  * NEAR authentication data for generating auth tokens
16
15
  * Can be undefined if not authorize yet
17
16
  */
18
- signature?: NearSignatureData;
17
+ nearAuthData?: NearAuthData;
19
18
  /**
20
19
  * Request timeout in milliseconds
21
20
  */
@@ -40,119 +39,136 @@ export async function makeRequest<T>(
40
39
  path: string,
41
40
  options: RequestOptions,
42
41
  data?: any,
42
+ query?: Record<string, any>,
43
43
  ): Promise<T> {
44
- const url = `${options.baseUrl}${path.startsWith('/') ? path : `/${path}`}`;
45
- let lastError: Error | null = null;
46
-
44
+ let url = `${options.baseUrl}${path.startsWith('/') ? path : `/${path}`}`;
45
+
46
+ // Add query parameters if provided
47
+ if (query && Object.keys(query).length > 0) {
48
+ const queryParams = new URLSearchParams();
49
+ for (const [key, value] of Object.entries(query)) {
50
+ if (value !== undefined && value !== null) {
51
+ queryParams.append(key, String(value));
52
+ }
53
+ }
54
+ const queryString = queryParams.toString();
55
+ if (queryString) {
56
+ url += `?${queryString}`;
57
+ }
58
+ }
47
59
  // Check if authentication data is available
48
- if (!options.signature) {
60
+ if (!options.nearAuthData) {
49
61
  throw ApiError.unauthorized('Authentication required. Please provide NEAR signature.');
50
62
  }
51
63
 
52
- for (let attempt = 0; attempt <= options.retries; attempt++) {
53
- const controller = new AbortController();
54
- const timeoutId = setTimeout(() => controller.abort(), options.timeout);
55
-
56
- try {
57
- const headers: Record<string, string> = {
58
- 'Content-Type': 'application/json',
59
- 'Accept': 'application/json',
60
- 'Authorization': `Bearer ${createAuthToken(options.signature)}`,
61
- };
62
-
63
- // Add CSRF token for state-changing requests (non-GET)
64
- if (method !== 'GET') {
65
- const csrfToken = getCsrfToken();
66
- if (csrfToken) {
67
- headers[CSRF_HEADER_NAME] = csrfToken;
68
- }
69
- }
64
+ // Create a context object for error enrichment
65
+ const context = {
66
+ method,
67
+ path,
68
+ url,
69
+ retries: options.retries,
70
+ };
70
71
 
71
- const requestOptions: RequestInit = {
72
- method,
73
- headers,
74
- body: method !== 'GET' && data ? JSON.stringify(data) : undefined,
75
- signal: controller.signal,
76
- };
72
+ return apiWrapper(async () => {
73
+ let lastError: Error | null = null;
77
74
 
78
- const response = await fetch(url, requestOptions);
79
- clearTimeout(timeoutId); // Clear timeout if fetch completes
75
+ for (let attempt = 0; attempt <= options.retries; attempt++) {
76
+ const controller = new AbortController();
77
+ const timeoutId = setTimeout(() => controller.abort(), options.timeout);
80
78
 
81
- let responseData: any;
82
79
  try {
83
- responseData = await response.json();
84
- } catch (jsonError) {
85
- // If JSON parsing fails, throw a specific error or handle based on status
86
- if (!response.ok) {
80
+ const headers: Record<string, string> = {
81
+ 'Content-Type': 'application/json',
82
+ 'Accept': 'application/json',
83
+ 'Authorization': `Bearer ${createAuthToken(options.nearAuthData!)}`,
84
+ };
85
+
86
+ const requestOptions: RequestInit = {
87
+ method,
88
+ headers,
89
+ body: method !== 'GET' && data ? JSON.stringify(data) : undefined,
90
+ signal: controller.signal,
91
+ };
92
+
93
+ const response = await fetch(url, requestOptions);
94
+ clearTimeout(timeoutId); // Clear timeout if fetch completes
95
+
96
+ let responseData: any;
97
+ try {
98
+ responseData = await response.json();
99
+ } catch (jsonError) {
100
+ // If JSON parsing fails, throw a specific error or handle based on status
101
+ if (!response.ok) {
102
+ throw new ApiError(
103
+ `API request failed with status ${response.status} and non-JSON response`,
104
+ ApiErrorCode.NETWORK_ERROR,
105
+ response.status as any,
106
+ { originalStatusText: response.statusText },
107
+ );
108
+ }
109
+ // If response was ok but JSON failed, maybe it was an empty 204 response?
110
+ if (response.status === 204) return {} as T; // Handle No Content
111
+ // Otherwise, throw a custom error
87
112
  throw new ApiError(
88
- `API request failed with status ${response.status} and non-JSON response`,
89
- ApiErrorCode.NETWORK_ERROR, // Or a more specific code
113
+ `Failed to parse JSON response: ${
114
+ jsonError instanceof Error ? jsonError.message : String(jsonError)
115
+ }`,
116
+ ApiErrorCode.INTERNAL_ERROR,
90
117
  response.status as any,
91
- { originalStatusText: response.statusText },
92
118
  );
93
119
  }
94
- // If response was ok but JSON failed, maybe it was an empty 204 response?
95
- if (response.status === 204) return {} as T; // Handle No Content
96
- // Otherwise, rethrow JSON parse error or a custom error
97
- throw new ApiError(
98
- `Failed to parse JSON response: ${
99
- jsonError instanceof Error ? jsonError.message : String(jsonError)
100
- }`,
101
- ApiErrorCode.INTERNAL_ERROR, // Or NETWORK_ERROR?
102
- response.status as any,
103
- );
104
- }
105
120
 
106
- if (!response.ok) {
107
- lastError = handleErrorResponse(responseData, response.status);
108
- // Retry only on 5xx errors or potentially recoverable errors if defined
109
- const shouldRetry = response.status >= 500 ||
110
- (lastError instanceof ApiError && lastError.recoverable);
111
- if (shouldRetry && attempt < options.retries) {
112
- await new Promise((resolve) => setTimeout(resolve, 1000 * Math.pow(2, attempt))); // Exponential backoff
113
- continue; // Retry
121
+ if (!response.ok) {
122
+ lastError = handleErrorResponse(responseData, response.status);
123
+ // Retry only on 5xx errors or potentially recoverable errors if defined
124
+ const shouldRetry = response.status >= 500 ||
125
+ (lastError instanceof ApiError && lastError.recoverable);
126
+ if (shouldRetry && attempt < options.retries) {
127
+ await new Promise((resolve) => setTimeout(resolve, 1000 * Math.pow(2, attempt))); // Exponential backoff
128
+ continue; // Retry
129
+ }
130
+ throw lastError; // Throw error if not retrying or retries exhausted
114
131
  }
115
- throw lastError; // Throw error if not retrying or retries exhausted
116
- }
117
132
 
118
- // Handle cases where API indicates failure within a 2xx response
119
- if (
120
- responseData && typeof responseData === 'object' && 'success' in responseData &&
121
- !responseData.success && responseData.error
122
- ) {
123
- lastError = handleErrorResponse(responseData, response.status);
124
- // Decide if this specific type of "successful" response with an error payload should be retried
125
- const shouldRetry = lastError instanceof ApiError && lastError.recoverable;
126
- if (shouldRetry && attempt < options.retries) {
133
+ // Handle cases where API indicates failure within a 2xx response
134
+ if (
135
+ responseData && typeof responseData === 'object' && 'success' in responseData &&
136
+ !responseData.success && responseData.error
137
+ ) {
138
+ lastError = handleErrorResponse(responseData, response.status);
139
+ // Decide if this specific type of "successful" response with an error payload should be retried
140
+ const shouldRetry = lastError instanceof ApiError && lastError.recoverable;
141
+ if (shouldRetry && attempt < options.retries) {
142
+ await new Promise((resolve) => setTimeout(resolve, 1000 * Math.pow(2, attempt))); // Exponential backoff
143
+ continue; // Retry
144
+ }
145
+ throw lastError;
146
+ }
147
+
148
+ return responseData as T;
149
+ } catch (error) {
150
+ clearTimeout(timeoutId); // Clear timeout on error
151
+ lastError = error instanceof Error ? error : new Error(String(error)); // Store the error
152
+
153
+ // Handle fetch/network errors specifically for retries
154
+ const isNetworkError = error instanceof TypeError ||
155
+ (error instanceof DOMException && error.name === 'AbortError');
156
+ if (isNetworkError && attempt < options.retries) {
127
157
  await new Promise((resolve) => setTimeout(resolve, 1000 * Math.pow(2, attempt))); // Exponential backoff
128
- continue; // Retry
158
+ continue; // Retry network error
129
159
  }
130
- throw lastError;
131
- }
132
160
 
133
- return responseData as T; // Success
134
- } catch (error) {
135
- clearTimeout(timeoutId); // Clear timeout on error
136
- lastError = error as Error; // Store the error
137
-
138
- // Handle fetch/network errors specifically for retries
139
- const isNetworkError = error instanceof TypeError ||
140
- (error instanceof DOMException && error.name === 'AbortError');
141
- if (isNetworkError && attempt < options.retries) {
142
- await new Promise((resolve) => setTimeout(resolve, 1000 * Math.pow(2, attempt))); // Exponential backoff
143
- continue; // Retry network error
144
- }
161
+ // If it's not a known ApiError/PlatformError, wrap it
162
+ if (!(error instanceof ApiError)) {
163
+ throw createNetworkError(error, url, options.timeout);
164
+ }
145
165
 
146
- // If it's not a known ApiError/PlatformError, wrap it
147
- if (!(error instanceof ApiError)) {
148
- throw createNetworkError(error, url, options.timeout);
166
+ throw error; // Re-throw known ApiError or final network error
149
167
  }
150
-
151
- throw error; // Re-throw known ApiError or final network error
152
168
  }
153
- }
154
169
 
155
- // Should not be reachable if retries >= 0, but needed for type safety
156
- throw lastError ||
157
- new ApiError('Request failed after multiple retries', ApiErrorCode.INTERNAL_ERROR, 500);
170
+ // Should not be reachable if retries >= 0, but needed for type safety
171
+ throw lastError ||
172
+ new ApiError('Request failed after multiple retries', ApiErrorCode.INTERNAL_ERROR, 500);
173
+ }, context);
158
174
  }
package/src/index.ts CHANGED
@@ -4,25 +4,35 @@
4
4
  */
5
5
 
6
6
  // Export main client
7
- export { CrosspostClient } from './core/client.js';
8
- export { CrosspostClientConfig } from './core/config.js';
7
+ export { CrosspostClient } from './core/client.ts';
8
+ export type { CrosspostClientConfig } from './core/config.ts';
9
9
 
10
10
  // Export API modules for advanced usage
11
- export { AuthApi } from './api/auth.js';
12
- export { PostApi } from './api/post.js';
11
+ export { ActivityApi } from './api/activity.ts';
12
+ export { AuthApi } from './api/auth.ts';
13
+ export { PostApi } from './api/post.ts';
14
+ export { SystemApi } from './api/system.ts';
13
15
 
14
16
  // Export utility functions
15
- export { createNetworkError, handleErrorResponse } from './utils/error.js';
16
17
  export {
17
- AUTH_COOKIE_NAME,
18
- AUTH_COOKIE_OPTIONS,
19
- clearAuthCookie,
20
- CSRF_COOKIE_NAME,
21
- CSRF_HEADER_NAME,
22
- getAuthFromCookie,
23
- getCsrfToken,
24
- storeAuthInCookie,
25
- } from './utils/cookie.js';
18
+ apiWrapper,
19
+ createNetworkError,
20
+ enrichErrorWithContext,
21
+ ERROR_CATEGORIES,
22
+ getErrorDetails,
23
+ getErrorMessage,
24
+ handleErrorResponse,
25
+ isAuthError,
26
+ isContentError,
27
+ isErrorOfCategory,
28
+ isMediaError,
29
+ isNetworkError,
30
+ isPlatformError,
31
+ isPostError,
32
+ isRateLimitError,
33
+ isRecoverableError,
34
+ isValidationError,
35
+ } from './utils/error.ts';
26
36
 
27
37
  // Re-export types from @crosspost/types for convenience
28
38
  export * from '@crosspost/types';