chore: wire release hook and service updates
This commit is contained in:
@@ -0,0 +1,99 @@
|
||||
/**
|
||||
* Facebook Graph API 工具
|
||||
*
|
||||
* 在 OAuth callback 之后、auth state machine 的 `syncingFacebookBackend` actor
|
||||
* 成功换取业务 token 之后调用 —— 用 accessToken 调 `/me` 拉用户资料(id / name
|
||||
* / email / picture),把 pictureUrl 写入 `UserStorage.setAvatarUrl()`、
|
||||
* id 写入 `AuthStorage.setFacebookId()`。
|
||||
*
|
||||
* 端点:`https://graph.facebook.com/me?fields=id,name,email,picture.type(large)&access_token=...`
|
||||
* - `picture.type(large)` 返回 ~200px 头像 URL(默认 square 50px 太小)
|
||||
* - 不指定 version 走最新稳定版
|
||||
* - 不需要 appId / appSecret(accessToken 自身就是凭证)
|
||||
*/
|
||||
import { FacebookUserData } from "@/data/dto/auth";
|
||||
import { Logger, Result, toError } from "@/utils";
|
||||
|
||||
const log = new Logger("FacebookGraph");
|
||||
|
||||
/** Facebook Graph `/me` 端点。 */
|
||||
const GRAPH_ME_URL = "https://graph.facebook.com/me";
|
||||
|
||||
/**
|
||||
* 拉取字段集合:
|
||||
* - id / name / email —— 给本地 FacebookUserData DTO
|
||||
* - picture.type(large) —— 头像 URL(默认 square 50px 太小)
|
||||
*/
|
||||
const FIELDS = "id,name,email,picture.type(large)";
|
||||
|
||||
/**
|
||||
* 用 accessToken 调 Facebook Graph `/me` 拉用户资料。
|
||||
*
|
||||
* 错误处理:返回 `Result<FacebookUserData>` 而非 throw —— 调用方在
|
||||
* auth state machine actor 内可走「best-effort」路径,失败只 warn 不
|
||||
* 让 Facebook 登录整条流挂掉(后端业务 token 已经换到,头像只是锦上添花)。
|
||||
*/
|
||||
export async function fetchFacebookUserData(
|
||||
accessToken: string,
|
||||
): Promise<Result<FacebookUserData>> {
|
||||
if (!accessToken) {
|
||||
return Result.err(new Error("fetchFacebookUserData: accessToken is empty"));
|
||||
}
|
||||
|
||||
const url = `${GRAPH_ME_URL}?fields=${encodeURIComponent(FIELDS)}&access_token=${encodeURIComponent(accessToken)}`;
|
||||
|
||||
log.debug({ url: url.replace(accessToken, "<redacted>") }, "fetchFacebookUserData REQUEST");
|
||||
|
||||
try {
|
||||
const res = await fetch(url, { method: "GET" });
|
||||
const json = (await res.json()) as Record<string, unknown>;
|
||||
|
||||
if (!res.ok) {
|
||||
const errBody = (json?.error as Record<string, unknown> | undefined) ?? {};
|
||||
const message =
|
||||
typeof errBody.message === "string"
|
||||
? errBody.message
|
||||
: `Graph /me failed: HTTP ${res.status}`;
|
||||
log.warn({ status: res.status, body: errBody }, "fetchFacebookUserData FAILED");
|
||||
return Result.err(new Error(message));
|
||||
}
|
||||
|
||||
// 提取 picture.data.url —— Graph API 返回嵌套结构
|
||||
const pictureUrl = extractPictureUrl(json.picture);
|
||||
|
||||
const data = FacebookUserData.from({
|
||||
id: typeof json.id === "string" ? json.id : null,
|
||||
name: typeof json.name === "string" ? json.name : null,
|
||||
email: typeof json.email === "string" ? json.email : null,
|
||||
pictureUrl,
|
||||
});
|
||||
|
||||
log.debug(
|
||||
{
|
||||
hasId: !!data.id,
|
||||
hasName: !!data.name,
|
||||
hasEmail: !!data.email,
|
||||
hasPicture: !!data.pictureUrl,
|
||||
},
|
||||
"fetchFacebookUserData SUCCESS",
|
||||
);
|
||||
return Result.ok(data);
|
||||
} catch (e) {
|
||||
const err = toError(e);
|
||||
log.error({ err }, "fetchFacebookUserData THREW");
|
||||
return Result.err(err);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 安全提取 `picture.data.url` —— 容错处理:
|
||||
* - 缺字段 / 类型不对 → null(头像可选)
|
||||
* - Graph API 偶尔返回 `picture: false`(没设头像)→ null
|
||||
*/
|
||||
function extractPictureUrl(raw: unknown): string | null {
|
||||
if (!raw || typeof raw !== "object") return null;
|
||||
const data = (raw as { data?: unknown }).data;
|
||||
if (!data || typeof data !== "object") return null;
|
||||
const url = (data as { url?: unknown }).url;
|
||||
return typeof url === "string" && url.length > 0 ? url : null;
|
||||
}
|
||||
@@ -10,6 +10,7 @@ export * from "./api/http_client";
|
||||
export * from "./api/metrics_api";
|
||||
export * from "./api/response_helper";
|
||||
export * from "./api/user_api";
|
||||
export * from "./facebook-graph";
|
||||
export * from "../../core/net/config/api_config";
|
||||
export * from "../../core/net/interceptor/auth_refresh_interceptor";
|
||||
export * from "../../core/net/interceptor/logging_interceptor";
|
||||
|
||||
Reference in New Issue
Block a user