@crosspost/sdk 0.1.9 → 0.1.10

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.9",
3
+ "version": "0.1.10",
4
4
  "description": "SDK for interacting with the Crosspost API",
5
5
  "type": "module",
6
6
  "main": "dist/index.cjs",
@@ -36,6 +36,7 @@
36
36
  "author": "crosspost.near",
37
37
  "license": "MIT",
38
38
  "dependencies": {
39
+ "@crosspost/types": "^0.1.9",
39
40
  "near-sign-verify": "^0.1.3"
40
41
  },
41
42
  "devDependencies": {
package/src/api/auth.ts CHANGED
@@ -5,7 +5,6 @@ import type {
5
5
  AuthStatusParams,
6
6
  AuthStatusResponse,
7
7
  AuthTokenRequest,
8
- AuthUrlResponse,
9
8
  ConnectedAccount,
10
9
  ConnectedAccountsResponse,
11
10
  NearAuthorizationRequest,
@@ -14,6 +13,7 @@ import type {
14
13
  Platform,
15
14
  } from '@crosspost/types';
16
15
  import { makeRequest, type RequestOptions } from '../core/request.ts';
16
+ import { openAuthPopup } from '../utils/popup.ts';
17
17
 
18
18
  /**
19
19
  * Authentication-related API operations
@@ -55,22 +55,40 @@ export class AuthApi {
55
55
  }
56
56
 
57
57
  /**
58
- * Initiates the login process for a specific platform.
59
- * The service handles the OAuth flow; this method triggers it.
58
+ * Initiates the login process for a specific platform using a popup window.
60
59
  * @param platform The target platform.
61
60
  * @param options Optional success and error redirect URLs.
62
- * @returns A promise resolving with the response from the service (might indicate success/failure or redirect info).
61
+ * @returns Promise that resolves with the authentication result when the popup completes.
62
+ * @throws Error if popups are blocked or if running in a non-browser environment.
63
63
  */
64
64
  async loginToPlatform(
65
65
  platform: Platform,
66
66
  options?: AuthInitRequest,
67
- ): Promise<AuthUrlResponse> {
68
- return makeRequest<AuthUrlResponse, AuthInitRequest>(
69
- 'POST',
70
- `/auth/${platform}/login`,
71
- this.options,
72
- options || {},
73
- );
67
+ ): Promise<AuthCallbackResponse> {
68
+ // Construct the login URL
69
+ const baseUrl = this.options.baseUrl || '';
70
+ const loginUrl = new URL(`/auth/${platform}/login`, baseUrl);
71
+
72
+ // Add successUrl and errorUrl if provided
73
+ if (options?.successUrl) {
74
+ loginUrl.searchParams.set('successUrl', options.successUrl);
75
+ }
76
+ if (options?.errorUrl) {
77
+ loginUrl.searchParams.set('errorUrl', options.errorUrl);
78
+ }
79
+
80
+ // Open the popup and wait for the result
81
+ const result = await openAuthPopup(loginUrl.toString());
82
+
83
+ if (!result.success || !result.userId) {
84
+ throw new Error(result.error || 'Authentication failed');
85
+ }
86
+
87
+ // Return the result in the expected format
88
+ return {
89
+ platform,
90
+ userId: result.userId,
91
+ };
74
92
  }
75
93
 
76
94
  /**
@@ -146,7 +164,8 @@ export class AuthApi {
146
164
 
147
165
  /**
148
166
  * Lists all accounts connected to the NEAR account.
149
- * @returns A promise resolving with the list of connected accounts.
167
+ * @returns A promise resolving with the connected accounts response containing an array of accounts.
168
+ * @throws {CrosspostError} If the request fails or returns invalid data.
150
169
  */
151
170
  async getConnectedAccounts(): Promise<ConnectedAccountsResponse> {
152
171
  return makeRequest<ConnectedAccountsResponse, never>(
@@ -32,13 +32,20 @@ export interface RequestOptions {
32
32
  }
33
33
 
34
34
  /**
35
- * Makes a request to the API with error handling
35
+ * Makes a request to the API with error handling and data extraction
36
36
  *
37
37
  * @param method The HTTP method
38
38
  * @param path The API path
39
39
  * @param options The request options
40
40
  * @param data Optional request data
41
- * @returns A promise resolving with the response data
41
+ * @param query Optional query parameters
42
+ * @returns A promise resolving with the data field from the API response
43
+ * @throws {CrosspostError}
44
+ * - If the request fails (network error, timeout)
45
+ * - If the response is not valid JSON
46
+ * - If the response does not follow the expected ApiResponse format
47
+ * - If the response indicates success but contains no data
48
+ * - If the response indicates failure (includes error details and metadata)
42
49
  */
43
50
  export async function makeRequest<
44
51
  TResponse,
@@ -154,6 +161,14 @@ export async function makeRequest<
154
161
  }
155
162
 
156
163
  if (responseData.success) {
164
+ if (!responseData.data) {
165
+ throw new CrosspostError(
166
+ 'API returned success but no data',
167
+ ApiErrorCode.INVALID_RESPONSE,
168
+ response.status as StatusCode,
169
+ { responseData },
170
+ );
171
+ }
157
172
  return responseData.data as TResponse;
158
173
  }
159
174
 
@@ -0,0 +1,129 @@
1
+ import type { PlatformName } from '@crosspost/types';
2
+
3
+ // Augment the Window interface
4
+ declare global {
5
+ interface Window {
6
+ innerWidth: number;
7
+ innerHeight: number;
8
+ open(url: string, target: string, features: string): Window | null;
9
+ }
10
+
11
+ interface WindowEventMap {
12
+ message: MessageEvent<AuthCallbackMessage>;
13
+ }
14
+ }
15
+
16
+ interface PopupOptions {
17
+ width?: number;
18
+ height?: number;
19
+ left?: number;
20
+ top?: number;
21
+ }
22
+
23
+ interface AuthCallbackData {
24
+ success: boolean;
25
+ platform: PlatformName;
26
+ userId?: string;
27
+ error?: string;
28
+ error_description?: string;
29
+ }
30
+
31
+ interface AuthCallbackMessage {
32
+ type: 'AUTH_CALLBACK';
33
+ data: AuthCallbackData;
34
+ }
35
+
36
+ /**
37
+ * Opens a popup window and returns a promise that resolves when the authentication is complete
38
+ * @param url The URL to open in the popup
39
+ * @param options Optional popup window dimensions and position
40
+ * @returns Promise that resolves with the authentication result
41
+ * @throws Error if popups are blocked or if running in a non-browser environment
42
+ */
43
+ export function openAuthPopup(url: string, options: PopupOptions = {}): Promise<AuthCallbackData> {
44
+ // Check for browser environment
45
+ if (typeof window === 'undefined') {
46
+ throw new Error('openAuthPopup can only be used in a browser environment');
47
+ }
48
+
49
+ return new Promise((resolve, reject) => {
50
+ // Calculate popup dimensions and position
51
+ const {
52
+ width = 600,
53
+ height = 700,
54
+ left = Math.max(0, (window.innerWidth - 600) / 2),
55
+ top = Math.max(0, (window.innerHeight - 700) / 2),
56
+ } = options;
57
+
58
+ // Open the popup
59
+ const popup = window.open(
60
+ url,
61
+ 'authPopup',
62
+ `width=${width},height=${height},left=${left},top=${top},scrollbars=yes`,
63
+ );
64
+
65
+ if (!popup) {
66
+ reject(new Error('Popup blocked. Please allow popups for this site.'));
67
+ return;
68
+ }
69
+
70
+ let messageReceived = false;
71
+
72
+ // Function to handle messages from the popup with proper typing
73
+ const handleMessage = (event: MessageEvent<AuthCallbackMessage>) => {
74
+ // Verify the message is from our popup and popup exists
75
+ if (!popup || event.source !== popup) {
76
+ return;
77
+ }
78
+
79
+ const message = event.data;
80
+ if (message?.type === 'AUTH_CALLBACK') {
81
+ messageReceived = true;
82
+ window.removeEventListener('message', handleMessage);
83
+ clearInterval(checkClosedInterval);
84
+
85
+ if (message.data.success) {
86
+ resolve(message.data);
87
+ } else {
88
+ reject(message.data);
89
+ }
90
+
91
+ // Give a moment for any final operations before closing
92
+ setTimeout(() => {
93
+ try {
94
+ if (popup && !popup.closed) {
95
+ popup.close();
96
+ }
97
+ } catch (e) {
98
+ console.warn('Failed to close popup window:', e);
99
+ }
100
+ }, 100);
101
+ }
102
+ };
103
+
104
+ // Listen for messages from the popup
105
+ window.addEventListener('message', handleMessage as EventListener);
106
+
107
+ // Check if popup was closed manually
108
+ const checkClosedInterval = setInterval(() => {
109
+ try {
110
+ if (!popup || popup.closed) {
111
+ cleanup();
112
+ }
113
+ } catch (e) {
114
+ console.warn('Error checking popup state:', e);
115
+ cleanup();
116
+ }
117
+ }, 500);
118
+
119
+ // Cleanup function to handle popup closure
120
+ function cleanup() {
121
+ clearInterval(checkClosedInterval);
122
+ window.removeEventListener('message', handleMessage as EventListener);
123
+
124
+ if (!messageReceived) {
125
+ reject(new Error('Authentication cancelled by user.'));
126
+ }
127
+ }
128
+ });
129
+ }