@crosspost/sdk 0.1.2 → 0.1.3

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/README.md CHANGED
@@ -2,126 +2,262 @@
2
2
 
3
3
  SDK for interacting with the Crosspost API.
4
4
 
5
- ## Overview
6
-
7
- This package provides a client for interacting with the Crosspost API, allowing you to easily
8
- integrate social media posting capabilities into your applications. The SDK is designed to be
9
- flexible and easy to use, with support for multiple authentication methods and platforms.
10
-
11
- ## Features
12
-
13
- - Unified client for all supported platforms
14
- - Flexible authentication:
15
- - Direct `nearAuthData` injection
16
- - Automatic cookie-based authentication (`__crosspost_auth`)
17
- - Explicit authentication via `setAuthentication` method
18
- - Platform-specific clients (Twitter, with more to come)
19
- - Type-safe request/response handling using `@crosspost/types`
20
- - Comprehensive error handling with specific `ApiError` and `PlatformError` types
21
- - CSRF protection via Double Submit Cookie pattern
22
-
23
5
  ## Installation
24
6
 
25
7
  ```bash
26
8
  bun install @crosspost/sdk
27
9
  ```
28
10
 
29
- ## Usage
30
-
31
- ### Initializing the SDK
32
-
33
- The SDK can be initialized in several ways depending on how you manage authentication:
34
-
35
- **1. Cookie-Based Authentication (Recommended for Browsers)**
36
-
37
- If the user has previously authenticated via the API, the SDK can automatically use the
38
- authentication data stored in the `__crosspost_auth` cookie.
11
+ ## Quick Start
39
12
 
40
13
  ```typescript
41
- import { CrosspostClient } from '@crosspost/sdk';
14
+ import { ApiError, CrosspostClient, isAuthError, PlatformError } from '@crosspost/sdk';
42
15
 
43
- // Client will automatically try to load auth from the cookie
16
+ // Initialize the client with direct authentication
44
17
  const client = new CrosspostClient({
45
18
  baseUrl: 'https://your-crosspost-api.com', // Optional: Defaults to official API
19
+ nearAuthData: {
20
+ accountId: 'your-account.near',
21
+ publicKey: 'ed25519:...',
22
+ signature: '...',
23
+ message: '...',
24
+ },
46
25
  });
47
26
 
48
- // If the cookie exists and is valid, requests will be authenticated.
49
- // If not, authenticated requests will throw an ApiError.
50
- ```
27
+ // Or initialize with cookie-based authentication (will auto-load from cookie if available)
28
+ const cookieClient = new CrosspostClient({
29
+ baseUrl: 'https://your-crosspost-api.com',
30
+ // No nearAuthData provided - will check for cookie
31
+ });
51
32
 
52
- The cookie is stored with secure attributes:
33
+ // Set authentication explicitly (also stores in secure cookie)
34
+ await cookieClient.setAuthentication({
35
+ accountId: 'your-account.near',
36
+ publicKey: 'ed25519:...',
37
+ signature: '...',
38
+ message: '...',
39
+ });
53
40
 
54
- - `secure: true` - Only sent over HTTPS
55
- - `sameSite: 'lax'` - Provides CSRF protection while allowing top-level navigation
56
- - `path: '/'` - Available across the entire domain
57
- - `expires: 30 days` - Persists for 30 days
41
+ // Check if client is authenticated
42
+ if (cookieClient.isAuthenticated()) {
43
+ console.log('Client is authenticated');
44
+ }
58
45
 
59
- **2. Direct Authentication**
46
+ // NEAR Account Authorization
47
+ async function authorizeNearAccount() {
48
+ try {
49
+ // Authorize with NEAR account
50
+ const authResponse = await client.auth.authorizeNearAccount();
51
+ console.log('NEAR authorization successful');
52
+ console.log('Account ID:', authResponse.accountId);
53
+ console.log('Status:', authResponse.status);
54
+ console.log('Connected platforms:', authResponse.connectedPlatforms);
55
+ return true;
56
+ } catch (error) {
57
+ console.error('NEAR authorization failed');
58
+ if (error instanceof ApiError) {
59
+ console.error('Error code:', error.code);
60
+ console.error('Status:', error.status);
61
+ console.error('Details:', error.details);
62
+ console.error('Recoverable:', error.recoverable);
63
+ }
64
+ return false;
65
+ }
66
+ }
60
67
 
