Files
what-to-eat/apps/web/app/utils/yunlefunAuth.ts
T
toor 76a5d71252 refactor: 只保留 Web,移除 Capacitor 原生壳
删除 android/、ios/ 两个 Capacitor 原生工程与 capacitor.config.ts、
ionic.config.json,以及 scripts/android、scripts/ios.sh 构建脚本;
根与 apps/web 的 @capacitor/* 依赖和相关 npm scripts 一并移除。

Web 侧去掉原生分支:登录只走云乐坊容器授权面板或顶层重定向,
暗色模式不再改原生状态栏,移除 Android 物理返回键处理。

保留 @nuxtjs/ionic(Web UI 框架)、云乐坊 window.ylf 桥接与 PWA。

决策记录见 docs/decisions/0004-web-only.md。
2026-10-21 12:27:50 +08:00

198 lines
4.4 KiB
TypeScript

import type {
SsoAuthorizationResult,
SsoRedirectResult,
} from '@yunlefun/sso'
export interface YunlefunAccount {
avatarUrl?: string
displayName: string
uid: string
}
export type YunlefunAuthStatus
= | 'checking'
| 'error'
| 'idle'
| 'signed-in'
| 'signed-out'
| 'signing-in'
export interface YunlefunAuthSnapshot {
account: YunlefunAccount | null
errorMessage: string
status: YunlefunAuthStatus
}
export type YunlefunAuthorizationResult
= | SsoAuthorizationResult
| Extract<SsoRedirectResult, { ok: false }>
export interface YunlefunAuthorizationAdapter {
consumeInitial: () => Promise<YunlefunAuthorizationResult | null>
start: () => Promise<YunlefunAuthorizationResult | null>
}
export interface YunlefunIdentityAdapter {
adopt: (authorization: SsoAuthorizationResult) => Promise<YunlefunAccount | null>
restore: () => Promise<YunlefunAccount | null>
signOut: () => Promise<void>
}
export interface YunlefunAuthController {
handleAuthorization: (authorization: YunlefunAuthorizationResult) => Promise<void>
initialize: () => Promise<void>
signIn: () => Promise<void>
signOut: () => Promise<void>
snapshot: () => Readonly<YunlefunAuthSnapshot>
}
interface YunlefunAuthControllerOptions {
identity: YunlefunIdentityAdapter
web: YunlefunAuthorizationAdapter
}
export function createYunlefunAuthController(
options: YunlefunAuthControllerOptions,
): YunlefunAuthController {
const authorization = options.web
let initialized = false
let state: YunlefunAuthSnapshot = {
account: null,
errorMessage: '',
status: 'idle',
}
async function initialize(): Promise<void> {
if (initialized)
return
initialized = true
update({ status: 'checking' })
try {
const result = await authorization.consumeInitial()
if (result) {
if (!result.ok) {
applyFailure(result.reason)
return
}
await adopt(result)
return
}
const account = await options.identity.restore()
update({
account,
status: account ? 'signed-in' : 'signed-out',
})
}
catch {
update({
account: null,
errorMessage: '登录状态检查失败,请稍后重试',
status: 'error',
})
}
}
async function signIn(): Promise<void> {
update({
errorMessage: '',
status: 'signing-in',
})
try {
const result = await authorization.start()
if (result)
await handleAuthorization(result)
}
catch {
update({
errorMessage: '无法打开登录授权,请稍后重试',
status: 'error',
})
}
}
async function handleAuthorization(result: YunlefunAuthorizationResult): Promise<void> {
if (!result.ok) {
applyFailure(result.reason)
return
}
try {
await adopt(result)
}
catch {
update({
account: null,
errorMessage: '登录身份验证失败,请重新授权',
status: 'error',
})
}
}
async function signOut(): Promise<void> {
try {
await options.identity.signOut()
update({
account: null,
errorMessage: '',
status: 'signed-out',
})
}
catch {
update({
errorMessage: '退出登录失败,请稍后重试',
status: 'error',
})
}
}
async function adopt(result: SsoAuthorizationResult): Promise<void> {
const account = await options.identity.adopt(result)
if (!account)
throw new Error('SSO identity is unavailable')
update({
account,
errorMessage: '',
status: 'signed-in',
})
}
function applyFailure(reason: Extract<SsoRedirectResult, { ok: false }>['reason']): void {
if (reason === 'not_authenticated') {
update({
account: null,
errorMessage: '',
status: 'signed-out',
})
return
}
const messages = {
access_denied: '登录授权已取消',
invalid_request: '登录请求无效,请刷新后重试',
server_error: '登录服务响应异常,请稍后重试',
} as const
update({
account: null,
errorMessage: messages[reason],
status: 'error',
})
}
function update(patch: Partial<YunlefunAuthSnapshot>): void {
state = { ...state, ...patch }
}
return {
handleAuthorization,
initialize,
signIn,
signOut,
snapshot: () => Object.freeze({ ...state }),
}
}