@blinkdotnew/sdk 0.19.4 → 0.19.6

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
@@ -195,48 +195,167 @@ const blink = createClient({
195
195
 
196
196
  The SDK has **first-class React Native support** with platform-aware features that automatically adapt to mobile environments.
197
197
 
198
+ #### Step 1: Install Dependencies
199
+
198
200
  ```bash
199
- npm install @blinkdotnew/sdk @react-native-async-storage/async-storage
201
+ npm install @blinkdotnew/sdk @react-native-async-storage/async-storage expo-web-browser
200
202
  ```
201
203
 
204
+ **Required packages:**
205
+ - `@blinkdotnew/sdk` - The Blink SDK
206
+ - `@react-native-async-storage/async-storage` - For token persistence
207
+ - `expo-web-browser` - For OAuth authentication (Google, GitHub, Apple, etc.)
208
+
209
+ #### Step 2: Create Client (`lib/blink.ts`)
210
+
211
+ **⚠️ IMPORTANT: You MUST pass `webBrowser: WebBrowser` to enable OAuth on mobile!**
212
+
202
213
  ```typescript
214
+ // lib/blink.ts
203
215
  import { createClient, AsyncStorageAdapter } from '@blinkdotnew/sdk'
204
216
  import AsyncStorage from '@react-native-async-storage/async-storage'
217
+ import * as WebBrowser from 'expo-web-browser' // ← Import this
205
218
 
206
- const blink = createClient({
219
+ export const blink = createClient({
207
220
  projectId: 'your-project-id',
208
221
  authRequired: false,
209
- // Use AsyncStorage for secure token persistence on mobile
222
+ auth: {
223
+ mode: 'headless',
224
+ webBrowser: WebBrowser // ← Pass it here! Required for OAuth
225
+ },
210
226
  storage: new AsyncStorageAdapter(AsyncStorage)
211
227
  })
228
+ ```
212
229
 
213
- // All features work seamlessly in React Native!
214
- const { data } = await blink.ai.generateImage({
215
- prompt: 'A beautiful sunset over mountains',
216
- n: 1
230
+ #### Step 3: Use Authentication
231
+
232
+ **Email/Password (works immediately):**
233
+ ```typescript
234
+ // Sign up
235
+ const user = await blink.auth.signUp({
236
+ email: 'user@example.com',
237
+ password: 'SecurePass123'
217
238
  })
239
+
240
+ // Sign in
241
+ const user = await blink.auth.signInWithEmail('user@example.com', 'SecurePass123')
242
+ ```
243
+
244
+ **OAuth (Google, GitHub, Apple, Microsoft):**
245
+ ```typescript
246
+ // ✅ Same code works on web, iOS, AND Android!
247
+ const user = await blink.auth.signInWithGoogle()
248
+ const user = await blink.auth.signInWithGitHub()
249
+ const user = await blink.auth.signInWithApple()
250
+ const user = await blink.auth.signInWithMicrosoft()
251
+ ```
252
+
253
+ #### Step 4: Create Auth Hook (`hooks/useAuth.ts`)
254
+
255
+ ```typescript
256
+ // hooks/useAuth.ts
257
+ import { useEffect, useState } from 'react'
258
+ import { blink } from '@/lib/blink'
259
+ import type { BlinkUser } from '@blinkdotnew/sdk'
260
+
261
+ export function useAuth() {
262
+ const [user, setUser] = useState<BlinkUser | null>(null)
263
+ const [isLoading, setIsLoading] = useState(true)
264
+
265
+ useEffect(() => {
266
+ const unsubscribe = blink.auth.onAuthStateChanged((state) => {
267
+ setUser(state.user)
268
+ setIsLoading(state.isLoading)
269
+ })
270
+ return unsubscribe
271
+ }, [])
272
+
273
+ return {
274
+ user,
275
+ isLoading,
276
+ isAuthenticated: !!user,
277
+ signInWithGoogle: () => blink.auth.signInWithGoogle(),
278
+ signInWithGitHub: () => blink.auth.signInWithGitHub(),
279
+ signInWithApple: () => blink.auth.signInWithApple(),
280
+ signOut: () => blink.auth.signOut(),
281
+ }
282
+ }
283
+ ```
284
+
285
+ #### Step 5: Use in Components
286
+
287
+ ```typescript
288
+ import { useAuth } from '@/hooks/useAuth'
289
+ import { View, Text, Button } from 'react-native'
290
+
291
+ function App() {
292
+ const { user, isLoading, signInWithGoogle, signOut } = useAuth()
293
+
294
+ if (isLoading) return <Text>Loading...</Text>
295
+
296
+ if (!user) {
297
+ return <Button onPress={signInWithGoogle} title="Sign in with Google" />
298
+ }
299
+
300
+ return (
301
+ <View>
302
+ <Text>Welcome, {user.email}!</Text>
303
+ <Button onPress={signOut} title="Sign Out" />
304
+ </View>
305
+ )
306
+ }
218
307
  ```
219
308
 
220
- **Platform-Aware Features:**
221
- - ✅ **AsyncStorage integration** for secure token persistence
222
- - ✅ **Automatic platform detection** - skips browser-only features (analytics tracking, cross-tab sync)
223
- - ✅ **No polyfills needed** - works out of the box
224
- - ✅ **Optimized for mobile** - reduced memory footprint
309
+ #### Common Mistakes
225
310
 
226
- **Configuration Options:**
227
311
  ```typescript
228
- // With AsyncStorage (recommended for token persistence)
312
+ // WRONG: Missing webBrowser - OAuth won't work on mobile!
229
313
  const blink = createClient({
230
314
  projectId: 'your-project-id',
315
+ auth: { mode: 'headless' }, // Missing webBrowser!
231
316
  storage: new AsyncStorageAdapter(AsyncStorage)
232
317
  })
233
318
 
234
- // Without persistence (tokens in memory only)
319
+ // CORRECT: Include webBrowser for OAuth support
320
+ import * as WebBrowser from 'expo-web-browser'
321
+
235
322
  const blink = createClient({
236
- projectId: 'your-project-id'
323
+ projectId: 'your-project-id',
324
+ auth: {
325
+ mode: 'headless',
326
+ webBrowser: WebBrowser // ← Required for OAuth!
327
+ },
328
+ storage: new AsyncStorageAdapter(AsyncStorage)
329
+ })
330
+ ```
331
+
332
+ #### Low-Level OAuth (For Custom Control)
333
+
334
+ If you need custom polling timeouts or manual browser handling:
335
+
336
+ ```typescript
337
+ // Get auth URL and authenticate function separately
338
+ const { authUrl, authenticate } = await blink.auth.signInWithProviderMobile('google')
339
+
340
+ // Open browser manually
341
+ await WebBrowser.openAuthSessionAsync(authUrl)
342
+
343
+ // Poll with custom options
344
+ const user = await authenticate({
345
+ maxAttempts: 120, // 60 seconds (default: 60 = 30 seconds)
346
+ intervalMs: 500 // Check every 500ms
237
347
  })
238
348
  ```
239
349
 
350
+ #### Platform Features
351
+
352
+ - ✅ **AsyncStorage** - Secure token persistence
353
+ - ✅ **Universal OAuth** - Same code works on web + mobile
354
+ - ✅ **expo-web-browser** - Native browser UI
355
+ - ✅ **No deep linking** - Session-based polling
356
+ - ✅ **Works in Expo Go** - No custom dev client needed
357
+ - ✅ **Auto token refresh** - Seamless sessions
358
+
240
359
  ## 📖 API Reference
241
360
 
242
361
  ### Authentication
package/dist/index.d.mts CHANGED
@@ -82,6 +82,49 @@ interface BlinkClientConfig {
82
82
  }
83
83
  interface BlinkAuthConfig {
84
84
  mode?: 'managed' | 'headless';
85
+ /**
86
+ * Automatically detect and extract auth tokens from URL parameters
87
+ *
88
+ * - Web: Set to `true` (default) to handle OAuth redirects
89
+ * - React Native: Set to `false` to use deep links instead
90
+ *
91
+ * @default true
92
+ *
93
+ * @example
94
+ * // React Native - disable URL detection, use deep links
95
+ * {
96
+ * auth: {
97
+ * detectSessionInUrl: false,
98
+ * storage: AsyncStorageAdapter(AsyncStorage)
99
+ * }
100
+ * }
101
+ */
102
+ detectSessionInUrl?: boolean;
103
+ /**
104
+ * WebBrowser module for React Native OAuth (expo-web-browser)
105
+ *
106
+ * When provided, `signInWithGoogle()` and other OAuth methods will
107
+ * automatically use the mobile session-based flow on React Native,
108
+ * so you don't need platform-specific code.
109
+ *
110
+ * @example
111
+ * // React Native setup (configure once)
112
+ * import * as WebBrowser from 'expo-web-browser'
113
+ * import AsyncStorage from '@react-native-async-storage/async-storage'
114
+ *
115
+ * const blink = createClient({
116
+ * projectId: 'your-project',
117
+ * auth: {
118
+ * mode: 'headless',
119
+ * webBrowser: WebBrowser // Pass the module here
120
+ * },
121
+ * storage: new AsyncStorageAdapter(AsyncStorage)
122
+ * })
123
+ *
124
+ * // Now this works on both web and mobile!
125
+ * const user = await blink.auth.signInWithGoogle()
126
+ */
127
+ webBrowser?: WebBrowserModule;
85
128
  email?: {
86
129
  requireVerification?: boolean;
87
130
  allowSignUp?: boolean;
@@ -118,11 +161,7 @@ type AuthProvider = 'email' | 'google' | 'github' | 'apple' | 'microsoft' | 'twi
118
161
  interface AuthOptions {
119
162
  redirectUrl?: string;
120
163
  metadata?: Record<string, any>;
121
- /**
122
- * Force redirect flow instead of popup (useful for Safari or when popups are blocked)
123
- * When true, always uses redirect flow regardless of browser detection
124
- */
125
- forceRedirect?: boolean;
164
+ useMobileSession?: boolean;
126
165
  }
127
166
  interface SignUpData {
128
167
  email: string;
@@ -136,6 +175,22 @@ interface SignUpData {
136
175
  interface MagicLinkOptions {
137
176
  redirectUrl?: string;
138
177
  }
178
+ /**
179
+ * WebBrowser interface for React Native OAuth
180
+ * Compatible with expo-web-browser module
181
+ *
182
+ * Simply pass the expo-web-browser module directly:
183
+ * ```typescript
184
+ * import * as WebBrowser from 'expo-web-browser'
185
+ * const blink = createClient({ auth: { webBrowser: WebBrowser } })
186
+ * ```
187
+ */
188
+ interface WebBrowserModule {
189
+ openAuthSessionAsync(url: string, redirectUrl?: string, options?: unknown): Promise<{
190
+ type: string;
191
+ url?: string;
192
+ }>;
193
+ }
139
194
  interface BlinkUser {
140
195
  id: string;
141
196
  email: string;
@@ -1014,13 +1069,142 @@ declare class BlinkAuth {
1014
1069
  */
1015
1070
  signInWithMicrosoft(options?: AuthOptions): Promise<BlinkUser>;
1016
1071
  /**
1017
- * Check if current browser is Safari (desktop, iPhone, iPad)
1018
- * Safari has strict popup blocking, so we must use redirect flow
1072
+ * Initiate OAuth for mobile without deep linking (expo-web-browser pattern)
1073
+ *
1074
+ * This method:
1075
+ * 1. Generates a unique session ID
1076
+ * 2. Returns OAuth URL with session parameter
1077
+ * 3. App opens URL in expo-web-browser
1078
+ * 4. App polls checkMobileOAuthSession() until complete
1079
+ *
1080
+ * @param provider - OAuth provider (google, github, apple, etc.)
1081
+ * @param options - Optional metadata
1082
+ * @returns Session ID and OAuth URL
1083
+ *
1084
+ * @example
1085
+ * // React Native with expo-web-browser
1086
+ * import * as WebBrowser from 'expo-web-browser';
1087
+ *
1088
+ * const { sessionId, authUrl } = await blink.auth.initiateMobileOAuth('google');
1089
+ *
1090
+ * // Open browser
1091
+ * await WebBrowser.openAuthSessionAsync(authUrl);
1092
+ *
1093
+ * // Poll for completion
1094
+ * const user = await blink.auth.pollMobileOAuthSession(sessionId);
1095
+ * console.log('Authenticated:', user.email);
1096
+ */
1097
+ initiateMobileOAuth(provider: AuthProvider, options?: Omit<AuthOptions, 'redirectUrl'>): Promise<{
1098
+ sessionId: string;
1099
+ authUrl: string;
1100
+ }>;
1101
+ /**
1102
+ * Check mobile OAuth session status (single check)
1103
+ *
1104
+ * @param sessionId - Session ID from initiateMobileOAuth
1105
+ * @returns Tokens if session is complete, null if still pending
1019
1106
  */
1020
- private isSafari;
1107
+ checkMobileOAuthSession(sessionId: string): Promise<AuthTokens | null>;
1108
+ /**
1109
+ * Poll mobile OAuth session until complete (convenience method)
1110
+ *
1111
+ * @param sessionId - Session ID from initiateMobileOAuth
1112
+ * @param options - Polling options
1113
+ * @returns Authenticated user
1114
+ *
1115
+ * @example
1116
+ * const { sessionId, authUrl } = await blink.auth.initiateMobileOAuth('google');
1117
+ * await WebBrowser.openAuthSessionAsync(authUrl);
1118
+ * const user = await blink.auth.pollMobileOAuthSession(sessionId, {
1119
+ * maxAttempts: 60,
1120
+ * intervalMs: 1000
1121
+ * });
1122
+ */
1123
+ pollMobileOAuthSession(sessionId: string, options?: {
1124
+ maxAttempts?: number;
1125
+ intervalMs?: number;
1126
+ }): Promise<BlinkUser>;
1127
+ /**
1128
+ * Sign in with OAuth provider using expo-web-browser (React Native)
1129
+ *
1130
+ * This is a convenience method that handles the entire flow:
1131
+ * 1. Initiates mobile OAuth session
1132
+ * 2. Returns auth URL to open in WebBrowser
1133
+ * 3. Provides polling function to call after browser opens
1134
+ *
1135
+ * @param provider - OAuth provider
1136
+ * @returns Object with authUrl and authenticate function
1137
+ *
1138
+ * @example
1139
+ * import * as WebBrowser from 'expo-web-browser';
1140
+ *
1141
+ * const { authUrl, authenticate } = await blink.auth.signInWithProviderMobile('google');
1142
+ *
1143
+ * // Open browser
1144
+ * await WebBrowser.openAuthSessionAsync(authUrl);
1145
+ *
1146
+ * // Wait for authentication
1147
+ * const user = await authenticate();
1148
+ */
1149
+ signInWithProviderMobile(provider: AuthProvider, options?: Omit<AuthOptions, 'redirectUrl'>): Promise<{
1150
+ authUrl: string;
1151
+ authenticate: () => Promise<BlinkUser>;
1152
+ }>;
1153
+ /**
1154
+ * Sign in with Google using expo-web-browser (React Native convenience)
1155
+ */
1156
+ signInWithGoogleMobile(options?: Omit<AuthOptions, 'redirectUrl'>): Promise<{
1157
+ authUrl: string;
1158
+ authenticate: () => Promise<BlinkUser>;
1159
+ }>;
1160
+ /**
1161
+ * Sign in with GitHub using expo-web-browser (React Native convenience)
1162
+ */
1163
+ signInWithGitHubMobile(options?: Omit<AuthOptions, 'redirectUrl'>): Promise<{
1164
+ authUrl: string;
1165
+ authenticate: () => Promise<BlinkUser>;
1166
+ }>;
1167
+ /**
1168
+ * Sign in with Apple using expo-web-browser (React Native convenience)
1169
+ */
1170
+ signInWithAppleMobile(options?: Omit<AuthOptions, 'redirectUrl'>): Promise<{
1171
+ authUrl: string;
1172
+ authenticate: () => Promise<BlinkUser>;
1173
+ }>;
1174
+ /**
1175
+ * React Native OAuth flow using expo-web-browser (internal)
1176
+ * Automatically handles opening browser and extracting tokens from redirect
1177
+ */
1178
+ private signInWithProviderReactNative;
1021
1179
  /**
1022
1180
  * Generic provider sign-in method (headless mode)
1023
- * Uses redirect flow for Safari, popup flow for other browsers
1181
+ *
1182
+ * Supports both web (popup) and React Native (deep link) OAuth flows.
1183
+ *
1184
+ * **React Native Setup:**
1185
+ * 1. Configure deep linking in your app (app.json):
1186
+ * ```json
1187
+ * { "expo": { "scheme": "com.yourapp" } }
1188
+ * ```
1189
+ *
1190
+ * 2. Add redirect URL in Blink Dashboard:
1191
+ * `com.yourapp://**`
1192
+ *
1193
+ * 3. Handle deep link callbacks:
1194
+ * ```typescript
1195
+ * import * as Linking from 'expo-linking'
1196
+ *
1197
+ * Linking.addEventListener('url', async ({ url }) => {
1198
+ * const { queryParams } = Linking.parse(url)
1199
+ * if (queryParams.access_token) {
1200
+ * await blink.auth.setSession(queryParams)
1201
+ * }
1202
+ * })
1203
+ * ```
1204
+ *
1205
+ * @param provider - OAuth provider (google, github, apple, etc.)
1206
+ * @param options - Optional redirect URL and metadata
1207
+ * @returns Promise that resolves with authenticated user
1024
1208
  */
1025
1209
  signInWithProvider(provider: AuthProvider, options?: AuthOptions): Promise<BlinkUser>;
1026
1210
  /**
@@ -1110,6 +1294,40 @@ declare class BlinkAuth {
1110
1294
  * Manually set tokens (for server-side usage)
1111
1295
  */
1112
1296
  setToken(jwt: string, persist?: boolean): Promise<void>;
1297
+ /**
1298
+ * Manually set auth session from tokens (React Native deep link OAuth)
1299
+ *
1300
+ * Use this method to set the user session after receiving tokens from a deep link callback.
1301
+ * This is the React Native equivalent of automatic URL token detection on web.
1302
+ *
1303
+ * @param tokens - Auth tokens received from deep link or OAuth callback
1304
+ * @param persist - Whether to persist tokens to storage (default: true)
1305
+ *
1306
+ * @example
1307
+ * // React Native: Handle deep link OAuth callback
1308
+ * import * as Linking from 'expo-linking'
1309
+ *
1310
+ * Linking.addEventListener('url', async ({ url }) => {
1311
+ * const { queryParams } = Linking.parse(url)
1312
+ *
1313
+ * if (queryParams.access_token) {
1314
+ * await blink.auth.setSession({
1315
+ * access_token: queryParams.access_token,
1316
+ * refresh_token: queryParams.refresh_token,
1317
+ * expires_in: parseInt(queryParams.expires_in) || 3600,
1318
+ * refresh_expires_in: parseInt(queryParams.refresh_expires_in)
1319
+ * })
1320
+ *
1321
+ * console.log('User authenticated:', blink.auth.currentUser())
1322
+ * }
1323
+ * })
1324
+ */
1325
+ setSession(tokens: {
1326
+ access_token: string;
1327
+ refresh_token?: string;
1328
+ expires_in?: number;
1329
+ refresh_expires_in?: number;
1330
+ }, persist?: boolean): Promise<BlinkUser>;
1113
1331
  /**
1114
1332
  * Refresh access token using refresh token
1115
1333
  */
@@ -1125,11 +1343,6 @@ declare class BlinkAuth {
1125
1343
  private setTokens;
1126
1344
  private clearTokens;
1127
1345
  private getStoredTokens;
1128
- /**
1129
- * Extract URL parameters from both search params and hash fragments
1130
- * Safari OAuth redirects often use hash fragments instead of query params
1131
- */
1132
- private extractUrlParams;
1133
1346
  private extractTokensFromUrl;
1134
1347
  private clearUrlTokens;
1135
1348
  private redirectToAuth;
@@ -1139,6 +1352,10 @@ declare class BlinkAuth {
1139
1352
  * Generate secure random state for OAuth flows
1140
1353
  */
1141
1354
  private generateState;
1355
+ /**
1356
+ * Generate unique session ID for mobile OAuth
1357
+ */
1358
+ private generateSessionId;
1142
1359
  /**
1143
1360
  * Extract magic link token from URL
1144
1361
  */
@@ -2080,4 +2297,4 @@ declare class BlinkRealtimeImpl implements BlinkRealtime {
2080
2297
  onPresence(channelName: string, callback: (users: PresenceUser[]) => void): () => void;
2081
2298
  }
2082
2299
 
2083
- export { type AnalyticsEvent, AsyncStorageAdapter, type AuthState, type AuthStateChangeCallback, type AuthTokens, type BlinkAI, BlinkAIImpl, type BlinkAnalytics, BlinkAnalyticsImpl, type BlinkClient, type BlinkClientConfig, type BlinkData, BlinkDataImpl, BlinkDatabase, type BlinkRealtime, BlinkRealtimeChannel, BlinkRealtimeError, BlinkRealtimeImpl, type BlinkStorage, BlinkStorageImpl, BlinkTable, type BlinkUser, type CreateOptions, type DataExtraction, type FileObject, type FilterCondition, type ImageGenerationRequest, type ImageGenerationResponse, type Message, NoOpStorageAdapter, type ObjectGenerationRequest, type ObjectGenerationResponse, type PresenceUser, type QueryOptions, type RealtimeChannel, type RealtimeGetMessagesOptions, type RealtimeMessage, type RealtimePublishOptions, type RealtimeSubscribeOptions, type SearchRequest, type SearchResponse, type SpeechGenerationRequest, type SpeechGenerationResponse, type StorageAdapter, type StorageUploadOptions, type StorageUploadResponse, type TableOperations, type TextGenerationRequest, type TextGenerationResponse, type TokenUsage, type TranscriptionRequest, type TranscriptionResponse, type UpdateOptions, type UpsertOptions, WebStorageAdapter, createClient, getDefaultStorageAdapter, isBrowser, isNode, isReactNative, isWeb, platform };
2300
+ export { type AnalyticsEvent, AsyncStorageAdapter, type AuthState, type AuthStateChangeCallback, type AuthTokens, type BlinkAI, BlinkAIImpl, type BlinkAnalytics, BlinkAnalyticsImpl, type BlinkClient, type BlinkClientConfig, type BlinkData, BlinkDataImpl, BlinkDatabase, type BlinkRealtime, BlinkRealtimeChannel, BlinkRealtimeError, BlinkRealtimeImpl, type BlinkStorage, BlinkStorageImpl, BlinkTable, type BlinkUser, type CreateOptions, type DataExtraction, type FileObject, type FilterCondition, type ImageGenerationRequest, type ImageGenerationResponse, type Message, NoOpStorageAdapter, type ObjectGenerationRequest, type ObjectGenerationResponse, type PresenceUser, type QueryOptions, type RealtimeChannel, type RealtimeGetMessagesOptions, type RealtimeMessage, type RealtimePublishOptions, type RealtimeSubscribeOptions, type SearchRequest, type SearchResponse, type SpeechGenerationRequest, type SpeechGenerationResponse, type StorageAdapter, type StorageUploadOptions, type StorageUploadResponse, type TableOperations, type TextGenerationRequest, type TextGenerationResponse, type TokenUsage, type TranscriptionRequest, type TranscriptionResponse, type UpdateOptions, type UpsertOptions, type WebBrowserModule, WebStorageAdapter, createClient, getDefaultStorageAdapter, isBrowser, isNode, isReactNative, isWeb, platform };