61
- Provide the `nearAuthData` object directly if you have obtained it through other means (e.g.,
62
- server-side flow, manual signing).
68
+ // Unauthorize NEAR Account
69
+ async function unauthorizeNearAccount() {
70
+ try {
71
+ // Unauthorize NEAR account (removes all platform connections)
72
+ const response = await client.auth.unauthorizeNearAccount();
73
+ console.log('NEAR account unauthorized');
74
+ console.log('Status:', response.status);
75
+ console.log('Message:', response.message);
76
+ return true;
77
+ } catch (error) {
78
+ console.error('Failed to unauthorize NEAR account');
79
+ if (error instanceof ApiError) {
80
+ console.error('Error code:', error.code);
81
+ console.error('Status:', error.status);
82
+ console.error('Details:', error.details);
83
+ }
84
+ return false;
85
+ }
86
+ }
63
87
 
64
- ```typescript
65
- import { CrosspostClient } from '@crosspost/sdk';
66
- import type { NearAuthData } from 'near-sign-verify'; // Assuming this type exists
88
+ // Revoke Platform Authorization
89
+ async function revokePlatformAuth(platform) {
90
+ try {
91
+ // Revoke specific platform authorization
92
+ const response = await client.auth.revokeAuth(platform);
93
+ console.log(`${platform} authorization revoked`);
94
+ console.log('Status:', response.status);
95
+ console.log('Platform:', response.platform);
96
+ console.log('Message:', response.message);
97
+ return true;
98
+ } catch (error) {
99
+ console.error(`Failed to revoke ${platform} authorization`);
100
+ if (error instanceof ApiError) {
101
+ console.error('Error code:', error.code);
102
+ console.error('Status:', error.status);
103
+ console.error('Details:', error.details);
104
+ }
105
+ return false;
106
+ }
107
+ }
67
108
 
68
- const nearAuthData: NearAuthData = {
69
- account_id: 'example.near',
70
- public_key: 'ed25519:...',
71
- signature: '...',
72
- message: '...',
73
- nonce: '...',
74
- recipient: 'crosspost-api.near',
75
- // callback_url and state are optional
76
- };
109
+ // Example usage
110
+ async function createPost() {
111
+ try {
112
+ const response = await client.post.createPost({
113
+ content: {
114
+ text: 'Hello from Crosspost SDK!',
115
+ },
116
+ });
117
+ console.log('Post created successfully');
118
+ console.log('Post ID:', response.id);
119
+ console.log('Platform:', response.platform);
120
+ console.log('URL:', response.url);
121
+ console.log('Created at:', response.createdAt);
122
+ } catch (error) {
123
+ // Check if it's an authentication error
124
+ if (isAuthError(error)) {
125
+ console.error('Authentication required. Attempting to authorize...');
126
+ const authorized = await authorizeNearAccount();
127
+ if (authorized) {
128
+ // Retry the operation
129
+ return createPost();
130
+ }
131
+ } else {
132
+ // Handle other error types
133
+ console.error('Error creating post:', error);
134
+ if (error instanceof ApiError) {
135
+ console.error('Error code:', error.code);
136
+ console.error('Status:', error.status);
137
+ console.error('Details:', error.details);
138
+ console.error('Recoverable:', error.recoverable);
139
+ } else if (error instanceof PlatformError) {
140
+ console.error('Platform:', error.platform);
141
+ console.error('Error code:', error.code);
142
+ console.error('Original error:', error.originalError);
143
+ }
144
+ }
145
+ }
146
+ }
147
+ ```
77
148
 
