refactor(auth): migrate social login to next-auth
This commit is contained in:
Vendored
-39
@@ -1,39 +0,0 @@
|
||||
/**
|
||||
* Facebook SDK 全局类型声明
|
||||
*/
|
||||
export {};
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
FB?: {
|
||||
init(params: {
|
||||
appId: string;
|
||||
cookie?: boolean;
|
||||
xfbml?: boolean;
|
||||
version: string;
|
||||
}): void;
|
||||
login(
|
||||
callback: (response: {
|
||||
authResponse?: {
|
||||
accessToken: string;
|
||||
userID: string;
|
||||
expiresIn: number;
|
||||
};
|
||||
status?: "connected" | "not_authorized" | "unknown";
|
||||
}) => void,
|
||||
options?: { scope: string },
|
||||
): void;
|
||||
logout(callback?: () => void): void;
|
||||
api(
|
||||
path: string,
|
||||
callback: (response: {
|
||||
id?: string;
|
||||
name?: string;
|
||||
email?: string;
|
||||
picture?: { data?: { url?: string } };
|
||||
}) => void,
|
||||
): void;
|
||||
};
|
||||
fbAsyncInit?: () => void;
|
||||
}
|
||||
}
|
||||
@@ -1,147 +0,0 @@
|
||||
/**
|
||||
* Facebook SDK JS 封装
|
||||
*
|
||||
* 原始 Dart: `flutter_facebook_auth` 包
|
||||
* (`lib/data/services/auth/auth_platform.dart` + `auth_screen.dart` 中 Facebook 登录入口)。
|
||||
*
|
||||
* 浏览器方案:
|
||||
* 1. 在 `app/layout.tsx` 通过 `<Script src="https://connect.facebook.net/en_US/sdk.js">` 注入。
|
||||
* 2. 调用 `FB.login` / `FB.getUserData` 完成 OAuth + 用户信息获取。
|
||||
* 3. TypeScript 类型声明在 `facebook-sdk.d.ts`(`window.FB`)。
|
||||
*/
|
||||
import { Result } from "@/utils/result";
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
FB?: {
|
||||
init(params: {
|
||||
appId: string;
|
||||
cookie?: boolean;
|
||||
xfbml?: boolean;
|
||||
version: string;
|
||||
}): void;
|
||||
login(
|
||||
callback: (response: {
|
||||
authResponse?: {
|
||||
accessToken: string;
|
||||
userID: string;
|
||||
expiresIn: number;
|
||||
};
|
||||
status?: "connected" | "not_authorized" | "unknown";
|
||||
}) => void,
|
||||
options?: { scope: string },
|
||||
): void;
|
||||
logout(callback?: () => void): void;
|
||||
api(
|
||||
path: string,
|
||||
callback: (response: {
|
||||
id?: string;
|
||||
name?: string;
|
||||
email?: string;
|
||||
picture?: { data?: { url?: string } };
|
||||
}) => void,
|
||||
): void;
|
||||
};
|
||||
fbAsyncInit?: () => void;
|
||||
}
|
||||
}
|
||||
|
||||
export interface FacebookUserData {
|
||||
id: string;
|
||||
name?: string;
|
||||
email?: string;
|
||||
pictureUrl?: string;
|
||||
}
|
||||
|
||||
const FB_APP_ID = process.env.NEXT_PUBLIC_FACEBOOK_APP_ID ?? "";
|
||||
const FB_VERSION = process.env.NEXT_PUBLIC_FACEBOOK_API_VERSION ?? "v18.0";
|
||||
|
||||
/**
|
||||
* 初始化 FB SDK(在 `window.fbAsyncInit` 触发时调用一次)。
|
||||
* 由 `app/layout.tsx` 的 inline script 触发。
|
||||
*/
|
||||
export function initFacebookSdk(): void {
|
||||
if (typeof window === "undefined" || !window.FB) return;
|
||||
window.FB.init({
|
||||
appId: FB_APP_ID,
|
||||
cookie: true,
|
||||
xfbml: false,
|
||||
version: FB_VERSION,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 触发 Facebook OAuth 登录。
|
||||
* - 成功返回 `{ accessToken, userId }`
|
||||
* - 取消 / 失败返回 `Result.err`
|
||||
*/
|
||||
export async function loginWithFacebook(): Promise<
|
||||
Result<{ accessToken: string; userId: string }>
|
||||
> {
|
||||
if (typeof window === "undefined" || !window.FB) {
|
||||
return Result.err(new Error("Facebook SDK not loaded"));
|
||||
}
|
||||
|
||||
return new Promise((resolve) => {
|
||||
window.FB!.login(
|
||||
(response) => {
|
||||
if (!response.authResponse) {
|
||||
resolve(
|
||||
Result.err(new Error("Facebook login cancelled or failed")),
|
||||
);
|
||||
return;
|
||||
}
|
||||
resolve(
|
||||
Result.ok({
|
||||
accessToken: response.authResponse.accessToken,
|
||||
userId: response.authResponse.userID,
|
||||
}),
|
||||
);
|
||||
},
|
||||
{ scope: "email,public_profile" },
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 拉取 Facebook 用户信息(头像、姓名等)。
|
||||
*/
|
||||
export async function getFacebookUserData(): Promise<
|
||||
Result<FacebookUserData>
|
||||
> {
|
||||
if (typeof window === "undefined" || !window.FB) {
|
||||
return Result.err(new Error("Facebook SDK not loaded"));
|
||||
}
|
||||
return new Promise((resolve) => {
|
||||
window.FB!.api("/me", (response) => {
|
||||
if (!response.id) {
|
||||
resolve(Result.err(new Error("No Facebook user data")));
|
||||
return;
|
||||
}
|
||||
// 单独拉取头像
|
||||
window.FB!.api(
|
||||
"/me/picture?type=large&redirect=false",
|
||||
(picResp: unknown) => {
|
||||
const url =
|
||||
picResp &&
|
||||
typeof picResp === "object" &&
|
||||
"data" in picResp &&
|
||||
picResp.data &&
|
||||
typeof picResp.data === "object" &&
|
||||
"url" in picResp.data &&
|
||||
typeof picResp.data.url === "string"
|
||||
? picResp.data.url
|
||||
: undefined;
|
||||
resolve(
|
||||
Result.ok({
|
||||
id: response.id!,
|
||||
name: response.name!,
|
||||
email: response.email!,
|
||||
pictureUrl: url,
|
||||
}),
|
||||
);
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
-36
@@ -1,36 +0,0 @@
|
||||
/**
|
||||
* Google Identity Services 全局类型声明
|
||||
*/
|
||||
export {};
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
google?: {
|
||||
accounts: {
|
||||
id: {
|
||||
initialize(params: {
|
||||
client_id: string;
|
||||
callback: (response: { credential?: string; select_by?: string }) => void;
|
||||
auto_select?: boolean;
|
||||
cancel_on_tap_outside?: boolean;
|
||||
}): void;
|
||||
prompt(): void;
|
||||
renderButton(
|
||||
parent: HTMLElement,
|
||||
options: {
|
||||
type?: "standard" | "icon";
|
||||
theme?: "outline" | "filled_blue" | "filled_black";
|
||||
size?: "large" | "medium" | "small";
|
||||
text?: "signin_with" | "signup_with" | "continue_with" | "signin";
|
||||
shape?: "rectangular" | "pill" | "circle" | "square";
|
||||
logo_alignment?: "left" | "center";
|
||||
width?: number;
|
||||
locale?: string;
|
||||
},
|
||||
): void;
|
||||
disableAutoSelect(): void;
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,72 +0,0 @@
|
||||
/**
|
||||
* Google Identity Services (GIS) 封装
|
||||
*
|
||||
* 原始 Dart: `google_sign_in_web` 包
|
||||
* (`lib/ui/auth/widgets/google_sign_in_web.dart` + `auth_bloc._onWebGoogleLoginSuccess`)。
|
||||
*
|
||||
* 浏览器方案:
|
||||
* 1. `<Script src="https://accounts.google.com/gsi/client">` 在 layout 注入。
|
||||
* 2. `renderButton(element, options)` 渲染 GIS 按钮(自动处理 OAuth 流程)。
|
||||
* 3. 登录成功 callback 回调 `onGoogleSignInSuccess(idToken, email)`。
|
||||
* 4. 把 idToken 传给 `authRepository.googleLogin`。
|
||||
*
|
||||
* 全局类型:`google.accounts.id` 在 `google-identity.d.ts` 中声明。
|
||||
*/
|
||||
import { Result } from "@/utils/result";
|
||||
|
||||
export interface GoogleSignInSuccess {
|
||||
idToken: string;
|
||||
email: string;
|
||||
}
|
||||
|
||||
let _onSuccess: ((s: GoogleSignInSuccess) => void) | null = null;
|
||||
|
||||
/** 设置全局成功回调(auth screen 在 mount 时设置,unmount 时清空)。 */
|
||||
export function setOnGoogleSignInSuccess(
|
||||
cb: ((s: GoogleSignInSuccess) => void) | null,
|
||||
): void {
|
||||
_onSuccess = cb;
|
||||
}
|
||||
|
||||
/** 渲染 GIS 按钮到指定 DOM 元素。 */
|
||||
export function renderGoogleButton(
|
||||
element: HTMLElement,
|
||||
options: { theme?: "outline" | "filled_blue" | "filled_black"; size?: "large" | "medium" | "small"; text?: "signin_with" | "signup_with" | "continue_with" | "signin" } = {},
|
||||
): void {
|
||||
if (typeof window === "undefined" || !window.google?.accounts?.id) return;
|
||||
window.google.accounts.id.renderButton(element, {
|
||||
type: "standard",
|
||||
theme: options.theme ?? "outline",
|
||||
size: options.size ?? "large",
|
||||
text: options.text ?? "continue_with",
|
||||
shape: "pill",
|
||||
width: element.clientWidth || 320,
|
||||
});
|
||||
}
|
||||
|
||||
/** 触发 GIS One Tap 提示(如果用户已登录过 Google)。 */
|
||||
export function promptGoogleOneTap(): Result<true> {
|
||||
if (typeof window === "undefined" || !window.google?.accounts?.id) {
|
||||
return Result.err(new Error("Google Identity Services not loaded"));
|
||||
}
|
||||
window.google.accounts.id.prompt();
|
||||
return Result.ok(true);
|
||||
}
|
||||
|
||||
/** 处理 GIS 凭据回调(由 GIS 内部调用)。 */
|
||||
export function handleGoogleCredentialResponse(
|
||||
response: { credential?: string; select_by?: string },
|
||||
): Result<GoogleSignInSuccess> {
|
||||
if (!response.credential) {
|
||||
return Result.err(new Error("No Google credential returned"));
|
||||
}
|
||||
// GIS 凭据是 JWT;本仓库不解析(idToken 直接交给后端校验),
|
||||
// email 字段需前端手动解码或由后端从 idToken 提取。
|
||||
// 此处仅返回 idToken,email 留空(后端会从 idToken 还原)。
|
||||
const success: GoogleSignInSuccess = {
|
||||
idToken: response.credential,
|
||||
email: "",
|
||||
};
|
||||
_onSuccess?.(success);
|
||||
return Result.ok(success);
|
||||
}
|
||||
@@ -4,9 +4,5 @@
|
||||
|
||||
export * from "./browser-detect";
|
||||
export * from "./chat-websocket";
|
||||
export * from "./facebook-sdk";
|
||||
export * from "./facebook-sdk";
|
||||
export * from "./google-identity";
|
||||
export * from "./google-identity";
|
||||
export * from "./media-recorder";
|
||||
export * from "./message-queue";
|
||||
|
||||
Reference in New Issue
Block a user