272 lines
7.1 KiB
Markdown
272 lines
7.1 KiB
Markdown
|
|
# 网页端集成示例代码
|
|||
|
|
|
|||
|
|
本文档提供网页端(https://cstb.upup.cool/)实现登录/注册回调的示例代码。
|
|||
|
|
|
|||
|
|
## 登录页面示例
|
|||
|
|
|
|||
|
|
在 `auth/login` 页面中:
|
|||
|
|
|
|||
|
|
```typescript
|
|||
|
|
'use client'
|
|||
|
|
|
|||
|
|
import { useState } from 'react'
|
|||
|
|
import { createClient } from '@/utils/supabase/client'
|
|||
|
|
import { useSearchParams } from 'next/navigation'
|
|||
|
|
|
|||
|
|
export default function LoginPage() {
|
|||
|
|
const [email, setEmail] = useState('')
|
|||
|
|
const [password, setPassword] = useState('')
|
|||
|
|
const [loading, setLoading] = useState(false)
|
|||
|
|
const searchParams = useSearchParams()
|
|||
|
|
const redirectTo = searchParams.get('redirect') // 应该是 'cstb://auth'
|
|||
|
|
|
|||
|
|
const supabase = createClient()
|
|||
|
|
|
|||
|
|
const handleLogin = async (e: React.FormEvent) => {
|
|||
|
|
e.preventDefault()
|
|||
|
|
setLoading(true)
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
const { data, error } = await supabase.auth.signInWithPassword({
|
|||
|
|
email,
|
|||
|
|
password,
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
if (error) {
|
|||
|
|
alert(`登录失败: ${error.message}`)
|
|||
|
|
setLoading(false)
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (data.session) {
|
|||
|
|
// 如果有 redirect 参数,说明是从应用跳转过来的
|
|||
|
|
if (redirectTo) {
|
|||
|
|
// 构建 deep-link URL
|
|||
|
|
const redirectUrl = new URL(redirectTo)
|
|||
|
|
redirectUrl.searchParams.set('access_token', data.session.access_token)
|
|||
|
|
redirectUrl.searchParams.set('refresh_token', data.session.refresh_token)
|
|||
|
|
|
|||
|
|
// 重定向到应用
|
|||
|
|
window.location.href = redirectUrl.toString()
|
|||
|
|
} else {
|
|||
|
|
// 正常网页端跳转
|
|||
|
|
window.location.href = '/'
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error('Login error:', error)
|
|||
|
|
alert('登录时发生错误')
|
|||
|
|
setLoading(false)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return (
|
|||
|
|
<form onSubmit={handleLogin}>
|
|||
|
|
<input
|
|||
|
|
type="email"
|
|||
|
|
value={email}
|
|||
|
|
onChange={(e) => setEmail(e.target.value)}
|
|||
|
|
placeholder="邮箱"
|
|||
|
|
required
|
|||
|
|
/>
|
|||
|
|
<input
|
|||
|
|
type="password"
|
|||
|
|
value={password}
|
|||
|
|
onChange={(e) => setPassword(e.target.value)}
|
|||
|
|
placeholder="密码"
|
|||
|
|
required
|
|||
|
|
/>
|
|||
|
|
<button type="submit" disabled={loading}>
|
|||
|
|
{loading ? '登录中...' : '登录'}
|
|||
|
|
</button>
|
|||
|
|
</form>
|
|||
|
|
)
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 注册页面示例
|
|||
|
|
|
|||
|
|
在 `auth/signup` 页面中:
|
|||
|
|
|
|||
|
|
```typescript
|
|||
|
|
'use client'
|
|||
|
|
|
|||
|
|
import { useState } from 'react'
|
|||
|
|
import { createClient } from '@/utils/supabase/client'
|
|||
|
|
import { useSearchParams } from 'next/navigation'
|
|||
|
|
|
|||
|
|
export default function SignupPage() {
|
|||
|
|
const [email, setEmail] = useState('')
|
|||
|
|
const [password, setPassword] = useState('')
|
|||
|
|
const [confirmPassword, setConfirmPassword] = useState('')
|
|||
|
|
const [loading, setLoading] = useState(false)
|
|||
|
|
const searchParams = useSearchParams()
|
|||
|
|
const redirectTo = searchParams.get('redirect') // 应该是 'cstb://auth'
|
|||
|
|
|
|||
|
|
const supabase = createClient()
|
|||
|
|
|
|||
|
|
const handleSignup = async (e: React.FormEvent) => {
|
|||
|
|
e.preventDefault()
|
|||
|
|
|
|||
|
|
if (password !== confirmPassword) {
|
|||
|
|
alert('两次输入的密码不一致')
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
setLoading(true)
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
const { data, error } = await supabase.auth.signUp({
|
|||
|
|
email,
|
|||
|
|
password,
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
if (error) {
|
|||
|
|
alert(`注册失败: ${error.message}`)
|
|||
|
|
setLoading(false)
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (data.session) {
|
|||
|
|
// 如果有 redirect 参数,说明是从应用跳转过来的
|
|||
|
|
if (redirectTo) {
|
|||
|
|
// 构建 deep-link URL
|
|||
|
|
const redirectUrl = new URL(redirectTo)
|
|||
|
|
redirectUrl.searchParams.set('access_token', data.session.access_token)
|
|||
|
|
redirectUrl.searchParams.set('refresh_token', data.session.refresh_token)
|
|||
|
|
|
|||
|
|
// 重定向到应用
|
|||
|
|
window.location.href = redirectUrl.toString()
|
|||
|
|
} else {
|
|||
|
|
// 正常网页端跳转
|
|||
|
|
alert('注册成功!请检查邮箱验证链接。')
|
|||
|
|
window.location.href = '/'
|
|||
|
|
}
|
|||
|
|
} else {
|
|||
|
|
// 需要邮箱验证
|
|||
|
|
alert('注册成功!请检查邮箱中的验证链接。')
|
|||
|
|
if (!redirectTo) {
|
|||
|
|
window.location.href = '/'
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error('Signup error:', error)
|
|||
|
|
alert('注册时发生错误')
|
|||
|
|
setLoading(false)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return (
|
|||
|
|
<form onSubmit={handleSignup}>
|
|||
|
|
<input
|
|||
|
|
type="email"
|
|||
|
|
value={email}
|
|||
|
|
onChange={(e) => setEmail(e.target.value)}
|
|||
|
|
placeholder="邮箱"
|
|||
|
|
required
|
|||
|
|
/>
|
|||
|
|
<input
|
|||
|
|
type="password"
|
|||
|
|
value={password}
|
|||
|
|
onChange={(e) => setPassword(e.target.value)}
|
|||
|
|
placeholder="密码"
|
|||
|
|
required
|
|||
|
|
/>
|
|||
|
|
<input
|
|||
|
|
type="password"
|
|||
|
|
value={confirmPassword}
|
|||
|
|
onChange={(e) => setConfirmPassword(e.target.value)}
|
|||
|
|
placeholder="确认密码"
|
|||
|
|
required
|
|||
|
|
/>
|
|||
|
|
<button type="submit" disabled={loading}>
|
|||
|
|
{loading ? '注册中...' : '注册'}
|
|||
|
|
</button>
|
|||
|
|
</form>
|
|||
|
|
)
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 使用 OAuth 提供商的示例
|
|||
|
|
|
|||
|
|
如果使用第三方登录(如 Google、GitHub 等):
|
|||
|
|
|
|||
|
|
```typescript
|
|||
|
|
const handleOAuthLogin = async (provider: 'google' | 'github') => {
|
|||
|
|
const { data, error } = await supabase.auth.signInWithOAuth({
|
|||
|
|
provider,
|
|||
|
|
options: {
|
|||
|
|
redirectTo: redirectTo || `${window.location.origin}/auth/callback`,
|
|||
|
|
},
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
if (error) {
|
|||
|
|
alert(`登录失败: ${error.message}`)
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// OAuth 会重定向到回调页面,在回调页面中处理
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 回调页面处理
|
|||
|
|
|
|||
|
|
如果需要处理 OAuth 回调:
|
|||
|
|
|
|||
|
|
```typescript
|
|||
|
|
// auth/callback/page.tsx
|
|||
|
|
'use client'
|
|||
|
|
|
|||
|
|
import { useEffect } from 'react'
|
|||
|
|
import { createClient } from '@/utils/supabase/client'
|
|||
|
|
import { useSearchParams } from 'next/navigation'
|
|||
|
|
|
|||
|
|
export default function CallbackPage() {
|
|||
|
|
const searchParams = useSearchParams()
|
|||
|
|
const redirectTo = searchParams.get('redirect') // 从应用跳转时的原始 redirect
|
|||
|
|
|
|||
|
|
useEffect(() => {
|
|||
|
|
const handleCallback = async () => {
|
|||
|
|
const supabase = createClient()
|
|||
|
|
const { data: { session }, error } = await supabase.auth.getSession()
|
|||
|
|
|
|||
|
|
if (error) {
|
|||
|
|
console.error('Error getting session:', error)
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (session) {
|
|||
|
|
// 如果是从应用跳转过来的,重定向回应用
|
|||
|
|
if (redirectTo) {
|
|||
|
|
const redirectUrl = new URL(redirectTo)
|
|||
|
|
redirectUrl.searchParams.set('access_token', session.access_token)
|
|||
|
|
redirectUrl.searchParams.set('refresh_token', session.refresh_token)
|
|||
|
|
window.location.href = redirectUrl.toString()
|
|||
|
|
} else {
|
|||
|
|
// 正常网页端跳转
|
|||
|
|
window.location.href = '/'
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
void handleCallback()
|
|||
|
|
}, [redirectTo])
|
|||
|
|
|
|||
|
|
return <div>正在处理登录...</div>
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 注意事项
|
|||
|
|
|
|||
|
|
1. **安全性**: 确保只在 HTTPS 环境下使用,避免 token 泄露
|
|||
|
|
2. **错误处理**: 始终处理登录/注册失败的情况
|
|||
|
|
3. **用户体验**: 在重定向前显示加载状态
|
|||
|
|
4. **验证**: 如果启用了邮箱验证,需要处理未验证的情况
|
|||
|
|
|
|||
|
|
## 测试步骤
|
|||
|
|
|
|||
|
|
1. 在应用中使用 `https://cstb.upup.cool/auth/login?redirect=cstb://auth` 打开登录页面
|
|||
|
|
2. 完成登录后,应该自动跳转回应用
|
|||
|
|
3. 检查应用是否成功接收并设置了 session
|
|||
|
|
|