78
- const client = new CrosspostClient({
79
- baseUrl: 'https://your-crosspost-api.com',
80
- nearAuthData: nearAuthData,
81
- });
149
+ ## API Reference
150
+
151
+ ### CrosspostClient
152
+
153
+ ```typescript
154
+ constructor(config?: {
155
+ baseUrl?: string;
156
+ nearAuthData?: NearAuthData;
157
+ timeout?: number;
158
+ retries?: number;
159
+ })
82
160
  ```
83
161
 
84
- **3. Explicit Authentication**
162
+ #### Methods
163
+
164
+ - `setAuthentication(nearAuthData: NearAuthData): Promise<void>` - Sets authentication data
165
+ - `isAuthenticated(): boolean` - Checks if client is authenticated
166
+
167
+ ### Auth API (client.auth)
168
+
169
+ - `authorizeNearAccount(): Promise<NearAuthorizationResponse>` - Authorizes NEAR account
170
+ - `unauthorizeNearAccount(): Promise<NearAuthorizationResponse>` - Unauthorizes NEAR account
171
+ - `getNearAuthorizationStatus(): Promise<NearAuthorizationResponse>` - Checks authorization status
172
+ - `loginToPlatform(platform, options?): Promise<EnhancedApiResponse<any>>` - Initiates OAuth flow
173
+ - `refreshToken(platform): Promise<EnhancedApiResponse<any>>` - Refreshes platform token
174
+ - `refreshProfile(platform): Promise<EnhancedApiResponse<any>>` - Refreshes user profile
175
+ - `getAuthStatus(platform): Promise<AuthStatusResponse>` - Gets authentication status
176
+ - `revokeAuth(platform): Promise<AuthRevokeResponse>` - Revokes platform access
177
+ - `getConnectedAccounts(): Promise<ConnectedAccountsResponse>` - Lists connected accounts
178
+
179
+ ### Post API (client.post)
180
+
181
+ - `createPost(request): Promise<CreatePostResponse>` - Creates a new post
182
+ - `repost(request): Promise<RepostResponse>` - Reposts an existing post
183
+ - `quotePost(request): Promise<QuotePostResponse>` - Quotes an existing post
184
+ - `replyToPost(request): Promise<ReplyToPostResponse>` - Replies to a post
185
+ - `likePost(request): Promise<LikePostResponse>` - Likes a post
186
+ - `unlikePost(request): Promise<UnlikePostResponse>` - Unlikes a post
187
+ - `deletePost(request): Promise<DeletePostResponse>` - Deletes a post
188
+
189
+ ### Activity API (client.activity)
190
+
191
+ - `getLeaderboard(options): Promise<LeaderboardResponse>` - Gets activity leaderboard
192
+ - `getAccountActivity(signerId, options): Promise<AccountActivityResponse>` - Gets account activity
193
+ - `getAccountPosts(signerId, options): Promise<AccountPostsResponse>` - Gets account posts
85
194
 
86
- Initialize the client without authentication and set it later, for example, after a user logs in via
87
- a NEAR wallet connection. This method also stores the authentication data in the `__crosspost_auth`
88
- cookie for future use.
195
+ ### System API (client.system)
196
+
197
+ - `getRateLimits(): Promise<RateLimitsResponse>` - Gets all rate limits
198
+ - `getEndpointRateLimit(endpoint): Promise<EndpointRateLimitResponse>` - Gets endpoint rate limit
199
+ - `getHealthStatus(): Promise<HealthStatusResponse>` - Gets API health status
200
+
201
+ ### Error Handling Utilities
89
202
 
