@blinkdotnew/sdk 0.17.3 → 0.18.0
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 +665 -85
- package/dist/index.d.mts +209 -47
- package/dist/index.d.ts +209 -47
- package/dist/index.js +902 -125
- package/dist/index.mjs +902 -125
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -27,10 +27,22 @@ import { createClient } from '@blinkdotnew/sdk'
|
|
|
27
27
|
|
|
28
28
|
const blink = createClient({
|
|
29
29
|
projectId: 'your-blink-project-id', // From blink.new dashboard
|
|
30
|
-
authRequired:
|
|
30
|
+
authRequired: false // Don't force immediate auth - let users browse first
|
|
31
31
|
})
|
|
32
32
|
|
|
33
|
-
// Authentication (
|
|
33
|
+
// Authentication (flexible) - Two modes available
|
|
34
|
+
|
|
35
|
+
// For websites: Let users browse, require auth for protected areas
|
|
36
|
+
if (!blink.auth.isAuthenticated() && needsAuth) {
|
|
37
|
+
blink.auth.login() // Redirects to blink.new auth page when needed
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// For apps with custom auth UI (headless mode)
|
|
41
|
+
const user = await blink.auth.signUp({ email: 'user@example.com', password: 'secure123' })
|
|
42
|
+
const user = await blink.auth.signInWithEmail('user@example.com', 'secure123')
|
|
43
|
+
const user = await blink.auth.signInWithGoogle()
|
|
44
|
+
|
|
45
|
+
// Current user (works in both modes)
|
|
34
46
|
const user = await blink.auth.me()
|
|
35
47
|
|
|
36
48
|
// Database operations (zero config)
|
|
@@ -110,7 +122,7 @@ blink.analytics.enable()
|
|
|
110
122
|
// Storage operations (instant - returns public URL directly)
|
|
111
123
|
const { publicUrl } = await blink.storage.upload(
|
|
112
124
|
file,
|
|
113
|
-
`avatars/${user.id}.
|
|
125
|
+
`avatars/${user.id}-${Date.now()}.${file.name.split('.').pop()}`, // ✅ Extract original extension
|
|
114
126
|
{ upsert: true }
|
|
115
127
|
)
|
|
116
128
|
```
|
|
@@ -128,9 +140,9 @@ const { publicUrl } = await blink.storage.upload(
|
|
|
128
140
|
|
|
129
141
|
This SDK powers every Blink-generated app with:
|
|
130
142
|
|
|
131
|
-
- **🔐 Authentication**:
|
|
143
|
+
- **🔐 Authentication**: Flexible auth system with managed (redirect) and headless (custom UI) modes, email/password, social providers (Google, GitHub, Apple, Microsoft), magic links, RBAC, and custom email branding
|
|
132
144
|
- **🗄️ Database**: PostgREST-compatible CRUD operations with advanced filtering
|
|
133
|
-
- **🤖 AI**: Text generation with web search, object generation, image creation, speech synthesis, and transcription
|
|
145
|
+
- **🤖 AI**: Text generation with web search, object generation, image creation (Gemini 2.5 Flash), speech synthesis, and transcription
|
|
134
146
|
- **📄 Data**: Extract text content from documents, secure API proxy with secret substitution, web scraping, screenshots, and web search
|
|
135
147
|
- **📁 Storage**: File upload, download, and management
|
|
136
148
|
- **📧 Notifications**: Email sending with attachments, custom branding, and delivery tracking
|
|
@@ -152,10 +164,15 @@ import { createClient } from '@blinkdotnew/sdk'
|
|
|
152
164
|
|
|
153
165
|
const blink = createClient({
|
|
154
166
|
projectId: 'your-blink-project-id', // From blink.new dashboard
|
|
155
|
-
authRequired:
|
|
167
|
+
authRequired: false, // Let users browse first - require auth only for protected areas
|
|
168
|
+
auth: {
|
|
169
|
+
mode: 'managed' // Use new explicit configuration
|
|
170
|
+
}
|
|
156
171
|
})
|
|
157
172
|
```
|
|
158
173
|
|
|
174
|
+
> **⚠️ Version Requirement**: The flexible authentication system (managed vs headless modes) requires SDK version **0.18.0 or higher**. If you're using version 0.17.x or below, you'll only have access to the legacy authentication system. Please upgrade to access the new authentication features.
|
|
175
|
+
|
|
159
176
|
### Server-side (Node.js, Deno, Edge functions)
|
|
160
177
|
|
|
161
178
|
```typescript
|
|
@@ -163,7 +180,7 @@ import { createClient } from '@blinkdotnew/sdk'
|
|
|
163
180
|
|
|
164
181
|
const blink = createClient({
|
|
165
182
|
projectId: 'your-blink-project-id', // From blink.new dashboard
|
|
166
|
-
|
|
183
|
+
auth: { mode: 'managed' } // Manual token management
|
|
167
184
|
})
|
|
168
185
|
|
|
169
186
|
// Set JWT manually
|
|
@@ -174,11 +191,224 @@ blink.auth.setToken(jwtFromHeader)
|
|
|
174
191
|
|
|
175
192
|
### Authentication
|
|
176
193
|
|
|
194
|
+
> **⚠️ Version Requirement**: The flexible authentication system requires SDK version **0.18.0 or higher**. Version 0.17.x and below only support the legacy authentication system.
|
|
195
|
+
|
|
196
|
+
Blink provides **two authentication modes** for maximum flexibility:
|
|
197
|
+
|
|
198
|
+
#### Managed Mode (Default - Redirect-based)
|
|
199
|
+
|
|
200
|
+
Perfect for quick setup with Blink's hosted auth page:
|
|
201
|
+
|
|
202
|
+
```typescript
|
|
203
|
+
// ⚠️ DEPRECATED: authRequired is legacy - use auth.mode instead
|
|
204
|
+
const blink = createClient({
|
|
205
|
+
projectId: 'your-project',
|
|
206
|
+
authRequired: false // Legacy - still works but deprecated
|
|
207
|
+
})
|
|
208
|
+
|
|
209
|
+
// ✅ RECOMMENDED: Use new auth configuration
|
|
210
|
+
const blink = createClient({
|
|
211
|
+
projectId: 'your-project',
|
|
212
|
+
auth: {
|
|
213
|
+
mode: 'managed', // Explicit mode configuration
|
|
214
|
+
redirectUrl: 'https://myapp.com/dashboard'
|
|
215
|
+
}
|
|
216
|
+
})
|
|
217
|
+
|
|
218
|
+
// Simple redirect authentication
|
|
219
|
+
blink.auth.login() // Redirects to blink.new auth page
|
|
220
|
+
blink.auth.logout() // Clear tokens and redirect
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
#### Headless Mode (Custom UI Control)
|
|
224
|
+
|
|
225
|
+
Build your own authentication UI with full control:
|
|
226
|
+
|
|
227
|
+
```typescript
|
|
228
|
+
const blink = createClient({
|
|
229
|
+
projectId: 'your-project',
|
|
230
|
+
auth: {
|
|
231
|
+
mode: 'headless'
|
|
232
|
+
// Providers controlled via project settings
|
|
233
|
+
}
|
|
234
|
+
})
|
|
235
|
+
|
|
236
|
+
// Email/password authentication
|
|
237
|
+
const user = await blink.auth.signUp({
|
|
238
|
+
email: 'user@example.com',
|
|
239
|
+
password: 'SecurePass123!',
|
|
240
|
+
metadata: { displayName: 'John Doe' }
|
|
241
|
+
})
|
|
242
|
+
|
|
243
|
+
const user = await blink.auth.signInWithEmail('user@example.com', 'password')
|
|
244
|
+
|
|
245
|
+
// Social provider authentication
|
|
246
|
+
const user = await blink.auth.signInWithGoogle()
|
|
247
|
+
const user = await blink.auth.signInWithGitHub()
|
|
248
|
+
const user = await blink.auth.signInWithApple()
|
|
249
|
+
const user = await blink.auth.signInWithMicrosoft()
|
|
250
|
+
|
|
251
|
+
// Magic links (passwordless)
|
|
252
|
+
await blink.auth.sendMagicLink('user@example.com', {
|
|
253
|
+
redirectUrl: 'https://myapp.com/dashboard'
|
|
254
|
+
})
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
#### 🔧 Provider Configuration
|
|
258
|
+
|
|
259
|
+
**Step 1: Enable Providers in Your Project**
|
|
260
|
+
1. Go to [blink.new](https://blink.new) and open your project
|
|
261
|
+
2. Navigate to **Project → Workspace → Authentication**
|
|
262
|
+
3. Toggle providers on/off:
|
|
263
|
+
- **Email** ✅ (enabled by default) - Includes email/password, magic links, and verification
|
|
264
|
+
- **Google** ✅ (enabled by default)
|
|
265
|
+
- **GitHub** ⚪ (disabled by default)
|
|
266
|
+
- **Apple** ⚪ (disabled by default)
|
|
267
|
+
- **Microsoft** ⚪ (disabled by default)
|
|
268
|
+
4. Configure email settings:
|
|
269
|
+
- **Require email verification**: Off by default (easier implementation)
|
|
270
|
+
- **Allow user signup**: On by default
|
|
271
|
+
|
|
272
|
+
**Step 2: Discover Available Providers**
|
|
273
|
+
```typescript
|
|
274
|
+
// Get providers enabled for your project
|
|
275
|
+
const availableProviders = await blink.auth.getAvailableProviders()
|
|
276
|
+
// Returns: ['email', 'google'] (based on your project settings)
|
|
277
|
+
|
|
278
|
+
// Use in your UI to show only enabled providers
|
|
279
|
+
const showGoogleButton = availableProviders.includes('google')
|
|
280
|
+
const showGitHubButton = availableProviders.includes('github')
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
**Step 3: Client-Side Filtering (Headless Mode)**
|
|
284
|
+
```typescript
|
|
285
|
+
const blink = createClient({
|
|
286
|
+
projectId: 'your-project',
|
|
287
|
+
auth: {
|
|
288
|
+
mode: 'headless'
|
|
289
|
+
// All providers controlled via project settings
|
|
290
|
+
}
|
|
291
|
+
})
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
**Managed Mode: Automatic Provider Display**
|
|
295
|
+
```typescript
|
|
296
|
+
const blink = createClient({
|
|
297
|
+
projectId: 'your-project',
|
|
298
|
+
auth: { mode: 'managed' }
|
|
299
|
+
})
|
|
300
|
+
|
|
301
|
+
// The hosted auth page automatically shows only enabled providers
|
|
302
|
+
blink.auth.login() // Shows Email + Google by default
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
#### 📧 Email Verification Flow
|
|
306
|
+
|
|
307
|
+
**By default, email verification is NOT required** for easier implementation. Enable it only if needed:
|
|
308
|
+
|
|
309
|
+
**Step 1: Configure Verification (Optional)**
|
|
310
|
+
1. Go to [blink.new](https://blink.new) → Project → Workspace → Authentication
|
|
311
|
+
2. Toggle **"Require email verification"** ON (disabled by default)
|
|
312
|
+
|
|
313
|
+
**Step 2: Handle the Complete Flow**
|
|
314
|
+
```typescript
|
|
315
|
+
// User signup - always send verification email for security
|
|
316
|
+
try {
|
|
317
|
+
const user = await blink.auth.signUp({ email, password })
|
|
318
|
+
await blink.auth.sendEmailVerification()
|
|
319
|
+
setMessage('Account created! Check your email to verify.')
|
|
320
|
+
} catch (error) {
|
|
321
|
+
setError(error.message)
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
// User signin - handle verification requirement
|
|
325
|
+
try {
|
|
326
|
+
await blink.auth.signInWithEmail(email, password)
|
|
327
|
+
// Success - user is signed in
|
|
328
|
+
} catch (error) {
|
|
329
|
+
if (error.code === 'EMAIL_NOT_VERIFIED') {
|
|
330
|
+
setError('Please verify your email first')
|
|
331
|
+
await blink.auth.sendEmailVerification() // Resend verification
|
|
332
|
+
} else {
|
|
333
|
+
setError(error.message)
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
// Manual verification resend
|
|
338
|
+
await blink.auth.sendEmailVerification()
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
**What Happens:**
|
|
342
|
+
1. **Signup**: User account created, `email_verified = false`
|
|
343
|
+
2. **Verification Email**: User clicks link → `email_verified = true`
|
|
344
|
+
3. **Signin Check**: If verification required AND not verified → `EMAIL_NOT_VERIFIED` error
|
|
345
|
+
4. **Success**: User can sign in once verified (or if verification not required)
|
|
346
|
+
|
|
347
|
+
#### Flexible Email System
|
|
348
|
+
|
|
349
|
+
**Maximum flexibility** - you control email branding while Blink handles secure tokens:
|
|
350
|
+
|
|
177
351
|
```typescript
|
|
178
|
-
//
|
|
179
|
-
blink.auth.
|
|
180
|
-
|
|
352
|
+
// Option A: Custom email delivery (full branding control)
|
|
353
|
+
const resetData = await blink.auth.generatePasswordResetToken('user@example.com')
|
|
354
|
+
// Returns: { token, expiresAt, resetUrl }
|
|
355
|
+
|
|
356
|
+
// Send with your email service and branding
|
|
357
|
+
await yourEmailService.send({
|
|
358
|
+
to: 'user@example.com',
|
|
359
|
+
subject: 'Reset your YourApp password',
|
|
360
|
+
html: `
|
|
361
|
+
<div style="font-family: Arial, sans-serif;">
|
|
362
|
+
<img src="https://yourapp.com/logo.png" alt="YourApp" />
|
|
363
|
+
<h1>Reset Your Password</h1>
|
|
364
|
+
<a href="${resetData.resetUrl}"
|
|
365
|
+
style="background: #0070f3; color: white; padding: 16px 32px;
|
|
366
|
+
text-decoration: none; border-radius: 8px;">
|
|
367
|
+
Reset Password
|
|
368
|
+
</a>
|
|
369
|
+
</div>
|
|
370
|
+
`
|
|
371
|
+
})
|
|
372
|
+
|
|
373
|
+
// Option B: Blink default email (zero setup)
|
|
374
|
+
await blink.auth.sendPasswordResetEmail('user@example.com')
|
|
375
|
+
|
|
376
|
+
// Same flexibility for email verification and magic links
|
|
377
|
+
const verifyData = await blink.auth.generateEmailVerificationToken()
|
|
378
|
+
const magicData = await blink.auth.generateMagicLinkToken('user@example.com')
|
|
379
|
+
```
|
|
380
|
+
|
|
381
|
+
#### Role-Based Access Control (RBAC)
|
|
181
382
|
|
|
383
|
+
```typescript
|
|
384
|
+
// Configure roles and permissions
|
|
385
|
+
const blink = createClient({
|
|
386
|
+
projectId: 'your-project',
|
|
387
|
+
auth: {
|
|
388
|
+
mode: 'headless',
|
|
389
|
+
roles: {
|
|
390
|
+
admin: { permissions: ['*'] },
|
|
391
|
+
editor: { permissions: ['posts.create', 'posts.update'] },
|
|
392
|
+
viewer: { permissions: ['posts.read'] }
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
})
|
|
396
|
+
|
|
397
|
+
// Check permissions
|
|
398
|
+
const canEdit = blink.auth.can('posts.update')
|
|
399
|
+
const isAdmin = blink.auth.hasRole('admin')
|
|
400
|
+
const isStaff = blink.auth.hasRole(['admin', 'editor'])
|
|
401
|
+
|
|
402
|
+
// Use in components
|
|
403
|
+
function EditButton() {
|
|
404
|
+
if (!blink.auth.can('posts.update')) return null
|
|
405
|
+
return <button onClick={editPost}>Edit Post</button>
|
|
406
|
+
}
|
|
407
|
+
```
|
|
408
|
+
|
|
409
|
+
#### Core Methods
|
|
410
|
+
|
|
411
|
+
```typescript
|
|
182
412
|
// User management
|
|
183
413
|
const user = await blink.auth.me()
|
|
184
414
|
await blink.auth.updateMe({ displayName: 'New Name' })
|
|
@@ -187,6 +417,16 @@ await blink.auth.updateMe({ displayName: 'New Name' })
|
|
|
187
417
|
blink.auth.setToken(jwt, persist?)
|
|
188
418
|
const isAuth = blink.auth.isAuthenticated()
|
|
189
419
|
|
|
420
|
+
// Password management
|
|
421
|
+
await blink.auth.changePassword('oldPass', 'newPass')
|
|
422
|
+
await blink.auth.confirmPasswordReset(token, newPassword)
|
|
423
|
+
|
|
424
|
+
// Email verification
|
|
425
|
+
await blink.auth.verifyEmail(token)
|
|
426
|
+
|
|
427
|
+
// Provider discovery
|
|
428
|
+
const providers = await blink.auth.getAvailableProviders()
|
|
429
|
+
|
|
190
430
|
// Auth state listener (REQUIRED for React apps!)
|
|
191
431
|
const unsubscribe = blink.auth.onAuthStateChanged((state) => {
|
|
192
432
|
console.log('Auth state:', state)
|
|
@@ -197,6 +437,31 @@ const unsubscribe = blink.auth.onAuthStateChanged((state) => {
|
|
|
197
437
|
})
|
|
198
438
|
```
|
|
199
439
|
|
|
440
|
+
#### Login Redirect Behavior
|
|
441
|
+
|
|
442
|
+
When `login()` is called, the SDK automatically determines where to redirect after authentication:
|
|
443
|
+
|
|
444
|
+
```typescript
|
|
445
|
+
// Automatic redirect (uses current page URL)
|
|
446
|
+
blink.auth.login()
|
|
447
|
+
// → Redirects to: blink.new/auth?redirect_url=https://yourapp.com/current-page
|
|
448
|
+
|
|
449
|
+
// Custom redirect URL
|
|
450
|
+
blink.auth.login('https://yourapp.com/dashboard')
|
|
451
|
+
// → Redirects to: blink.new/auth?redirect_url=https://yourapp.com/dashboard
|
|
452
|
+
|
|
453
|
+
// Manual login button example
|
|
454
|
+
const handleLogin = () => {
|
|
455
|
+
// The SDK will automatically use the current page URL
|
|
456
|
+
blink.auth.login()
|
|
457
|
+
|
|
458
|
+
// Or specify a custom redirect
|
|
459
|
+
// blink.auth.login('https://yourapp.com/welcome')
|
|
460
|
+
}
|
|
461
|
+
```
|
|
462
|
+
|
|
463
|
+
**✅ Fixed in v1.x**: The SDK now ensures redirect URLs are always absolute, preventing broken redirects when `window.location.href` returns relative paths.
|
|
464
|
+
|
|
200
465
|
### Database Operations
|
|
201
466
|
|
|
202
467
|
**🎉 NEW: Automatic Case Conversion!**
|
|
@@ -260,7 +525,6 @@ await blink.db.todos.upsertMany([...])
|
|
|
260
525
|
// Text generation (simple prompt)
|
|
261
526
|
const { text } = await blink.ai.generateText({
|
|
262
527
|
prompt: 'Write a poem about coding',
|
|
263
|
-
model: 'gpt-4o-mini',
|
|
264
528
|
maxTokens: 150
|
|
265
529
|
})
|
|
266
530
|
|
|
@@ -281,6 +545,7 @@ const { text } = await blink.ai.generateText({
|
|
|
281
545
|
// Text generation with image content
|
|
282
546
|
// ⚠️ IMPORTANT: Images must be HTTPS URLs with file extensions (.jpg, .jpeg, .png, .gif, .webp)
|
|
283
547
|
// For file uploads, use blink.storage.upload() first to get public HTTPS URLs
|
|
548
|
+
// 🚫 CRITICAL: When uploading files, NEVER hardcode extensions - use file.name or auto-detection
|
|
284
549
|
const { text } = await blink.ai.generateText({
|
|
285
550
|
messages: [
|
|
286
551
|
{
|
|
@@ -355,26 +620,97 @@ const { object: todoList } = await blink.ai.generateObject({
|
|
|
355
620
|
// })
|
|
356
621
|
// Error: "schema must be a JSON Schema of 'type: \"object\"', got 'type: \"array\"'"
|
|
357
622
|
|
|
358
|
-
// Generate and modify images with AI
|
|
623
|
+
// Generate and modify images with AI (powered by Gemini 2.5 Flash Image)
|
|
624
|
+
// 🔥 Automatic optimization - no need to specify size, quality, or style parameters
|
|
625
|
+
// 🎨 For style transfer: provide ALL images in the images array, don't reference URLs in prompts
|
|
359
626
|
|
|
360
627
|
// Basic image generation - returns public URLs directly
|
|
361
628
|
const { data } = await blink.ai.generateImage({
|
|
362
|
-
prompt: 'A serene landscape with mountains and a lake at sunset'
|
|
363
|
-
size: '1024x1024',
|
|
364
|
-
quality: 'high',
|
|
365
|
-
n: 2
|
|
629
|
+
prompt: 'A serene landscape with mountains and a lake at sunset'
|
|
366
630
|
})
|
|
367
631
|
console.log('Image URL:', data[0].url)
|
|
368
632
|
|
|
633
|
+
// Multiple images
|
|
634
|
+
const { data } = await blink.ai.generateImage({
|
|
635
|
+
prompt: 'A futuristic robot in different poses',
|
|
636
|
+
n: 3
|
|
637
|
+
})
|
|
638
|
+
data.forEach((img, i) => console.log(`Image ${i+1}:`, img.url))
|
|
639
|
+
|
|
369
640
|
// Image editing - transform existing images with prompts
|
|
370
641
|
const { data: headshots } = await blink.ai.modifyImage({
|
|
371
|
-
images: ['https://storage.example.com/user-photo.jpg'], //
|
|
372
|
-
prompt: '
|
|
373
|
-
|
|
374
|
-
|
|
642
|
+
images: ['https://storage.example.com/user-photo.jpg'], // Up to 50 images supported!
|
|
643
|
+
prompt: 'Transform into professional business headshot with studio lighting'
|
|
644
|
+
})
|
|
645
|
+
|
|
646
|
+
// Advanced image editing with multiple input images
|
|
647
|
+
const { data } = await blink.ai.modifyImage({
|
|
648
|
+
images: [
|
|
649
|
+
'https://storage.example.com/photo1.jpg',
|
|
650
|
+
'https://storage.example.com/photo2.jpg',
|
|
651
|
+
'https://storage.example.com/photo3.jpg'
|
|
652
|
+
],
|
|
653
|
+
prompt: 'Combine these architectural styles into a futuristic building design',
|
|
654
|
+
n: 2
|
|
655
|
+
})
|
|
656
|
+
|
|
657
|
+
// 🎨 Style Transfer & Feature Application
|
|
658
|
+
// ⚠️ IMPORTANT: When applying styles/features from one image to another,
|
|
659
|
+
// provide ALL images in the array - don't reference images in the prompt text
|
|
660
|
+
|
|
661
|
+
// ❌ WRONG - Don't reference images in prompt
|
|
662
|
+
const wrongWay = await blink.ai.modifyImage({
|
|
663
|
+
images: [userPhotoUrl],
|
|
664
|
+
prompt: `Apply this hairstyle from the reference image to the person's head. Reference hairstyle: ${hairstyleUrl}`
|
|
665
|
+
})
|
|
666
|
+
|
|
667
|
+
// ✅ CORRECT - Provide all images in the array
|
|
668
|
+
const { data } = await blink.ai.modifyImage({
|
|
669
|
+
images: [userPhotoUrl, hairstyleUrl], // Both images provided
|
|
670
|
+
prompt: 'Apply the hairstyle from the second image to the person in the first image. Blend naturally with face shape.'
|
|
671
|
+
})
|
|
672
|
+
|
|
673
|
+
// More style transfer examples
|
|
674
|
+
const { data } = await blink.ai.modifyImage({
|
|
675
|
+
images: [portraitUrl, artworkUrl],
|
|
676
|
+
prompt: 'Apply the artistic style and color palette from the second image to the portrait in the first image'
|
|
677
|
+
})
|
|
678
|
+
|
|
679
|
+
const { data } = await blink.ai.modifyImage({
|
|
680
|
+
images: [roomPhotoUrl, designReferenceUrl],
|
|
681
|
+
prompt: 'Redesign the room in the first image using the interior design style shown in the second image'
|
|
682
|
+
})
|
|
683
|
+
|
|
684
|
+
// Multiple reference images for complex transformations
|
|
685
|
+
const { data } = await blink.ai.modifyImage({
|
|
686
|
+
images: [
|
|
687
|
+
originalPhotoUrl,
|
|
688
|
+
lightingReferenceUrl,
|
|
689
|
+
colorPaletteReferenceUrl,
|
|
690
|
+
compositionReferenceUrl
|
|
691
|
+
],
|
|
692
|
+
prompt: 'Transform the first image using the lighting style from image 2, color palette from image 3, and composition from image 4'
|
|
693
|
+
})
|
|
694
|
+
|
|
695
|
+
// 📱 File Upload + Style Transfer
|
|
696
|
+
// ⚠️ Extract file extension properly - never hardcode .jpg/.png
|
|
697
|
+
|
|
698
|
+
// ❌ WRONG - Hardcoded extension
|
|
699
|
+
const userUpload = await blink.storage.upload(file, `photos/${Date.now()}.jpg`) // Breaks HEIC/PNG files
|
|
700
|
+
// ✅ CORRECT - Extract original extension
|
|
701
|
+
const userUpload = await blink.storage.upload(
|
|
702
|
+
userPhoto.file,
|
|
703
|
+
`photos/${Date.now()}.${userPhoto.file.name.split('.').pop()}`
|
|
704
|
+
)
|
|
705
|
+
const hairstyleUpload = await blink.storage.upload(
|
|
706
|
+
hairstylePhoto.file,
|
|
707
|
+
`haircuts/${Date.now()}.${hairstylePhoto.file.name.split('.').pop()}`
|
|
708
|
+
)
|
|
709
|
+
|
|
710
|
+
const { data } = await blink.ai.modifyImage({
|
|
711
|
+
images: [userUpload.publicUrl, hairstyleUpload.publicUrl],
|
|
712
|
+
prompt: 'Apply hairstyle from second image to person in first image'
|
|
375
713
|
})
|
|
376
|
-
// Options: size ('1024x1024', '1536x1024'), quality ('auto'|'low'|'medium'|'high'),
|
|
377
|
-
// background ('auto'|'transparent'|'opaque'), n (1-10 images)
|
|
378
714
|
|
|
379
715
|
|
|
380
716
|
// Speech synthesis
|
|
@@ -707,20 +1043,17 @@ try {
|
|
|
707
1043
|
// Upload files (returns public URL directly)
|
|
708
1044
|
const { publicUrl } = await blink.storage.upload(
|
|
709
1045
|
file,
|
|
710
|
-
|
|
1046
|
+
`uploads/${Date.now()}.${file.name.split('.').pop()}`, // ✅ Extract original extension
|
|
711
1047
|
{
|
|
712
1048
|
upsert: true,
|
|
713
1049
|
onProgress: (percent) => console.log(`${percent}%`)
|
|
714
1050
|
}
|
|
715
1051
|
)
|
|
716
|
-
// If file is PNG, final path will be: path/to/file.png
|
|
717
1052
|
|
|
718
|
-
//
|
|
719
|
-
const
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
{ upsert: true }
|
|
723
|
-
)
|
|
1053
|
+
// ❌ WRONG - Hardcoded extensions break HEIC/PNG/WebP files
|
|
1054
|
+
const wrong = await blink.storage.upload(file, `uploads/${Date.now()}.jpg`) // Corrupts non-JPG files
|
|
1055
|
+
// ✅ CORRECT - Extract file extension
|
|
1056
|
+
const correct = await blink.storage.upload(file, `uploads/${Date.now()}.${file.name.split('.').pop()}`)
|
|
724
1057
|
|
|
725
1058
|
// Remove files
|
|
726
1059
|
await blink.storage.remove('file1.jpg', 'file2.jpg')
|
|
@@ -1184,14 +1517,44 @@ try {
|
|
|
1184
1517
|
### Error Handling
|
|
1185
1518
|
|
|
1186
1519
|
```typescript
|
|
1187
|
-
import {
|
|
1188
|
-
|
|
1520
|
+
import {
|
|
1521
|
+
BlinkAuthError,
|
|
1522
|
+
BlinkAuthErrorCode,
|
|
1523
|
+
BlinkAIError,
|
|
1524
|
+
BlinkStorageError,
|
|
1525
|
+
BlinkDataError,
|
|
1526
|
+
BlinkRealtimeError,
|
|
1527
|
+
BlinkNotificationsError
|
|
1528
|
+
} from '@blinkdotnew/sdk'
|
|
1529
|
+
|
|
1530
|
+
// Authentication error handling
|
|
1189
1531
|
try {
|
|
1190
|
-
const user = await blink.auth.
|
|
1532
|
+
const user = await blink.auth.signInWithEmail(email, password)
|
|
1191
1533
|
} catch (error) {
|
|
1192
1534
|
if (error instanceof BlinkAuthError) {
|
|
1193
|
-
|
|
1194
|
-
|
|
1535
|
+
switch (error.code) {
|
|
1536
|
+
case BlinkAuthErrorCode.EMAIL_NOT_VERIFIED:
|
|
1537
|
+
console.log('Email verification required')
|
|
1538
|
+
await blink.auth.sendEmailVerification()
|
|
1539
|
+
break
|
|
1540
|
+
case BlinkAuthErrorCode.INVALID_CREDENTIALS:
|
|
1541
|
+
console.error('Invalid email or password')
|
|
1542
|
+
break
|
|
1543
|
+
case BlinkAuthErrorCode.RATE_LIMITED:
|
|
1544
|
+
console.error('Too many attempts, try again later')
|
|
1545
|
+
break
|
|
1546
|
+
default:
|
|
1547
|
+
console.error('Auth error:', error.message)
|
|
1548
|
+
}
|
|
1549
|
+
}
|
|
1550
|
+
}
|
|
1551
|
+
|
|
1552
|
+
// Other error types
|
|
1553
|
+
try {
|
|
1554
|
+
const { text } = await blink.ai.generateText({ prompt: 'Hello' })
|
|
1555
|
+
} catch (error) {
|
|
1556
|
+
if (error instanceof BlinkAIError) {
|
|
1557
|
+
console.error('AI error:', error.message)
|
|
1195
1558
|
}
|
|
1196
1559
|
}
|
|
1197
1560
|
```
|
|
@@ -1202,7 +1565,18 @@ try {
|
|
|
1202
1565
|
const blink = createClient({
|
|
1203
1566
|
projectId: 'your-project',
|
|
1204
1567
|
baseUrl: 'https://custom-api.example.com',
|
|
1205
|
-
|
|
1568
|
+
auth: {
|
|
1569
|
+
mode: 'headless', // 'managed' | 'headless'
|
|
1570
|
+
authUrl: 'https://custom-auth.example.com', // Custom auth domain
|
|
1571
|
+
coreUrl: 'https://custom-core.example.com', // Custom API domain
|
|
1572
|
+
// Providers controlled via project settings
|
|
1573
|
+
redirectUrl: 'https://myapp.com/dashboard',
|
|
1574
|
+
roles: {
|
|
1575
|
+
admin: { permissions: ['*'] },
|
|
1576
|
+
editor: { permissions: ['posts.create', 'posts.update'], inherit: ['viewer'] },
|
|
1577
|
+
viewer: { permissions: ['posts.read'] }
|
|
1578
|
+
}
|
|
1579
|
+
},
|
|
1206
1580
|
httpClient: {
|
|
1207
1581
|
timeout: 30000,
|
|
1208
1582
|
retries: 3
|
|
@@ -1403,6 +1777,220 @@ function MyRealtimeComponent() {
|
|
|
1403
1777
|
|
|
1404
1778
|
### React
|
|
1405
1779
|
|
|
1780
|
+
#### Authentication Integration
|
|
1781
|
+
|
|
1782
|
+
**Complete authentication example with custom UI:**
|
|
1783
|
+
|
|
1784
|
+
```typescript
|
|
1785
|
+
import { createClient } from '@blinkdotnew/sdk'
|
|
1786
|
+
import { useState, useEffect } from 'react'
|
|
1787
|
+
|
|
1788
|
+
// ✅ RECOMMENDED: Don't force immediate auth - let users browse first
|
|
1789
|
+
const blink = createClient({
|
|
1790
|
+
projectId: 'your-project',
|
|
1791
|
+
authRequired: false, // Let users browse public areas
|
|
1792
|
+
auth: {
|
|
1793
|
+
mode: 'headless' // Use headless for custom UI
|
|
1794
|
+
// Providers controlled via project settings
|
|
1795
|
+
}
|
|
1796
|
+
})
|
|
1797
|
+
|
|
1798
|
+
function App() {
|
|
1799
|
+
const [user, setUser] = useState(null)
|
|
1800
|
+
const [loading, setLoading] = useState(true)
|
|
1801
|
+
const [currentPage, setCurrentPage] = useState('home')
|
|
1802
|
+
|
|
1803
|
+
// ✅ CRITICAL: Always use onAuthStateChanged
|
|
1804
|
+
useEffect(() => {
|
|
1805
|
+
const unsubscribe = blink.auth.onAuthStateChanged((state) => {
|
|
1806
|
+
setUser(state.user)
|
|
1807
|
+
setLoading(state.isLoading)
|
|
1808
|
+
})
|
|
1809
|
+
return unsubscribe
|
|
1810
|
+
}, [])
|
|
1811
|
+
|
|
1812
|
+
// Protected route check - only require auth for dashboard/account areas
|
|
1813
|
+
const requiresAuth = ['dashboard', 'account', 'settings'].includes(currentPage)
|
|
1814
|
+
|
|
1815
|
+
if (loading) return <div>Loading...</div>
|
|
1816
|
+
if (requiresAuth && !user) return <AuthForm />
|
|
1817
|
+
|
|
1818
|
+
// Public pages (home, pricing, about) don't require auth
|
|
1819
|
+
if (currentPage === 'home') return <HomePage />
|
|
1820
|
+
if (currentPage === 'pricing') return <PricingPage />
|
|
1821
|
+
|
|
1822
|
+
// Protected pages require authentication
|
|
1823
|
+
return <Dashboard user={user} />
|
|
1824
|
+
}
|
|
1825
|
+
|
|
1826
|
+
function AuthForm() {
|
|
1827
|
+
const [mode, setMode] = useState('signin') // 'signin' | 'signup' | 'reset'
|
|
1828
|
+
const [email, setEmail] = useState('')
|
|
1829
|
+
const [password, setPassword] = useState('')
|
|
1830
|
+
const [error, setError] = useState('')
|
|
1831
|
+
const [message, setMessage] = useState('')
|
|
1832
|
+
|
|
1833
|
+
const handleEmailAuth = async (e) => {
|
|
1834
|
+
e.preventDefault()
|
|
1835
|
+
setError('')
|
|
1836
|
+
|
|
1837
|
+
try {
|
|
1838
|
+
if (mode === 'signup') {
|
|
1839
|
+
const user = await blink.auth.signUp({ email, password })
|
|
1840
|
+
setMessage('Account created! Please check your email to verify.')
|
|
1841
|
+
await blink.auth.sendEmailVerification()
|
|
1842
|
+
} else if (mode === 'signin') {
|
|
1843
|
+
await blink.auth.signInWithEmail(email, password)
|
|
1844
|
+
// User state updates automatically via onAuthStateChanged
|
|
1845
|
+
} else if (mode === 'reset') {
|
|
1846
|
+
await blink.auth.sendPasswordResetEmail(email)
|
|
1847
|
+
setMessage('Password reset email sent!')
|
|
1848
|
+
}
|
|
1849
|
+
} catch (err) {
|
|
1850
|
+
if (err.code === 'EMAIL_NOT_VERIFIED') {
|
|
1851
|
+
setError('Please verify your email first')
|
|
1852
|
+
await blink.auth.sendEmailVerification()
|
|
1853
|
+
} else {
|
|
1854
|
+
setError(err.message)
|
|
1855
|
+
}
|
|
1856
|
+
}
|
|
1857
|
+
}
|
|
1858
|
+
|
|
1859
|
+
const handleSocialAuth = async (provider) => {
|
|
1860
|
+
try {
|
|
1861
|
+
await blink.auth.signInWithProvider(provider)
|
|
1862
|
+
} catch (err) {
|
|
1863
|
+
setError(err.message)
|
|
1864
|
+
}
|
|
1865
|
+
}
|
|
1866
|
+
|
|
1867
|
+
return (
|
|
1868
|
+
<div style={{ maxWidth: '400px', margin: '0 auto', padding: '2rem' }}>
|
|
1869
|
+
<h1>{mode === 'signin' ? 'Sign In' : mode === 'signup' ? 'Sign Up' : 'Reset Password'}</h1>
|
|
1870
|
+
|
|
1871
|
+
{error && <div style={{ color: 'red', marginBottom: '1rem' }}>{error}</div>}
|
|
1872
|
+
{message && <div style={{ color: 'green', marginBottom: '1rem' }}>{message}</div>}
|
|
1873
|
+
|
|
1874
|
+
<form onSubmit={handleEmailAuth} style={{ marginBottom: '1rem' }}>
|
|
1875
|
+
<input
|
|
1876
|
+
type="email"
|
|
1877
|
+
placeholder="Email"
|
|
1878
|
+
value={email}
|
|
1879
|
+
onChange={(e) => setEmail(e.target.value)}
|
|
1880
|
+
style={{ width: '100%', padding: '0.5rem', marginBottom: '0.5rem' }}
|
|
1881
|
+
/>
|
|
1882
|
+
|
|
1883
|
+
{mode !== 'reset' && (
|
|
1884
|
+
<input
|
|
1885
|
+
type="password"
|
|
1886
|
+
placeholder="Password"
|
|
1887
|
+
value={password}
|
|
1888
|
+
onChange={(e) => setPassword(e.target.value)}
|
|
1889
|
+
style={{ width: '100%', padding: '0.5rem', marginBottom: '1rem' }}
|
|
1890
|
+
/>
|
|
1891
|
+
)}
|
|
1892
|
+
|
|
1893
|
+
<button type="submit" style={{ width: '100%', padding: '0.75rem', marginBottom: '1rem' }}>
|
|
1894
|
+
{mode === 'signin' ? 'Sign In' : mode === 'signup' ? 'Create Account' : 'Send Reset Email'}
|
|
1895
|
+
</button>
|
|
1896
|
+
</form>
|
|
1897
|
+
|
|
1898
|
+
{mode !== 'reset' && (
|
|
1899
|
+
<div style={{ display: 'flex', gap: '0.5rem', marginBottom: '1rem' }}>
|
|
1900
|
+
<button onClick={() => handleSocialAuth('google')} style={{ flex: 1, padding: '0.5rem' }}>
|
|
1901
|
+
Google
|
|
1902
|
+
</button>
|
|
1903
|
+
<button onClick={() => handleSocialAuth('github')} style={{ flex: 1, padding: '0.5rem' }}>
|
|
1904
|
+
GitHub
|
|
1905
|
+
</button>
|
|
1906
|
+
</div>
|
|
1907
|
+
)}
|
|
1908
|
+
|
|
1909
|
+
<div style={{ textAlign: 'center', fontSize: '0.875rem' }}>
|
|
1910
|
+
{mode === 'signin' && (
|
|
1911
|
+
<>
|
|
1912
|
+
<button onClick={() => setMode('signup')} style={{ marginRight: '1rem' }}>
|
|
1913
|
+
Create Account
|
|
1914
|
+
</button>
|
|
1915
|
+
<button onClick={() => setMode('reset')}>Forgot Password?</button>
|
|
1916
|
+
</>
|
|
1917
|
+
)}
|
|
1918
|
+
{mode === 'signup' && (
|
|
1919
|
+
<button onClick={() => setMode('signin')}>Already have an account?</button>
|
|
1920
|
+
)}
|
|
1921
|
+
{mode === 'reset' && (
|
|
1922
|
+
<button onClick={() => setMode('signin')}>Back to Sign In</button>
|
|
1923
|
+
)}
|
|
1924
|
+
</div>
|
|
1925
|
+
</div>
|
|
1926
|
+
)
|
|
1927
|
+
}
|
|
1928
|
+
```
|
|
1929
|
+
|
|
1930
|
+
#### Custom Email Branding Example
|
|
1931
|
+
|
|
1932
|
+
**Send password reset with your own email service:**
|
|
1933
|
+
|
|
1934
|
+
```typescript
|
|
1935
|
+
function PasswordResetForm() {
|
|
1936
|
+
const [email, setEmail] = useState('')
|
|
1937
|
+
const [message, setMessage] = useState('')
|
|
1938
|
+
|
|
1939
|
+
const handleReset = async (e) => {
|
|
1940
|
+
e.preventDefault()
|
|
1941
|
+
|
|
1942
|
+
try {
|
|
1943
|
+
// Generate secure token (no email sent by Blink)
|
|
1944
|
+
const resetData = await blink.auth.generatePasswordResetToken(email)
|
|
1945
|
+
|
|
1946
|
+
// Send with your own email service and branding
|
|
1947
|
+
await yourEmailService.send({
|
|
1948
|
+
to: email,
|
|
1949
|
+
from: 'support@yourapp.com',
|
|
1950
|
+
subject: 'Reset your YourApp password',
|
|
1951
|
+
html: `
|
|
1952
|
+
<div style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; max-width: 600px; margin: 0 auto;">
|
|
1953
|
+
<div style="text-align: center; padding: 40px;">
|
|
1954
|
+
<img src="https://yourapp.com/logo.svg" alt="YourApp" style="width: 120px; margin-bottom: 32px;" />
|
|
1955
|
+
<h1 style="color: #1a1a1a; font-size: 28px; margin-bottom: 16px;">Reset Your Password</h1>
|
|
1956
|
+
<p style="color: #666; font-size: 16px; margin-bottom: 32px;">
|
|
1957
|
+
We received a request to reset your YourApp password.
|
|
1958
|
+
</p>
|
|
1959
|
+
<a href="${resetData.resetUrl}"
|
|
1960
|
+
style="display: inline-block; background: #0070f3; color: white;
|
|
1961
|
+
padding: 16px 32px; text-decoration: none; border-radius: 8px;
|
|
1962
|
+
font-weight: 600; font-size: 16px;">
|
|
1963
|
+
Reset My Password
|
|
1964
|
+
</a>
|
|
1965
|
+
<p style="color: #999; font-size: 14px; margin-top: 32px;">
|
|
1966
|
+
This link expires in 1 hour. If you didn't request this, you can ignore this email.
|
|
1967
|
+
</p>
|
|
1968
|
+
</div>
|
|
1969
|
+
</div>
|
|
1970
|
+
`
|
|
1971
|
+
})
|
|
1972
|
+
|
|
1973
|
+
setMessage('Password reset email sent! Check your inbox.')
|
|
1974
|
+
} catch (error) {
|
|
1975
|
+
console.error('Reset failed:', error.message)
|
|
1976
|
+
}
|
|
1977
|
+
}
|
|
1978
|
+
|
|
1979
|
+
return (
|
|
1980
|
+
<form onSubmit={handleReset}>
|
|
1981
|
+
<input
|
|
1982
|
+
type="email"
|
|
1983
|
+
value={email}
|
|
1984
|
+
onChange={(e) => setEmail(e.target.value)}
|
|
1985
|
+
placeholder="Enter your email"
|
|
1986
|
+
/>
|
|
1987
|
+
<button type="submit">Send Reset Link</button>
|
|
1988
|
+
{message && <p>{message}</p>}
|
|
1989
|
+
</form>
|
|
1990
|
+
)
|
|
1991
|
+
}
|
|
1992
|
+
```
|
|
1993
|
+
|
|
1406
1994
|
**⚠️ Critical: Always Use Auth State Listener, Never One-Time Checks**
|
|
1407
1995
|
|
|
1408
1996
|
The most common authentication mistake is checking auth status once instead of listening to changes:
|
|
@@ -1433,61 +2021,45 @@ useEffect(() => {
|
|
|
1433
2021
|
}, [])
|
|
1434
2022
|
```
|
|
1435
2023
|
|
|
1436
|
-
|
|
1437
|
-
1. App loads → `blink.auth.me()` called immediately
|
|
1438
|
-
2. Auth still initializing → Call fails, user set to `null`
|
|
1439
|
-
3. Auth completes later → App never knows because it only checked once
|
|
1440
|
-
4. User stuck on "Please sign in" screen forever
|
|
1441
|
-
|
|
1442
|
-
**Always use `onAuthStateChanged()` for React apps!**
|
|
2024
|
+
#### Error Handling Example
|
|
1443
2025
|
|
|
1444
2026
|
```typescript
|
|
1445
|
-
import {
|
|
1446
|
-
import { useState, useEffect } from 'react'
|
|
1447
|
-
|
|
1448
|
-
const blink = createClient({ projectId: 'your-project', authRequired: true })
|
|
1449
|
-
|
|
1450
|
-
function App() {
|
|
1451
|
-
const [user, setUser] = useState(null)
|
|
1452
|
-
const [loading, setLoading] = useState(true)
|
|
1453
|
-
|
|
1454
|
-
useEffect(() => {
|
|
1455
|
-
const unsubscribe = blink.auth.onAuthStateChanged((state) => {
|
|
1456
|
-
setUser(state.user)
|
|
1457
|
-
setLoading(state.isLoading)
|
|
1458
|
-
})
|
|
1459
|
-
return unsubscribe
|
|
1460
|
-
}, [])
|
|
1461
|
-
|
|
1462
|
-
if (loading) return <div>Loading...</div>
|
|
1463
|
-
if (!user) return <div>Please log in</div>
|
|
1464
|
-
|
|
1465
|
-
return <div>Welcome, {user.email}!</div>
|
|
1466
|
-
}
|
|
2027
|
+
import { BlinkAuthError, BlinkAuthErrorCode } from '@blinkdotnew/sdk'
|
|
1467
2028
|
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
const [user, setUser] = useState(null)
|
|
1471
|
-
const [loading, setLoading] = useState(true)
|
|
2029
|
+
function AuthForm() {
|
|
2030
|
+
const [error, setError] = useState('')
|
|
1472
2031
|
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
2032
|
+
const handleAuth = async () => {
|
|
2033
|
+
try {
|
|
2034
|
+
await blink.auth.signInWithEmail(email, password)
|
|
2035
|
+
} catch (err) {
|
|
2036
|
+
if (err instanceof BlinkAuthError) {
|
|
2037
|
+
switch (err.code) {
|
|
2038
|
+
case BlinkAuthErrorCode.EMAIL_NOT_VERIFIED:
|
|
2039
|
+
setError('Please verify your email first')
|
|
2040
|
+
await blink.auth.sendEmailVerification()
|
|
2041
|
+
break
|
|
2042
|
+
case BlinkAuthErrorCode.INVALID_CREDENTIALS:
|
|
2043
|
+
setError('Invalid email or password')
|
|
2044
|
+
break
|
|
2045
|
+
case BlinkAuthErrorCode.RATE_LIMITED:
|
|
2046
|
+
setError('Too many attempts. Please try again later.')
|
|
2047
|
+
break
|
|
2048
|
+
default:
|
|
2049
|
+
setError('Authentication failed. Please try again.')
|
|
2050
|
+
}
|
|
1482
2051
|
}
|
|
1483
2052
|
}
|
|
2053
|
+
}
|
|
1484
2054
|
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
2055
|
+
return (
|
|
2056
|
+
<div>
|
|
2057
|
+
{error && <div style={{ color: 'red' }}>{error}</div>}
|
|
2058
|
+
{/* Auth form */}
|
|
2059
|
+
</div>
|
|
2060
|
+
)
|
|
1490
2061
|
}
|
|
2062
|
+
```
|
|
1491
2063
|
|
|
1492
2064
|
// React example with search functionality
|
|
1493
2065
|
function SearchResults() {
|
|
@@ -1724,7 +2296,11 @@ function RealtimeChat() {
|
|
|
1724
2296
|
// pages/api/todos.ts
|
|
1725
2297
|
import { createClient } from '@blinkdotnew/sdk'
|
|
1726
2298
|
|
|
1727
|
-
|
|
2299
|
+
// ✅ RECOMMENDED: Use new auth configuration
|
|
2300
|
+
const blink = createClient({
|
|
2301
|
+
projectId: 'your-project',
|
|
2302
|
+
auth: { mode: 'managed' } // Explicit configuration
|
|
2303
|
+
})
|
|
1728
2304
|
|
|
1729
2305
|
export default async function handler(req, res) {
|
|
1730
2306
|
const jwt = req.headers.authorization?.replace('Bearer ', '')
|
|
@@ -1740,7 +2316,11 @@ export default async function handler(req, res) {
|
|
|
1740
2316
|
```typescript
|
|
1741
2317
|
import { createClient } from '@blinkdotnew/sdk'
|
|
1742
2318
|
|
|
1743
|
-
|
|
2319
|
+
// ✅ RECOMMENDED: Use new auth configuration
|
|
2320
|
+
const blink = createClient({
|
|
2321
|
+
projectId: 'your-project',
|
|
2322
|
+
auth: { mode: 'managed' } // Explicit configuration
|
|
2323
|
+
})
|
|
1744
2324
|
|
|
1745
2325
|
Deno.serve(async (req) => {
|
|
1746
2326
|
const jwt = req.headers.get('authorization')?.replace('Bearer ', '')
|