@ blinkdotnew/ sdk (英语)
授权 Blink AI 生成的应用程序的全存储 TypeScript SDK
Blink是一款AI App Builder,以秒为单位构建出功能完备的应用程序. 这个SDK( S)@blinkdotnew/sdk)是TypeScript的基础,它赋予每个 Blink app 本地权限,提供0-boilerplate认证,数据库操作,AI能力,以及文件存储. 在客户端(React,Vue等)和服务器端(Node.js,Deno,Edge功能)上无缝工作.
QQ 快速启动
步骤1: 创建一个闪烁工程
访问 眨一眼,换一眼 并创建一个新项目。 布莱克的AI代理 将在几秒钟内构建你的应用程序.
第2步:安装SDK
使用 Blink 的 AI 代理在您的 Vite React TypeScript 客户端中自动安装此 SDK, 或者手动安装 :
npm install @blinkdotnew/sdk
第3步: 使用您的工程ID
从您的闪烁仪表板上获取您的项目ID并开始构建:
import { createClient } from '@blinkdotnew/sdk'
const blink = createClient({
projectId: 'your-blink-project-id', // From blink.new dashboard
authRequired: false // Don't force immediate auth - let users browse first
})
// Authentication - Choose your mode:
// 🎯 MANAGED MODE: Quick setup with hosted auth page
const blink = createClient({
projectId: 'your-project',
auth: { mode: 'managed' }
})
// Use: blink.auth.login() - redirects to blink.new auth
// 🎨 HEADLESS MODE: Custom UI with full control
const blink = createClient({
projectId: 'your-project',
auth: { mode: 'headless' }
})
// Use: blink.auth.signInWithEmail(), blink.auth.signInWithGoogle(), etc.
// Current user (works in both modes)
const user = await blink.auth.me()
// Database operations (zero config)
const todos = await blink.db.todos.list({
where: { userId: user.id },
orderBy: { createdAt: 'desc' },
limit: 20
})
// AI operations (native)
const { text } = await blink.ai.generateText({
prompt: "Write a summary of the user's todos"
})
// Data operations (extract text from documents)
const text = await blink.data.extractFromUrl("https://example.com/document.pdf")
// Website scraping and screenshots (crystal clear results!)
const { markdown, metadata, links } = await blink.data.scrape("https://competitor.com")
const screenshotUrl = await blink.data.screenshot("https://competitor.com")
// Web search (get real-time information)
const searchResults = await blink.data.search("chatgpt latest news", { type: 'news' })
const localResults = await blink.data.search("best restaurants", { location: "San Francisco,CA,United States" })
// Notifications (NEW!)
const { success } = await blink.notifications.email({
to: '[email protected]',
subject: 'Your order has shipped!',
html: '<h1>Order Confirmation</h1><p>Your order #12345 is on its way.</p>'
})
// Secure API proxy (call external APIs with secret substitution)
const response = await blink.data.fetch({
url: "https://api.sendgrid.com/v3/mail/send",
method: "POST",
headers: { "Authorization": "Bearer {{sendgrid_api_key}}" },
body: { /* email data */ }
})
// Realtime operations (live messaging and presence)
const unsubscribe = await blink.realtime.subscribe('chat-room', (message) => {
console.log('New message:', message.data)
})
await blink.realtime.publish('chat-room', 'message', { text: 'Hello world!' })
// Get presence - returns array of PresenceUser objects directly
const users = await blink.realtime.presence('chat-room')
console.log('Online users:', users.length)
// users is PresenceUser[] format:
// [
// {
// userId: 'user123',
// metadata: { displayName: 'Alice', status: 'online' },
// joinedAt: 1640995200000,
// lastSeen: 1640995230000
// }
// ]
// Analytics operations (automatic pageview tracking + custom events)
// Pageviews are tracked automatically on initialization and route changes
blink.analytics.log('button_clicked', {
button_id: 'signup',
page: '/pricing'
})
// Check if analytics is enabled
if (blink.analytics.isEnabled()) {
console.log('Analytics is active')
}
// Turn SDK analytics off from the start: the SDK sends and stores nothing
// (sites hosted on Blink also get a separate page-view tag, not controlled here)
// const blink = createClient({ projectId, analytics: false })
// Opt a visitor out (remembered on this device) or back in
blink.analytics.disable()
blink.analytics.enable()
// Storage operations (instant - returns public URL directly)
const { publicUrl } = await blink.storage.upload(
file,
`avatars/${user.id}-${Date.now()}.${file.name.split('.').pop()}`, // ✅ Extract original extension
{ upsert: true }
)
什么是闪烁?
闪烁是 AI App 构建器 以秒数创建全功能应用程序。 简而言之,你想要构建的东西,而Blink的AI代理将:
- 🏗️ 生成完整的应用程序 使用反应 + 类型脚本 + Vite
- 🔧 自动安装此 SDK 配置为零
- 🎨 创建美丽的 UIs 有尾风 CSS
- 🚀 立即部署 具有认证、数据库、AI和存储内置
SDK 特性
这个 SDK 赋予每个 Blink 生成的应用程序 :
- 认证: 具有管理(定向)和无头(海关UI)模式的灵活认证系统,电子邮件/密码,社交供应商(Google,GitHub,苹果,微软),魔法链接,RBAC,以及自定义电子邮件品牌
- QQ 数据库: PostgREST 兼容 CRUD 操作,高级过滤
- 大赦国际:多模式图像生成和编辑(10个模式),文本生成,同时进行网络搜索,对象生成,语音合成,并抄录
- 数据: 从文档中取出文本内容, 以秘密替代安全 API 代理, 网络刮取, 截图和网络搜索
- 📁 存储: 文件上传、下载和管理
- * 通知:带有附件的电子邮件发送、定制品牌和交货跟踪
- 实时: 基于 WebSocket 的 pub/ sub 消息、 存在跟踪和实时更新
- 分析: 自动页面视图跟踪、自定义事件记录、会话管理以及隐私第一设计
- 通用: 客户端和服务器端的工作
- 框架不可知论:反应,Vue,斯维尔特,香草JS,Node.js,德诺, 原生反应
- * 反应原生:具有AsyncStorage集成和平台意识功能的一等移动支持
- 实时: 内置认证状态管理并刷新标志
- 零波列尔普拉特: 一切都在盒子里运作
QQ 手动安装和设置
QQ 小费:如果你使用Blink的AI代理,这都是自动为你完成的!
客户端( 反应, Vue等)
import { createClient } from '@blinkdotnew/sdk'
const blink = createClient({
projectId: 'your-blink-project-id', // From blink.new dashboard
authRequired: false, // Let users browse first - require auth only for protected areas
auth: {
mode: 'managed' // Use new explicit configuration
}
})
版本要求: 灵活的认证系统( 管理 vs 无头模式) 需要 SDK 版本 0.1.8.0或以上(含). 若使用0.17.x或以下版本,则只能访问遗留认证系统. 请升级以访问新的认证功能 .
服务器侧 (Node.js, Deno, Edge 函数)
import { createClient } from '@blinkdotnew/sdk'
const blink = createClient({
projectId: 'your-blink-project-id', // From blink.new dashboard
auth: { mode: 'managed' } // Manual token management
})
// Token injection is only needed when calling blink.auth.* methods on the server
QQ 本地反应( iOS & Android)
SDK已经 一等反应原生支持 拥有自动适应移动环境的平台意识功能.
步骤1:安装附属物
npm install @blinkdotnew/sdk @react-native-async-storage/async-storage expo-web-browser
需要的软件包 :
@blinkdotnew/sdk- 闪闪发光的SDK@react-native-async-storage/async-storage- 为象征的坚持expo-web-browser- 用于OAuth认证(Google, GitHub, Apple等)
步骤2: 创建客户端( E)lib/blink.ts(中文(简体) )
你一定要通过 webBrowser: WebBrowser 启用 OAuth 移动!
// lib/blink.ts
import { createClient, AsyncStorageAdapter } from '@blinkdotnew/sdk'
import AsyncStorage from '@react-native-async-storage/async-storage'
import * as WebBrowser from 'expo-web-browser' // ← Import this
import * as Linking from 'expo-linking'
export const blink = createClient({
projectId: 'your-project-id',
authRequired: false,
auth: {
mode: 'headless',
webBrowser: WebBrowser, // ← Pass it here! Required for OAuth
linking: Linking // ← Binds the sign-in to your app (needs a reverse-domain app.json scheme)
},
storage: new AsyncStorageAdapter(AsyncStorage)
})
与 linking 和一个倒置域 scheme 内 app.json (例如:. com.example.myapp), OAuth 返回到
您的应用程序有一次性代码, 只有您的应用程序可以收集登录。 没有他们(或在博览会)
sDK又回到了旧的流量.
当应用程序启动时, SDK 会恢复保存的会话 AsyncStorage 独自一人。 在此之前
结束, onAuthStateChanged 报告 isLoading: true,所以等待它 之前显示登录.
第3步: 使用认证
电子邮件/密码(立即工作):
// Sign up
const user = await blink.auth.signUp({
email: '[email protected]',
password: 'SecurePass123'
})
// Sign in
const user = await blink.auth.signInWithEmail('[email protected]', 'SecurePass123')
OAuth (谷歌, GitHub, 苹果, 微软):
// ✅ Same code works on web, iOS, AND Android!
const user = await blink.auth.signInWithGoogle()
const user = await blink.auth.signInWithGitHub()
const user = await blink.auth.signInWithApple()
const user = await blink.auth.signInWithMicrosoft()
第4步: 创建 Auth Hook(hooks/useAuth.ts(中文(简体) )
// hooks/useAuth.ts
import { useEffect, useState } from 'react'
import { blink } from '@/lib/blink'
import type { BlinkUser } from '@blinkdotnew/sdk'
export function useAuth() {
const [user, setUser] = useState<BlinkUser | null>(null)
const [isLoading, setIsLoading] = useState(true)
useEffect(() => {
const unsubscribe = blink.auth.onAuthStateChanged((state) => {
setUser(state.user)
setIsLoading(state.isLoading)
})
return unsubscribe
}, [])
return {
user,
isLoading,
isAuthenticated: !!user,
signInWithGoogle: () => blink.auth.signInWithGoogle(),
signInWithGitHub: () => blink.auth.signInWithGitHub(),
signInWithApple: () => blink.auth.signInWithApple(),
signOut: () => blink.auth.signOut(),
}
}
第5步:组件使用
import { useAuth } from '@/hooks/useAuth'
import { View, Text, Button } from 'react-native'
function App() {
const { user, isLoading, signInWithGoogle, signOut } = useAuth()
if (isLoading) return <Text>Loading...</Text>
if (!user) {
return <Button onPress={signInWithGoogle} title="Sign in with Google" />
}
return (
<View>
<Text>Welcome, {user.email}!</Text>
<Button onPress={signOut} title="Sign Out" />
</View>
)
}
常见错误
// ❌ WRONG: Missing webBrowser - OAuth won't work on mobile!
const blink = createClient({
projectId: 'your-project-id',
auth: { mode: 'headless' }, // Missing webBrowser!
storage: new AsyncStorageAdapter(AsyncStorage)
})
// ✅ CORRECT: Include webBrowser for OAuth support
import * as WebBrowser from 'expo-web-browser'
const blink = createClient({
projectId: 'your-project-id',
auth: {
mode: 'headless',
webBrowser: WebBrowser // ← Required for OAuth!
},
storage: new AsyncStorageAdapter(AsyncStorage)
})
低级 OAuth (用于自定义控制)
如果您需要自定义的投票超时或人工浏览器处理 :
// Get auth URL and authenticate function separately
const { authUrl, authenticate } = await blink.auth.signInWithProviderMobile('google')
// Open browser manually
await WebBrowser.openAuthSessionAsync(authUrl)
// Poll with custom options
const user = await authenticate({
maxAttempts: 120, // 60 seconds (default: 60 = 30 seconds)
intervalMs: 500 // Check every 500ms
})
平台特征
- ✅ 同步组合 - 坚守安全
- ✅ 通用操作 - 同一代码在网络+移动上有效
- ✅ 前网络浏览器 - 本地浏览器UI
- ✅ 无深层链接 - 按会期投票
- ✅ 博览会 - 不需要自定义dev客户端
- ✅ 自动签名刷新 - 无缝会议
API 参考
认证
版本要求: 灵活的认证系统需要SDK版本 0.1.8.0或以上(含). 0.17.x和以下版本只支持遗留的认证系统.
闪烁提供 两种认证模式数字 :
QQ 管理模式( 直接模式)
完美为: 快速设置, 最小代码 最佳服务: 网站,简易应用程序,MVP开发
const blink = createClient({
projectId: 'your-project',
auth: { mode: 'managed' }
})
// ONE METHOD: Redirect to hosted auth page
blink.auth.login() // → Redirects to blink.new/auth
blink.auth.logout() // Clear tokens and redirect
// User state (automatic after redirect)
const user = await blink.auth.me()
QQ 无头模式( Custom UI)
完美为: 自定义品牌、高级UX、移动应用程序 最佳服务: 生产应用、品牌经验
const blink = createClient({
projectId: 'your-project',
auth: { mode: 'headless' }
})
// MULTIPLE METHODS: Build your own UI
const user = await blink.auth.signUp({ email, password })
const user = await blink.auth.signInWithEmail(email, password)
const user = await blink.auth.signInWithGoogle()
const user = await blink.auth.signInWithGitHub()
const user = await blink.auth.signInWithApple()
const user = await blink.auth.signInWithMicrosoft()
// ✅ Store custom signup fields inside metadata
await blink.auth.signUp({
email: '[email protected]',
password: 'SuperSecret123',
displayName: 'Alex Founder',
role: 'operations',
metadata: {
company: 'Acme Freight',
marketingConsent: true
}
})
// `displayName`, `avatar`, and `role` map to dedicated auth columns.
// Everything else goes into auth.users.metadata automatically.
// Keep custom fields in metadata or your own profile table—avoid adding NOT NULL
// columns directly to auth tables.
// Magic links (passwordless)
await blink.auth.sendMagicLink(email)
// Password management
await blink.auth.sendPasswordResetEmail(email)
await blink.auth.sendPasswordResetEmail(email, {
redirectUrl: 'https://myapp.com/reset-password'
})
await blink.auth.changePassword(oldPass, newPass)
// Email verification
await blink.auth.sendEmailVerification()
QQ 快速模式比较
| 特性 | 管理模式 | 无头模式 |
|---|---|---|
| 设置 | 1行码 | 需要自定义用户界面 |
| 方法 | login() 仅限 | signInWith*() 方法 |
| 用户 | 主机认证页面 | 您的自定义表格 |
| 标记 | 闪闪发光 | 完全自定义 |
| 移动 | 网络重定向 | 土著融合 |
🚨 常见错误
// ❌ WRONG: Using managed method in headless mode
const blink = createClient({
auth: { mode: 'headless' }
})
blink.auth.login() // Still redirects! Wrong method for headless
// ✅ CORRECT: Use headless methods
await blink.auth.signInWithEmail(email, password)
await blink.auth.signInWithGoogle()
提供方配置
步骤1: 在您的工程中启用提供者
- 转到 眨一眼,换一眼 打开您的项目
- 导航到 项目-工作空间-认证
- 切换打开/关闭提供者 :
- 电子邮件 QQ(默认启用) - 包括电子邮件/密码、魔法链接和校验
- 谷歌 (默认启用)
- GitHub 图像 qq( 默认残疾)
- 苹果公司 qq( 默认残疾)
- 微软 qq( 默认残疾)
- 配置电子邮件设置 :
- 需要电子邮件验证: 默认关闭(容易执行)
- 允许用户注册: 在默认情况下
步骤2:发现现有供应商
// Get providers enabled for your project
const availableProviders = await blink.auth.getAvailableProviders()
// Returns: ['email', 'google'] (based on your project settings)
// Use in your UI to show only enabled providers
const showGoogleButton = availableProviders.includes('google')
const showGitHubButton = availableProviders.includes('github')
第3步: 客户端- 系统过滤( 无头模式)
const blink = createClient({
projectId: 'your-project',
auth: {
mode: 'headless'
// All providers controlled via project settings
}
})
管理模式: 自动提供者显示
const blink = createClient({
projectId: 'your-project',
auth: { mode: 'managed' }
})
// The hosted auth page automatically shows only enabled providers
blink.auth.login() // Shows Email + Google by default
电子邮件验证流程
默认情况下, 不需要电子邮件验证 以方便执行。 仅在需要时启用 :
步骤1:配置验证(可选)
- 转到 眨一眼,换一眼 项目 工作空间 认证
- 切换 "需要电子邮件验证" ON( 默认损坏)
第2步:处理整流
// User signup - always send verification email for security
try {
const user = await blink.auth.signUp({ email, password })
await blink.auth.sendEmailVerification()
setMessage('Account created! Check your email to verify.')
} catch (error) {
setError(error.message)
}
// User signin - handle verification requirement
try {
await blink.auth.signInWithEmail(email, password)
// Success - user is signed in
} catch (error) {
if (error.code === 'EMAIL_NOT_VERIFIED') {
setError('Please verify your email first')
await blink.auth.sendEmailVerification() // Resend verification
} else {
setError(error.message)
}
}
// Manual verification resend
await blink.auth.sendEmailVerification()
发生什么了
- 注册: 创建用户账户,
email_verified = false - 校验电子邮件: 用户点击链接
email_verified = true - 签名检查: 如果需要核查而未经核查 □
EMAIL_NOT_VERIFIED错误 - 成绩: 用户一旦核实(或不需要核实)即可签名
灵活电子邮件系统
最大灵活性 - 控制电子邮件品牌,而Brink处理安全标志:
generatePasswordResetToken()财务报告和审定财务报表generateMagicLinkToken()只有服务器. 他们 返回可赎回的证书,所以他们需要客户端与secretKey并且现在 拒绝(403)否则。 从浏览器代码使用中sendPasswordResetEmail()编号 :sendMagicLink()换句话说.
// Option A: Custom email delivery (full branding control) — SERVER-ONLY
const serverBlink = createClient({
projectId: process.env.BLINK_PROJECT_ID,
secretKey: process.env.BLINK_SECRET_KEY,
})
const resetData = await serverBlink.auth.generatePasswordResetToken('[email protected]')
// Returns: { token, expiresAt, resetUrl }
// Send with your email service and branding
await yourEmailService.send({
to: '[email protected]',
subject: 'Reset your YourApp password',
html: `
<div style="font-family: Arial, sans-serif;">
<img src="https://yourapp.com/logo.png" alt="YourApp" />
<h1>Reset Your Password</h1>
<a href="${resetData.resetUrl}"
style="background: #0070f3; color: white; padding: 16px 32px;
text-decoration: none; border-radius: 8px;">
Reset Password
</a>
</div>
`
})
// Option B: Blink default email (zero setup)
await blink.auth.sendPasswordResetEmail('[email protected]')
// Email verification is scoped to the signed-in user, so it stays client-safe
const verifyData = await blink.auth.generateEmailVerificationToken()
// Magic links mint a sign-in credential — server-only, like Option A above
const magicData = await serverBlink.auth.generateMagicLinkToken('[email protected]')
角色接入控制
// Configure roles and permissions
const blink = createClient({
projectId: 'your-project',
auth: {
mode: 'headless',
roles: {
admin: { permissions: ['*'] },
editor: { permissions: ['posts.create', 'posts.update'] },
viewer: { permissions: ['posts.read'] }
}
}
})
// Check permissions
const canEdit = blink.auth.can('posts.update')
const isAdmin = blink.auth.hasRole('admin')
const isStaff = blink.auth.hasRole(['admin', 'editor'])
// Use in components
function EditButton() {
if (!blink.auth.can('posts.update')) return null
return <button onClick={editPost}>Edit Post</button>
}
核心方法
QQ Tokens 为 Blink API 自动管理. 使用
getValidToken()只有您必须手动将一个符号传递给您的后端或第三方服务.
// User management
const user = await blink.auth.me()
await blink.auth.updateMe({ displayName: 'New Name' })
// Token management
blink.auth.setToken(jwt, persist?)
const isAuth = blink.auth.isAuthenticated()
const token = await blink.auth.getValidToken() // Get valid token (auto-refreshes)
// Password management
await blink.auth.sendPasswordResetEmail('[email protected]')
await blink.auth.sendPasswordResetEmail('[email protected]', {
redirectUrl: 'https://myapp.com/reset-password' // Custom reset page
})
await blink.auth.changePassword('oldPass', 'newPass')
await blink.auth.confirmPasswordReset(token, newPassword)
// Email verification
await blink.auth.verifyEmail(token)
// Provider discovery
const providers = await blink.auth.getAvailableProviders()
// Auth state listener (REQUIRED for React apps!)
const unsubscribe = blink.auth.onAuthStateChanged((state) => {
console.log('Auth state:', state)
// state.user - current user or null
// state.isLoading - true while auth is initializing
// state.isAuthenticated - true if user is logged in
// state.tokens - current auth tokens
})
会话在网络blips中幸存. SDK仅在认证服务器表示会话无效(过期,被撤销,被封禁,或被删除的账户)时才会将用户签名出局. 如果一个令牌刷新无法到达服务器——离线,当一个电话应用程序从背景恢复时,一个请求被放弃,或者一个临时的服务器错误——存储会话和用户被保留. 在下一个请求中重试了已丢弃的连接; 在服务器出错后, SDK 在重试前等待了几秒钟 . 虽然认证服务器无法访问,
getValidToken()返回当前访问符号,即使它已经过期(而不是null),所以您的请求作为一个网络错误失败或获得一个401您可以重试—— 它返回null只有在没有会议的时候.
登录方向行为
何时 login() 名称, SDK 自动确定认证后方向:
// Automatic redirect (uses current page URL)
blink.auth.login()
// → Redirects to: blink.new/auth?redirect_url=https://yourapp.com/current-page
// Custom redirect URL
blink.auth.login('https://yourapp.com/dashboard')
// → Redirects to: blink.new/auth?redirect_url=https://yourapp.com/dashboard
// Manual login button example
const handleLogin = () => {
// The SDK will automatically use the current page URL
blink.auth.login()
// Or specify a custom redirect
// blink.auth.login('https://yourapp.com/welcome')
}
固定在v1.x中: SDK 现在确保重定向 URL 总是绝对的, 防止在 window.location.href 返回相对路径.
数据库操作
QQ新:自动大小写转换!
SDK现在在JavaScript骆驼Case和SQL蛇 case之间自动转换:
- 表格名称数字 :
blink.db.emailDrafts→email_drafts图表 - 字段名称数字 :
userId, (中文(简体) ),createdAt, (中文(简体) ),isCompleted→user_id, (中文(简体) ),created_at, (中文(简体) ),is_completed - 不需要手动转换!
QQ 重要: 总是在您的代码中使用骆驼Case
- ✅ 对数字 :
blink.db.emailDrafts.create({ userId: user.id, createdAt: new Date() }) - ❌ 错误数字 :
blink.db.email_drafts.create({ user_id: user.id, created_at: new Date() })
// Create (ID auto-generated if not provided)
const todo = await blink.db.todos.create({
id: 'todo_12345', // Optional - auto-generated if not provided
title: 'Learn Blink SDK',
userId: user.id, // camelCase in code
createdAt: new Date(), // camelCase in code
isCompleted: false // camelCase in code
})
// Read with filtering - returns camelCase fields
const todos = await blink.db.todos.list({
where: {
AND: [
{ userId: user.id }, // camelCase in filters
{ OR: [{ status: 'open' }, { priority: 'high' }] }
]
},
orderBy: { createdAt: 'desc' }, // camelCase in orderBy
limit: 20
})
// `todos` is a direct array: Todo[]
// Note: Boolean fields are returned as "0"/"1" strings from SQLite
// Check boolean values using Number(value) > 0
const completedTodos = todos.filter(todo => Number(todo.isCompleted) > 0)
const incompleteTodos = todos.filter(todo => Number(todo.isCompleted) === 0)
// Update
await blink.db.todos.update(todo.id, { isCompleted: true })
// Delete
await blink.db.todos.delete(todo.id)
// Bulk operations (IDs auto-generated if not provided)
await blink.db.todos.createMany([
{ title: 'Task 1', userId: user.id }, // ID will be auto-generated
{ id: 'custom_id', title: 'Task 2', userId: user.id } // Custom ID provided
])
await blink.db.todos.upsertMany([...])
AI 操作
// Text generation (simple prompt)
const { text } = await blink.ai.generateText({
prompt: 'Write a poem about coding',
maxTokens: 150
})
// Web search (OpenAI only) - get real-time information
const { text, sources } = await blink.ai.generateText({
prompt: 'Who is the current US president?',
search: true // Returns current info + source URLs
})
// Multi-step reasoning - for complex analysis
const { text } = await blink.ai.generateText({
prompt: 'Research and analyze tech trends',
search: true,
maxSteps: 10, // Override default (25 when tools used)
experimental_continueSteps: true // Override default (true when tools used)
})
// Text generation with image content
// ⚠️ IMPORTANT: Images must be HTTPS URLs with file extensions (.jpg, .jpeg, .png, .gif, .webp)
// For file uploads, use blink.storage.upload() first to get public HTTPS URLs
// 🚫 CRITICAL: When uploading files, NEVER hardcode extensions - use file.name or auto-detection
const { text } = await blink.ai.generateText({
messages: [
{
role: "user",
content: [
{ type: "text", text: "What do you see in this image?" },
{ type: "image", image: "https://storage.googleapis.com/.../.../photo.jpg" }
]
}
]
})
// Mixed content with multiple images
const { text } = await blink.ai.generateText({
messages: [
{
role: "user",
content: [
{ type: "text", text: "Compare these two images:" },
{ type: "image", image: "https://storage.googleapis.com/.../.../image1.jpg" },
{ type: "image", image: "https://cdn.example.com/image2.png" }
]
}
]
})
// Structured object generation
const { object } = await blink.ai.generateObject({
prompt: 'Generate a user profile',
schema: {
type: 'object',
properties: {
name: { type: 'string' },
age: { type: 'number' }
}
}
})
// ⚠️ IMPORTANT: Schema Rule for generateObject()
// The top-level schema MUST use type: "object" - you cannot use type: "array" at the top level
// This ensures clear, robust, and extensible API calls with named parameters
// ✅ Correct: Array inside object
const { object: todoList } = await blink.ai.generateObject({
prompt: 'Generate a list of 5 daily tasks',
schema: {
type: 'object',
properties: {
tasks: {
type: 'array',
items: {
type: 'object',
properties: {
title: { type: 'string' },
priority: { type: 'string', enum: ['low', 'medium', 'high'] }
}
}
}
},
required: ['tasks']
}
})
// Result: { tasks: [{ title: "Exercise", priority: "high" }, ...] }
// ❌ Wrong: Top-level array (will fail)
// const { object } = await blink.ai.generateObject({
// prompt: 'Generate tasks',
// schema: {
// type: 'array', // ❌ This will throw an error
// items: { type: 'string' }
// }
// })
// Error: "schema must be a JSON Schema of 'type: \"object\"', got 'type: \"array\"'"
// Generate and modify images with AI - Multi-Model Support (10 models available)
// 🔥 Choose between fast generation or high-quality results
// 🎨 For style transfer: provide ALL images in the images array, don't reference URLs in prompts
// Basic image generation (uses default fast model: fal-ai/nano-banana)
const { data } = await blink.ai.generateImage({
prompt: 'A serene landscape with mountains and a lake at sunset'
})
console.log('Image URL:', data[0].url)
// High-quality generation with Pro model
const { data: proImage } = await blink.ai.generateImage({
prompt: 'A detailed infographic about AI with charts and diagrams',
model: 'fal-ai/nano-banana-pro', // High quality model
n: 1,
size: '1792x1024' // Custom size
})
// Generate multiple variations
const { data } = await blink.ai.generateImage({
prompt: 'A futuristic robot in different poses',
model: 'fal-ai/nano-banana', // Fast model
n: 3
})
data.forEach((img, i) => console.log(`Image ${i+1}:`, img.url))
**Available Models for Text-to-Image:**
| Model | Speed | Quality | Best For |
|-------|-------|---------|----------|
| `fal-ai/nano-banana` (default) | ⚡ Fast | Good | Prototypes, high-volume generation |
| `fal-ai/nano-banana-pro` | Standard | ⭐ Excellent | Marketing materials, high-fidelity visuals |
| `fal-ai/gemini-25-flash-image` | ⚡ Fast | Good | Alias for `nano-banana` |
| `fal-ai/gemini-3-pro-image-preview` | Standard | ⭐ Excellent | Alias for `nano-banana-pro` |
| `gemini-2.5-flash-image-preview` | ⚡ Fast | Good | Legacy - Direct Gemini API |
| `gemini-3-pro-image-preview` | Standard | ⭐ Excellent | Legacy - Direct Gemini API |
// Image editing - transform existing images with prompts (uses default fast model)
const { data: headshots } = await blink.ai.modifyImage({
images: ['https://storage.example.com/user-photo.jpg'], // Up to 16 images supported!
prompt: 'Transform into professional business headshot with studio lighting'
})
// High-quality editing with Pro model
const { data: proEdited } = await blink.ai.modifyImage({
images: ['https://storage.example.com/portrait.jpg'],
prompt: 'Add a majestic ancient tree in the background with glowing leaves',
model: 'fal-ai/nano-banana-pro/edit' // High quality editing
})
// Advanced image editing with multiple input images
const { data } = await blink.ai.modifyImage({
images: [
'https://storage.example.com/photo1.jpg',
'https://storage.example.com/photo2.jpg',
'https://storage.example.com/photo3.jpg'
],
prompt: 'Combine these architectural styles into a futuristic building design',
model: 'fal-ai/nano-banana/edit', // Fast editing
n: 2
})
**Available Models for Image Editing:**
| Model | Speed | Quality | Best For |
|-------|-------|---------|----------|
| `fal-ai/nano-banana/edit` (default) | ⚡ Fast | Good | Quick adjustments, style transfers |
| `fal-ai/nano-banana-pro/edit` | Standard | ⭐ Excellent | Detailed retouching, complex edits |
| `fal-ai/gemini-25-flash-image/edit` | ⚡ Fast | Good | Alias for `nano-banana/edit` |
| `fal-ai/gemini-3-pro-image-preview/edit` | Standard | ⭐ Excellent | Alias for `nano-banana-pro/edit` |
// 🎨 Style Transfer & Feature Application
// ⚠️ IMPORTANT: When applying styles/features from one image to another,
// provide ALL images in the array - don't reference images in the prompt text
// ❌ WRONG - Don't reference images in prompt
const wrongWay = await blink.ai.modifyImage({
images: [userPhotoUrl],
prompt: `Apply this hairstyle from the reference image to the person's head. Reference hairstyle: ${hairstyleUrl}`
})
// ✅ CORRECT - Provide all images in the array
const { data } = await blink.ai.modifyImage({
images: [userPhotoUrl, hairstyleUrl], // Both images provided
prompt: 'Apply the hairstyle from the second image to the person in the first image. Blend naturally with face shape.'
})
// More style transfer examples
const { data } = await blink.ai.modifyImage({
images: [portraitUrl, artworkUrl],
prompt: 'Apply the artistic style and color palette from the second image to the portrait in the first image'
})
const { data } = await blink.ai.modifyImage({
images: [roomPhotoUrl, designReferenceUrl],
prompt: 'Redesign the room in the first image using the interior design style shown in the second image'
})
// Multiple reference images for complex transformations
const { data } = await blink.ai.modifyImage({
images: [
originalPhotoUrl,
lightingReferenceUrl,
colorPaletteReferenceUrl,
compositionReferenceUrl
],
prompt: 'Transform the first image using the lighting style from image 2, color palette from image 3, and composition from image 4'
})
// 📱 File Upload + Style Transfer
// ⚠️ Extract file extension properly - never hardcode .jpg/.png
// ❌ WRONG - Hardcoded extension
const userUpload = await blink.storage.upload(file, `photos/${Date.now()}.jpg`) // Breaks HEIC/PNG files
// ✅ CORRECT - Extract original extension
const userUpload = await blink.storage.upload(
userPhoto.file,
`photos/${Date.now()}.${userPhoto.file.name.split('.').pop()}`
)
const hairstyleUpload = await blink.storage.upload(
hairstylePhoto.file,
`haircuts/${Date.now()}.${hairstylePhoto.file.name.split('.').pop()}`
)
const { data } = await blink.ai.modifyImage({
images: [userUpload.publicUrl, hairstyleUpload.publicUrl],
prompt: 'Apply hairstyle from second image to person in first image'
})
// Speech synthesis
const { url } = await blink.ai.generateSpeech({
text: 'Hello, world!',
voice: 'nova'
})
// Audio transcription - Multiple input formats supported
// 🔥 Most common: Browser audio recording → Base64 → Transcription
let mediaRecorder: MediaRecorder;
let audioChunks: Blob[] = [];
// Step 1: Start recording
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
mediaRecorder = new MediaRecorder(stream);
mediaRecorder.ondataavailable = (event) => {
audioChunks.push(event.data);
};
mediaRecorder.start();
// Step 2: Stop recording and transcribe
mediaRecorder.onstop = async () => {
const audioBlob = new Blob(audioChunks, { type: 'audio/wav' });
// SAFE method for large files - use FileReader (recommended)
const base64 = await new Promise<string>((resolve, reject) => {
const reader = new FileReader();
reader.onload = () => {
const dataUrl = reader.result as string;
const base64Data = dataUrl.split(',')[1]; // Extract base64 part
resolve(base64Data);
};
reader.onerror = reject;
reader.readAsDataURL(audioBlob);
});
// Transcribe using base64 (preferred method)
const { text } = await blink.ai.transcribeAudio({
audio: base64, // Raw base64 string
language: 'en'
});
console.log('Transcription:', text);
audioChunks = []; // Reset for next recording
};
// Alternative: Data URL format (also supported)
const reader = new FileReader();
reader.onload = async () => {
const dataUrl = reader.result as string;
const { text } = await blink.ai.transcribeAudio({
audio: dataUrl, // Data URL format
language: 'en'
});
};
reader.readAsDataURL(audioBlob);
// File upload transcription
const fileInput = document.getElementById('audioFile') as HTMLInputElement;
const file = fileInput.files[0];
// Option 1: Convert to base64 using FileReader (recommended for large files)
const base64Audio = await new Promise<string>((resolve, reject) => {
const reader = new FileReader();
reader.onload = () => {
const dataUrl = reader.result as string;
const base64Data = dataUrl.split(',')[1]; // Extract base64 part
resolve(base64Data);
};
reader.onerror = reject;
reader.readAsDataURL(file);
});
const { text } = await blink.ai.transcribeAudio({
audio: base64Audio,
language: 'en'
});
// Option 2: Use ArrayBuffer directly (works for any file size)
const arrayBuffer = await file.arrayBuffer();
const { text } = await blink.ai.transcribeAudio({
audio: arrayBuffer,
language: 'en'
});
// Option 3: Use Uint8Array directly (works for any file size)
const arrayBuffer = await file.arrayBuffer();
const uint8Array = new Uint8Array(arrayBuffer);
const { text } = await blink.ai.transcribeAudio({
audio: uint8Array,
language: 'en'
});
// Public URL transcription (for hosted audio files)
const { text } = await blink.ai.transcribeAudio({
audio: 'https://example.com/audio/meeting.mp3',
language: 'en'
});
// Advanced options
const { text } = await blink.ai.transcribeAudio({
audio: base64Audio,
language: 'en',
model: 'whisper-1',
response_format: 'verbose_json' // Get timestamps and confidence scores
});
// Supported audio formats: MP3, WAV, M4A, FLAC, OGG, WebM
// Supported input types:
// - string: Base64 data, Data URL (data:audio/...;base64,...), or public URL (https://...)
// - ArrayBuffer: Raw audio buffer (works for any file size)
// - Uint8Array: Audio data as byte array (works for any file size)
// - number[]: Audio data as number array
// ⚠️ IMPORTANT: For large audio files, use FileReader or ArrayBuffer/Uint8Array
// Avoid btoa(String.fromCharCode(...array)) as it crashes with large files
// Streaming support
await blink.ai.streamText(
{ prompt: 'Write a story...' },
(chunk) => console.log(chunk)
)
// Streaming with web search
await blink.ai.streamText(
{ prompt: 'Latest AI news', search: true },
(chunk) => console.log(chunk)
)
// React streaming example - parse chunks for immediate UI display
const [streamingText, setStreamingText] = useState('')
await blink.ai.streamText(
{ prompt: 'Write a story about AI...' },
(chunk) => {
setStreamingText(prev => prev + chunk) // chunk is a string
}
)
数据操作
// Simple text extraction (default - returns single string)
const text = await blink.data.extractFromUrl('https://example.com/document.pdf');
console.log(typeof text); // 'string'
// Extract with chunking enabled
const chunks = await blink.data.extractFromUrl('https://example.com/document.pdf', {
chunking: true,
chunkSize: 2000
});
console.log(Array.isArray(chunks)); // true
// Extract from different file types
const csvText = await blink.data.extractFromUrl('https://example.com/data.csv');
const htmlText = await blink.data.extractFromUrl('https://example.com/page.html');
const jsonText = await blink.data.extractFromUrl('https://example.com/config.json');
// Extract from uploaded file blob (simple)
const fileInput = document.getElementById('fileInput') as HTMLInputElement;
const file = fileInput.files[0];
const extractedText = await blink.data.extractFromBlob(file);
// Extract from uploaded file blob (with chunking)
const chunks = await blink.data.extractFromBlob(file, {
chunking: true,
chunkSize: 3000
});
// Website scraping (NEW!) - Crystal clear destructuring
const { markdown, metadata, links, extract } = await blink.data.scrape('https://example.com');
console.log(markdown); // Clean markdown content
console.log(metadata.title); // Page title
console.log(links.length); // Number of links found
// Even cleaner - destructure only what you need
const { metadata, extract } = await blink.data.scrape('https://blog.example.com/article');
console.log(metadata.title); // Always available
console.log(extract.headings); // Always an array
// Website screenshots (NEW!)
const screenshotUrl = await blink.data.screenshot('https://example.com');
console.log(screenshotUrl); // Direct URL to screenshot image
// Full-page screenshot with custom dimensions
const fullPageUrl = await blink.data.screenshot('https://example.com', {
fullPage: true,
width: 1920,
height: 1080
});
// 🔥 Web Search (NEW!) - Google search results with clean structure
// Perfect for getting real-time information and current data
// Basic web search - just provide a query
const searchResults = await blink.data.search('chatgpt');
console.log(searchResults.organic_results); // Main search results
console.log(searchResults.related_searches); // Related search suggestions
console.log(searchResults.people_also_ask); // People also ask questions
// Search with location for local results
const localResults = await blink.data.search('best restaurants', {
location: 'San Francisco,CA,United States'
});
console.log(localResults.local_results); // Local business results
console.log(localResults.organic_results); // Regular web results
// News search - get latest news articles
const newsResults = await blink.data.search('artificial intelligence', {
type: 'news'
});
console.log(newsResults.news_results); // News articles with dates and sources
// Image search - find images
const imageResults = await blink.data.search('elon musk', {
type: 'images',
limit: 20
});
console.log(imageResults.image_results); // Image results with thumbnails
// Search in different languages
const spanishResults = await blink.data.search('noticias tecnología', {
language: 'es',
type: 'news'
});
// Shopping search - find products
const shoppingResults = await blink.data.search('macbook pro', {
type: 'shopping'
});
console.log(shoppingResults.shopping_results); // Product results with prices
// All search types return consistent, structured data:
// - organic_results: Main search results (always included)
// - related_searches: Related search suggestions
// - people_also_ask: FAQ-style questions and answers
// - local_results: Local businesses (when location provided)
// - news_results: News articles (when type='news')
// - image_results: Images (when type='images')
// - shopping_results: Products (when type='shopping')
// - ads: Sponsored results (when present)
// 🔥 Secure API Proxy (NEW!) - Make API calls with secret substitution
// Basic API call with secret substitution
const response = await blink.data.fetch({
url: 'https://api.sendgrid.com/v3/mail/send',
method: 'POST',
headers: {
'Authorization': 'Bearer {{sendgrid_api_key}}', // Secret replaced server-side
'Content-Type': 'application/json'
},
body: {
from: { email: '[email protected]' },
personalizations: [{ to: [{ email: '[email protected]' }] }],
subject: 'Hello from Blink',
content: [{ type: 'text/plain', value: 'Sent securely through Blink!' }]
}
});
console.log('Email sent:', response.status === 200);
console.log('Response:', response.body);
console.log('Took:', response.durationMs, 'ms');
// GET request with secret in URL and query params
const weatherData = await blink.data.fetch({
url: 'https://api.openweathermap.org/data/2.5/weather',
method: 'GET',
query: {
q: 'London',
appid: '{{openweather_api_key}}', // Secret replaced in query params
units: 'metric'
}
});
console.log('Weather:', weatherData.body.main.temp, '°C');
// Async/background requests (fire-and-forget)
const asyncResponse = await blink.data.fetchAsync({
url: 'https://api.stripe.com/v1/customers',
method: 'POST',
headers: {
'Authorization': 'Bearer {{stripe_secret_key}}',
'Content-Type': 'application/x-www-form-urlencoded'
},
body: '[email protected]&name=John Doe'
});
console.log(asyncResponse.status); // 'triggered'
console.log(asyncResponse.message); // 'Request triggered in background'
// Multiple secrets in different places
const complexRequest = await blink.data.fetch({
url: 'https://api.github.com/repos/{{github_username}}/{{repo_name}}/issues',
method: 'POST',
headers: {
'Authorization': 'token {{github_token}}',
'Accept': 'application/vnd.github.v3+json',
'User-Agent': '{{app_name}}'
},
body: {
title: 'Bug Report',
body: 'Found via {{app_name}} monitoring'
}
});
// Secret substitution works everywhere:
// - URL path: /api/{{version}}/users
// - Query params: ?key={{api_key}}&user={{user_id}}
// - Headers: Authorization: Bearer {{token}}
// - Body: { "apiKey": "{{secret}}", "data": "{{value}}" }
// Error handling for data extraction
try {
const result = await blink.data.extractFromUrl('https://example.com/huge-file.pdf');
} catch (error) {
if (error instanceof BlinkDataError) {
console.error('Data processing error:', error.message);
}
}
存储操作
// Upload files (returns public URL directly)
const { publicUrl } = await blink.storage.upload(
file,
`uploads/${Date.now()}.${file.name.split('.').pop()}`, // ✅ Extract original extension
{
upsert: true,
onProgress: (percent) => console.log(`${percent}%`)
}
)
// ❌ WRONG - Hardcoded extensions break HEIC/PNG/WebP files
const wrong = await blink.storage.upload(file, `uploads/${Date.now()}.jpg`) // Corrupts non-JPG files
// ✅ CORRECT - Extract file extension
const correct = await blink.storage.upload(file, `uploads/${Date.now()}.${file.name.split('.').pop()}`)
// Remove files
await blink.storage.remove('file1.jpg', 'file2.jpg')
通知业务
// 🔥 Email Notifications (NEW!) - Send emails with attachments, custom branding, and delivery tracking
// Send a simple email - returns success status and message ID
const result = await blink.notifications.email({
to: '[email protected]',
subject: 'Your order has shipped!',
html: '<h1>Order Confirmation</h1><p>Your order #12345 is on its way.</p>'
})
console.log(result.success) // true/false - whether email was sent
console.log(result.messageId) // "msg_abc123..." - unique message identifier
// Send with plain text fallback (recommended for better deliverability)
const { success, messageId } = await blink.notifications.email({
to: '[email protected]',
subject: 'Welcome to our platform!',
html: '<h1>Welcome!</h1><p>Thanks for joining us.</p>',
text: 'Welcome!\n\nThanks for joining us.' // Plain text version
})
// Send an email with attachments and custom branding
const result = await blink.notifications.email({
to: ['[email protected]', '[email protected]'],
from: '[email protected]', // Must be valid email address
replyTo: '[email protected]',
subject: 'New Invoice #12345',
html: `
<div style="font-family: Arial, sans-serif;">
<h2>Invoice Ready</h2>
<p>Please find the invoice attached.</p>
</div>
`,
text: 'Invoice Ready\n\nPlease find the invoice attached.',
cc: '[email protected]',
bcc: '[email protected]',
attachments: [
{
url: 'https://mycompany.com/invoices/12345.pdf',
filename: 'Invoice-12345.pdf', // Custom filename
type: 'application/pdf' // MIME type (optional)
},
{
url: 'https://mycompany.com/terms.pdf',
filename: 'Terms-of-Service.pdf'
}
]
})
console.log(`Email ${result.success ? 'sent' : 'failed'}`)
console.log(`Message ID: ${result.messageId}`)
// Send to multiple recipients with different recipient types
const { success, messageId } = await blink.notifications.email({
to: ['[email protected]', '[email protected]'],
cc: ['[email protected]'],
bcc: ['[email protected]', '[email protected]'],
from: '[email protected]',
subject: 'Monthly Newsletter',
html: '<h2>This Month\'s Updates</h2><p>Here are the highlights...</p>'
})
// Dynamic email content with user data
const user = await blink.auth.me()
const welcomeEmail = await blink.notifications.email({
to: user.email,
from: '[email protected]',
subject: `Welcome ${user.displayName}!`,
html: `
<h1>Hi ${user.displayName}!</h1>
<p>Welcome to our platform. Your account is now active.</p>
<p>Account ID: ${user.id}</p>
<a href="https://myapp.com/dashboard">Get Started</a>
`,
text: `Hi ${user.displayName}!\n\nWelcome to our platform. Your account is now active.\nAccount ID: ${user.id}\n\nGet Started: https://myapp.com/dashboard`
})
// Comprehensive error handling with detailed error information
try {
const result = await blink.notifications.email({
to: '[email protected]',
subject: 'Important Update',
html: '<p>This is an important update about your account.</p>'
})
if (result.success) {
console.log('✅ Email sent successfully!')
console.log('📧 Message ID:', result.messageId)
} else {
console.error('❌ Email failed to send')
// Handle failed send (retry logic, fallback notification, etc.)
}
} catch (error) {
if (error instanceof BlinkNotificationsError) {
console.error('❌ Email error:', error.message)
// Common error scenarios:
// - "The 'to', 'subject', and either 'html' or 'text' fields are required."
// - "Invalid email address format"
// - "Attachment URL must be accessible"
// - "Failed to send email: Rate limit exceeded"
// Handle specific error types
if (error.message.includes('Rate limit')) {
// Implement retry with backoff
console.log('⏳ Rate limited, will retry later')
} else if (error.message.includes('Invalid email')) {
// Log invalid email for cleanup
console.log('📧 Invalid email address, removing from list')
}
} else {
console.error('❌ Unexpected error:', error)
}
}
// Email validation and best practices
const validateAndSendEmail = async (recipient: string, subject: string, content: string) => {
// Basic validation
if (!recipient.includes('@') || !subject.trim() || !content.trim()) {
throw new Error('Invalid email parameters')
}
try {
const result = await blink.notifications.email({
to: recipient,
from: '[email protected]',
subject: subject,
html: `
<div style="max-width: 600px; margin: 0 auto; font-family: Arial, sans-serif;">
<div style="background: #f8f9fa; padding: 20px; text-align: center;">
<h1 style="color: #333; margin: 0;">My Company</h1>
</div>
<div style="padding: 20px;">
${content}
</div>
<div style="background: #f8f9fa; padding: 15px; text-align: center; font-size: 12px; color: #666;">
<p>© 2024 My Company. All rights reserved.</p>
<p><a href="https://mycompany.com/unsubscribe">Unsubscribe</a></p>
</div>
</div>
`,
text: content.replace(/<[^>]*>/g, '') // Strip HTML for text version
})
return result
} catch (error) {
console.error(`Failed to send email to ${recipient}:`, error)
throw error
}
}
// Usage with validation
try {
const result = await validateAndSendEmail(
'[email protected]',
'Account Verification Required',
'<p>Please verify your account by clicking the link below.</p><a href="https://myapp.com/verify">Verify Account</a>'
)
console.log('Email sent with ID:', result.messageId)
} catch (error) {
console.error('Email validation or sending failed:', error.message)
}
// Bulk email sending with error handling
const sendBulkEmails = async (recipients: string[], subject: string, htmlContent: string) => {
const results = []
for (const recipient of recipients) {
try {
const result = await blink.notifications.email({
to: recipient,
from: '[email protected]',
subject,
html: htmlContent,
text: htmlContent.replace(/<[^>]*>/g, '')
})
results.push({
recipient,
success: result.success,
messageId: result.messageId
})
// Rate limiting: wait between sends
await new Promise(resolve => setTimeout(resolve, 100))
} catch (error) {
results.push({
recipient,
success: false,
error: error.message
})
}
}
return results
}
// Response format details:
// ✅ Success response: { success: true, messageId: "msg_abc123...", from: "[email protected]", to: ["[email protected]"], subject: "Email Subject", timestamp: "2024-01-20T10:30:00.000Z" }
// ❌ The method throws BlinkNotificationsError on failure
// 🔍 Error types: validation errors, rate limits, network issues, invalid attachments
// API Response Format:
// The notifications API returns data directly (not wrapped in {data: ..., error: ...})
// This is consistent with other Blink APIs like database and storage
// All Blink APIs follow this pattern for clean, predictable responses
// Best practices:
// 1. Always include both HTML and text versions for better deliverability
// 2. Use valid email addresses for 'from' field (not display names)
// 3. Keep HTML simple with inline CSS for email client compatibility
// 4. Handle rate limits with retry logic
// 5. Validate email addresses before sending
// 6. Use message IDs for tracking and debugging
// 7. Include unsubscribe links for compliance
分析操作
// 🔥 Analytics (NEW!) - Automatic pageview tracking + custom events
// Pageviews are tracked automatically on initialization and route changes
// Log custom events - context data is added automatically
blink.analytics.log('button_clicked', {
button_id: 'signup',
campaign: 'summer_sale'
})
// All events automatically include:
// - timestamp, project_id, session_id (no user IDs or emails)
// - pathname (current page), referrer, screen_width
// - device/browser/OS info (parsed server-side)
// - channel detection (Organic Search, Social, Direct, etc.)
// - UTM parameters (source, medium, campaign, content, term)
// - UTM persistence for attribution tracking across sessions
// Control analytics
// createClient({ projectId, analytics: false }) → SDK analytics off from the start: nothing sent or stored
// (sites hosted on Blink also get a separate page-view tag, not controlled here)
blink.analytics.disable() // stops sending, clears queued events and stored IDs, remembers the opt-out
blink.analytics.enable() // opts back in
const isEnabled = blink.analytics.isEnabled()
// Off automatically when the browser sends Do Not Track or Global Privacy Control
// Clear attribution data (e.g., when user logs out)
blink.analytics.clearAttribution()
// Features: Privacy-first, offline support, event batching, session management
// Attribution: UTM params persist across sessions for conversion tracking
// How UTM persistence works:
// 1. User visits with ?utm_source=google&utm_campaign=summer_sale
// 2. These params are saved to localStorage for attribution
// 3. Future events (even days later) include these UTM params
// 4. Perfect for tracking which campaigns drive conversions
// 5. New UTM params override old ones (last-touch model available)
实时操作
零波纹连接管理!
所有连接状态,排队,再连接都是自动处理的. 别再犯"状态"错误了!
QQ 反应用户: 见 反应 + 实时连接 下面的章节,用于适当的ASync清理模式,以避免“订阅已取消”错误.
// 🔥 Real-time Messaging & Presence (NEW!)
// Perfect for chat apps, live collaboration, multiplayer games, and live updates
// Simple subscribe and publish (most common pattern)
const unsubscribe = await blink.realtime.subscribe('chat-room', (message) => {
console.log('New message:', message.data)
console.log('From user:', message.userId)
console.log('Message type:', message.type)
})
// message callback receives RealtimeMessage format:
// {
// id: '1640995200000-0',
// type: 'chat',
// data: { text: 'Hello!', timestamp: 1640995200000 },
// timestamp: 1640995200000,
// userId: 'user123',
// metadata: { displayName: 'John' }
// }
// Publish a message to all subscribers - returns message ID
const messageId = await blink.realtime.publish('chat-room', 'message', {
text: 'Hello everyone!',
timestamp: Date.now()
})
// messageId is string format: '1640995200000-0'
// Advanced channel usage with presence tracking
const channel = blink.realtime.channel('game-lobby')
// Subscribe with user metadata
await channel.subscribe({
userId: user.id,
metadata: {
displayName: user.name,
avatar: user.avatar,
status: 'online'
}
})
// Listen for messages
const unsubMessage = channel.onMessage((message) => {
if (message.type === 'chat') {
addChatMessage(message.data)
} else if (message.type === 'game-move') {
updateGameState(message.data)
}
})
// message parameter format:
// {
// id: '1640995200000-0',
// type: 'chat',
// data: { text: 'Hello!', timestamp: 1640995200000 },
// timestamp: 1640995200000,
// userId: 'user123',
// metadata: { displayName: 'John' }
// }
// Listen for presence changes (who's online)
// Callback receives array of PresenceUser objects
const unsubPresence = channel.onPresence((users) => {
console.log(`${users.length} users online:`)
users.forEach(user => {
console.log(`- ${user.metadata?.displayName} (${user.userId})`)
})
updateOnlineUsersList(users)
})
// users parameter format:
// [
// {
// userId: 'user123',
// metadata: { displayName: 'John', status: 'online' },
// joinedAt: 1640995200000,
// lastSeen: 1640995230000
// }
// ]
// Publish different types of messages
await channel.publish('chat', { text: 'Hello!' }, { userId: user.id })
await channel.publish('game-move', { x: 5, y: 3, piece: 'king' })
await channel.publish('typing', { isTyping: true })
// Get current presence (one-time check)
// Returns array of PresenceUser objects directly
const currentUsers = await channel.getPresence()
console.log('Currently online:', currentUsers.length)
// currentUsers is PresenceUser[] format:
// [
// {
// userId: 'user123',
// metadata: { displayName: 'John', status: 'online' },
// joinedAt: 1640995200000,
// lastSeen: 1640995230000
// }
// ]
// Get message history - returns array of RealtimeMessage objects
const recentMessages = await channel.getMessages({
limit: 50,
before: lastMessageId // Pagination support
})
// recentMessages is RealtimeMessage[] format:
// [
// {
// id: '1640995200000-0',
// type: 'chat',
// data: { text: 'Hello!', timestamp: 1640995200000 },
// timestamp: 1640995200000,
// userId: 'user123',
// metadata: { displayName: 'John' }
// }
// ]
// Cleanup when done
unsubMessage()
unsubPresence()
await channel.unsubscribe()
// Or use the simple unsubscribe from subscribe()
unsubscribe()
// Multiple channels for different features
const chatChannel = blink.realtime.channel('chat')
const notificationChannel = blink.realtime.channel('notifications')
const gameChannel = blink.realtime.channel('game-state')
// Each channel is independent with its own subscribers and presence
await chatChannel.subscribe({ userId: user.id })
await notificationChannel.subscribe({ userId: user.id })
await gameChannel.subscribe({ userId: user.id, metadata: { team: 'red' } })
// Real-time collaboration example
const docChannel = blink.realtime.channel(`document-${docId}`)
await docChannel.subscribe({
userId: user.id,
metadata: {
name: user.name,
cursor: { line: 1, column: 0 }
}
})
// Broadcast cursor movements
docChannel.onMessage((message) => {
if (message.type === 'cursor-move') {
updateUserCursor(message.userId, message.data.position)
} else if (message.type === 'text-change') {
applyTextChange(message.data.delta)
}
})
// Send cursor updates
await docChannel.publish('cursor-move', {
position: { line: 5, column: 10 }
}, { userId: user.id })
// Send text changes
await docChannel.publish('text-change', {
delta: { insert: 'Hello', retain: 5 },
timestamp: Date.now()
})
// Presence with live cursor positions
docChannel.onPresence((users) => {
users.forEach(user => {
if (user.metadata?.cursor) {
showUserCursor(user.userId, user.metadata.cursor)
}
})
})
// Auto-cleanup on page unload
window.addEventListener('beforeunload', () => {
docChannel.unsubscribe()
})
// Error handling
try {
await blink.realtime.publish('restricted-channel', 'message', { data: 'test' })
} catch (error) {
if (error instanceof BlinkRealtimeError) {
console.error('Realtime error:', error.message)
}
}
高级用途
处理错误
import {
BlinkAuthError,
BlinkAuthErrorCode,
BlinkAIError,
BlinkStorageError,
BlinkDataError,
BlinkRealtimeError,
BlinkNotificationsError
} from '@blinkdotnew/sdk'
// Authentication error handling
try {
const user = await blink.auth.signInWithEmail(email, password)
} catch (error) {
if (error instanceof BlinkAuthError) {
switch (error.code) {
case BlinkAuthErrorCode.EMAIL_NOT_VERIFIED:
console.log('Email verification required')
await blink.auth.sendEmailVerification()
break
case BlinkAuthErrorCode.INVALID_CREDENTIALS:
console.error('Invalid email or password')
break
case BlinkAuthErrorCode.RATE_LIMITED:
console.error('Too many attempts, try again later')
break
default:
console.error('Auth error:', error.message)
}
}
}
// Other error types
try {
const { text } = await blink.ai.generateText({ prompt: 'Hello' })
} catch (error) {
if (error instanceof BlinkAIError) {
console.error('AI error:', error.message)
}
}
自定义配置
const blink = createClient({
projectId: 'your-project',
baseUrl: 'https://custom-api.example.com',
auth: {
mode: 'headless', // 'managed' | 'headless'
authUrl: 'https://your-auth-service.com', // Custom auth domain (for all auth endpoints)
coreUrl: 'https://custom-core.example.com', // Custom API domain (for db, ai, storage)
// Providers controlled via project settings
redirectUrl: 'https://myapp.com/dashboard',
roles: {
admin: { permissions: ['*'] },
editor: { permissions: ['posts.create', 'posts.update'], inherit: ['viewer'] },
viewer: { permissions: ['posts.read'] }
}
},
httpClient: {
timeout: 30000,
retries: 3
}
})
类型脚本支持
SDK是用TypeScript写成的,并提供完全类型的安全性:
interface Todo {
id: string
title: string
isCompleted: boolean // Will be returned as "0" or "1" string from SQLite
userId: string // Automatically converted from snake_case user_id
createdAt: string // Automatically converted from snake_case created_at
}
// Note: Boolean fields are returned as "0"/"1" strings from SQLite
// Use Number(value) > 0 to check boolean values
const todos = await blink.db.todos.list<Todo>()
// Check boolean values properly
const completedTodos = todos.filter(todo => Number(todo.isCompleted) > 0)
const incompleteTodos = todos.filter(todo => Number(todo.isCompleted) === 0)
// When filtering by boolean values in queries, use "0"/"1" strings
const onlyCompleted = await blink.db.todos.list<Todo>({
where: { isCompleted: "1" } // Use string "1" for true, "0" for false
})
// todos is fully typed as Todo[]
API 代理服务器的秘密管理
这个 blink.data.fetch() 方法允许您用自动秘密替代方式安全地拨打API。 下面是如何设置:
步骤一: 将您的秘密存储在您的 Blink 项目中 访问您的项目仪表板 眨一眼,换一眼 并添加您的 API 密钥到“ 秘密” 部分 :
sendgrid_api_key→SG.abc123...openweather_api_key→d4f5g6h7...stripe_secret_key→sk_live_abc123...
第2步:在您的 API 呼叫中使用秘密
// Secrets are automatically substituted server-side - never exposed to frontend
const result = await blink.data.fetch({
url: 'https://api.example.com/endpoint',
headers: {
'Authorization': 'Bearer {{your_secret_key}}' // Replaced with actual value
}
})
第3步:秘密替代在任何地方都起作用
await blink.data.fetch({
url: 'https://api.{{service_domain}}/v{{api_version}}/users/{{user_id}}',
query: {
key: '{{api_key}}',
format: 'json'
},
headers: {
'X-API-Key': '{{secondary_key}}'
},
body: {
token: '{{auth_token}}',
data: 'regular string data'
}
})
全体 {{secret_name}} 占位符被您项目秘密存储的加密值所取代。 秘密从不离开服务器 也永远看不到前端代码.
框架实例
反应 + 实时连接
QQ 关键: 避免多个 WebSocket 连接
最常见的错误是在使用效果中使用失去清理功能的同源函数:
import type { RealtimeChannel } from '@blinkdotnew/sdk'
// ❌ WRONG - Async function loses cleanup (causes "Subscription cancelled" errors)
useEffect(() => {
const initApp = async () => {
const channel = blink.realtime.channel('room')
await cha