90
203
  ```typescript
91
- import { CrosspostClient } from '@crosspost/sdk';
92
- import type { NearAuthData } from 'near-sign-verify';
204
+ import {
205
+ apiWrapper,
206
+ enrichErrorWithContext,
207
+ getErrorDetails,
208
+ getErrorMessage,
209
+ isAuthError,
210
+ isContentError,
211
+ isMediaError,
212
+ isNetworkError,
213
+ isPlatformError,
214
+ isPostError,
215
+ isRateLimitError,
216
+ isRecoverableError,
217
+ isValidationError,
218
+ } from '@crosspost/sdk';
219
+
220
+ // Check error types
221
+ if (isAuthError(error)) {
222
+ // Handle authentication errors
223
+ }
93
224
 
94
- const client = new CrosspostClient({
95
- baseUrl: 'https://your-crosspost-api.com',
225
+ // Get user-friendly error message
226
+ const message = getErrorMessage(error, 'Default message');
227
+
228
+ // Get error details
229
+ const details = getErrorDetails(error);
230
+
231
+ // Add context to errors
232
+ const enrichedError = enrichErrorWithContext(error, {
233
+ operation: 'createPost',
234
+ timestamp: Date.now(),
96
235
  });
97
236
 
98
- // Later, after obtaining the signature...
99
- async function handleAuthentication(nearAuthData: NearAuthData) {
100
- try {
101
- // This sets the auth data in the client and stores it in the cookie
102
- await client.setAuthentication(nearAuthData);
103
- console.log('Authentication successful and stored.');
104
- // Client is now ready for authenticated requests
105
- } catch (error) {
106
- console.error('Authentication failed:', error);
107
- }
108
- }
237
+ // Wrap API calls with error handling
238
+ const result = await apiWrapper(
239
+ async () => {
240
+ // API call implementation
241
+ return await fetch('/api/endpoint');
242
+ },
243
+ { operation: 'fetchData' }, // Optional context
244
+ );
109
245
  ```
110
246
 
111
- ### Making Authenticated Requests (Example: Twitter Client)
247
+ ## Usage Examples
248
+
249
+ ### Creating a Post
112
250
 
113
251
  ```typescript
114
- // Create a post
115
- const createPostResponse = await client.twitter.createPost({
252
+ // Create a simple text post
253
+ const textPostResponse = await client.post.createPost({
116
254
  content: {
117
255
  text: 'Hello from Crosspost SDK!',
118
256
  },
119
257
  });
120
258
 
121
- console.log(`Post created with ID: ${createPostResponse.id}`);
122
-
123
259
  // Create a post with media
124
- const createPostWithMediaResponse = await client.twitter.createPost({
260
+ const mediaPostResponse = await client.post.createPost({
125
261
  content: {
126
262
  text: 'Check out this image!',
127
263
  media: [
@@ -132,19 +268,23 @@ const createPostWithMediaResponse = await client.twitter.createPost({
132
268
  ],
133
269
  },
134
270
  });
271
+ ```
272
+
273
+ ### Post Interactions
135
274
 
275
+ ```typescript
136
276
  // Like a post
137
- await client.twitter.likePost({
277
+ await client.post.likePost({
138
278
  postId: '1234567890',
139
279
  });
140
280
 
141
- // Repost a post
142
- await client.twitter.repost({
281
+ // Repost
282
+ await client.post.repost({
143
283
  postId: '1234567890',
144
284
  });
145
285
 
146
286
  // Reply to a post
147
- await client.twitter.reply({
287
+ await client.post.replyToPost({
148
288
  postId: '1234567890',
149
289
  content: {
150
290
  text: 'This is a reply!',
@@ -152,122 +292,93 @@ await client.twitter.reply({
152
292
  });
153
293
 
154
294
  // Delete a post
155
- await client.twitter.deletePost({
295
+ await client.post.deletePost({
156
296
  postId: '1234567890',
157
297
  });
158
298
  ```
159
299
 
160
- ### Error Handling
300
+ ### Getting Activity Data
161
301
 
162
302
  ```typescript
163
- import { ApiError, ApiErrorCode, CrosspostClient, PlatformError } from '@crosspost/sdk';
164
-
165
- try {
166
- // Ensure client is authenticated (either via cookie or direct data)
167
- const response = await client.post.createPost({ // Assuming a generic post API exists
168
- targets: [{ platform: 'twitter', userId: 'twitter_user_id' }], // Example target
169
- content: {
170
- text: 'Hello from Crosspost SDK!',
171
- },
172
- });
173
-
174
- console.log(`Post created with ID: ${response.id}`);
175
- } catch (error) {
176
- if (error instanceof ApiError) {
177
- // Handle authentication errors
178
- if (error.code === ApiErrorCode.UNAUTHORIZED) {
179
- console.error('Authentication required. Please sign in with your NEAR wallet.');
180
- // Redirect to authentication flow or show login UI
181
- } else {
182
- console.error(`API Error: ${error.message}`);
183
- console.error(`Error Code: ${error.code}`); // e.g., RATE_LIMITED
184
- console.error(`Status: ${error.status}`); // HTTP status code
185
- console.error(`Details:`, error.details); // Additional context
186
- console.error(`Recoverable: ${error.recoverable}`);
187
- }
188
- } else if (error instanceof PlatformError) {
189
- // Handle errors specific to a platform (e.g., Twitter API error)
190
- console.error(`Platform Error (${error.platform}): ${error.message}`);
191
- console.error(`Original Error:`, error.originalError);
192
- } else {
193
- console.error(`Unexpected error:`, error);
194
- }
195
- }
196
- ```
197
-
198
- ## API Reference
303
+ // Get leaderboard
304
+ const leaderboard = await client.activity.getLeaderboard({
305
+ timeframe: 'week',
306
+ limit: 10,
307
+ });
199
308
 
200
- ### `CrosspostClient`
309
+ // Get account activity
310
+ const activity = await client.activity.getAccountActivity('user.near', {
311
+ timeframe: 'month',
312
+ });
201
313
 
202
- The main client for interacting with the Crosspost API.
314
+ // Get account posts
315
+ const posts = await client.activity.getAccountPosts('user.near', {
316
+ limit: 20,
317
+ offset: 0,
318
+ });
319
+ ```
203
320
 
204
- #### Constructor
321
+ ### Checking Rate Limits
205
322
 
206
323
  ```typescript
207
- constructor(config?: CrosspostClientConfig)
208
- ```
324
+ // Get all rate limits
325
+ const rateLimits = await client.system.getRateLimits();
209
326
 
210
- `CrosspostClientConfig` Options:
327
+ // Get rate limit for a specific endpoint
328
+ const postRateLimit = await client.system.getEndpointRateLimit('post');
329
+ ```
211
330
 
212
- - `baseUrl?: string`: Base URL of the Crosspost API. Defaults to the official endpoint.
213
- - `nearAuthData?: NearAuthData`: NEAR authentication data object. If not provided, the client
214
- attempts to load from the `__crosspost_auth` cookie.
215
- - `timeout?: number`: Request timeout in milliseconds (default: 30000).
216
- - `retries?: number`: Number of retries for failed requests (network/5xx errors) (default: 2).
331
+ ## Authentication and Security
217
332
 
218
- #### Methods
333
+ ### Authentication Strategies
219
334
 
220
- - `setAuthentication(nearAuthData: NearAuthData): Promise<void>`: Sets the provided `NearAuthData`
221
- in the client and stores it in the `__crosspost_auth` cookie for future use.
335
+ The SDK supports two authentication strategies:
222
336
 
223
- #### Properties
337
+ 1. **Direct Authentication**: Provide `nearAuthData` directly in the constructor.
338
+ ```typescript
339
+ const client = new CrosspostClient({
340
+ nearAuthData: {
341
+ accountId: 'your-account.near',
342
+ publicKey: 'ed25519:...',
343
+ signature: '...',
344
+ message: '...',
345
+ },
346
+ });
347
+ ```
224
348
 
225
- - `auth`: Instance of `AuthApi` for authentication-related operations.
226
- - `post`: Instance of `PostApi` for post-related operations.
349
+ 2. **Cookie-Based Authentication**: Automatically read/write authentication data from a secure
350
+ cookie.
351
+ ```typescript
352
+ // Initialize without auth data (will check for cookie)
353
+ const client = new CrosspostClient();
227
354
 
228
- ### API Modules
355
+ // Set authentication (also stores in cookie)
356
+ client.setAuthentication(nearAuthData);
357
+ ```
229
358
 
230
- #### `AuthApi` (`client.auth`)
359
+ ### Cookie Security
231
360
 
232
- - `authorizeNearAccount(): Promise<NearAuthorizationResponse>`: Authorizes the current NEAR account
233
- (requires `nearAuthData` to be set).
234
- - `getNearAuthorizationStatus(): Promise<NearAuthorizationResponse>`: Checks if the current NEAR
235
- account is authorized.
236
- - `loginToPlatform(platform, options?): Promise<EnhancedApiResponse<any>>`: Initiates the OAuth
237
- login flow for a platform.
238
- - `refreshToken(platform): Promise<EnhancedApiResponse<any>>`: Refreshes the platform token.
239
- - `refreshProfile(platform): Promise<EnhancedApiResponse<any>>`: Refreshes the user's profile from
240
- the platform.
241
- - `getAuthStatus(platform): Promise<AuthStatusResponse>`: Gets the authentication status for a
242
- specific platform.
243
- - `revokeAuth(platform): Promise<AuthRevokeResponse>`: Revokes access for a specific platform.
244
- - `getConnectedAccounts(): Promise<ConnectedAccountsResponse>`: Lists all platform accounts
245
- connected to the NEAR account.
361
+ When using cookie-based authentication, the SDK stores authentication data in a secure cookie with
362
+ the following settings:
246
363
 
247
- #### `PostApi` (`client.post`)
248
-
249
- - `createPost(request: CreatePostRequest): Promise<CreatePostResponse>`: Creates a new post.
250
- - `repost(request: RepostRequest): Promise<RepostResponse>`: Reposts an existing post.
251
- - `quotePost(request: QuotePostRequest): Promise<QuotePostResponse>`: Quotes an existing post.
252
- - `replyToPost(request: ReplyToPostRequest): Promise<ReplyToPostResponse>`: Replies to an existing
253
- post.
254
- - `likePost(request: LikePostRequest): Promise<LikePostResponse>`: Likes a post.
255
- - `unlikePost(request: UnlikePostRequest): Promise<UnlikePostResponse>`: Unlikes a post.
256
- - `deletePost(request: DeletePostRequest): Promise<DeletePostResponse>`: Deletes one or more posts.
364
+ - **Name**: `__crosspost_auth`
365
+ - **Secure**: `true` (only sent over HTTPS)
366
+ - **SameSite**: `Lax` (sent with same-site requests and top-level navigations)
367
+ - **Path**: `/` (available across the entire domain)
368
+ - **Expires**: 30 days
257
369
 
258
370
  ### CSRF Protection
259
371
 
260
- The SDK supports the Double Submit Cookie pattern for CSRF protection:
261
-
262
- 1. The backend API sets a CSRF token in a non-HttpOnly cookie named `XSRF-TOKEN`
263
- 2. The SDK automatically reads this token and includes it in the `X-CSRF-Token` header for all
264
- state-changing requests (non-GET)
265
- 3. The backend API validates that the token in the header matches the token in the cookie
372
+ The SDK implements CSRF protection for state-changing requests (non-GET) using the Double Submit
373
+ Cookie pattern:
266
374
 
267
- This protection is automatically enabled when the backend API is configured to use CSRF tokens.
375
+ 1. The backend API sets a CSRF token in a non-HttpOnly cookie (`XSRF-TOKEN`)
376
+ 2. The SDK reads this token and includes it in the `X-CSRF-Token` header for all state-changing
377
+ requests
378
+ 3. The backend validates that the token in the header matches the token in the cookie
268
379
 
269
- _(Note: Specific platform clients like `client.twitter` might be deprecated in favor of using the
270
- generic `client.post` API with platform targets specified in the request body.)_
380
+ This protection is automatically enabled when using cookie-based authentication and requires no
381
+ additional configuration from the client side.
271
382
 
272
383
  ## License
273
384