haixunMaster/lib/threads-api/config.ts

97 lines
2.8 KiB
TypeScript
Raw Permalink Normal View History

2026-06-21 12:50:31 +00:00
import type { Setting } from "@prisma/client";
export function normalizeThreadsAppId(value: string | null | undefined): string | undefined {
const trimmed = value?.trim();
if (!trimmed) return undefined;
if (!/^\d+$/.test(trimmed)) return undefined;
return trimmed;
}
/** Meta App 憑證僅從部署層 .env 讀取THREADS_APP_ID / THREADS_APP_SECRET。 */
export function getThreadsAppId(): string | undefined {
return normalizeThreadsAppId(process.env.THREADS_APP_ID);
}
export function getThreadsAppSecret(): string | undefined {
return process.env.THREADS_APP_SECRET?.trim() || undefined;
}
export function isThreadsAppConfigured(): boolean {
return !!(getThreadsAppId() && getThreadsAppSecret());
}
type ThreadsAccountAuth = {
threadsAccessToken: string | null;
threadsUserId: string | null;
};
export function accountHasThreadsToken(account: ThreadsAccountAuth | null): boolean {
return !!(account?.threadsAccessToken?.trim() && account?.threadsUserId?.trim());
}
export function getThreadsCredentials(
settings: Setting,
account: ThreadsAccountAuth | null,
overrideToken?: string | null
) {
const appId = getThreadsAppId();
const appSecret = getThreadsAppSecret();
const accessToken = (overrideToken ?? account?.threadsAccessToken)?.trim();
const userId = account?.threadsUserId?.trim();
if (!accessToken || !userId) return null;
return {
appId,
appSecret,
accessToken,
userId,
};
}
export function maskToken(token?: string | null): string | null {
if (!token) return null;
if (token.length <= 8) return "••••";
return `••••${token.slice(-6)}`;
}
export function normalizeAppUrl(value: string | null | undefined): string | undefined {
const trimmed = value?.trim();
if (!trimmed) return undefined;
try {
const url = new URL(trimmed);
if (!["http:", "https:"].includes(url.protocol)) return undefined;
return `${url.protocol}//${url.host}`.replace(/\/$/, "");
} catch {
return undefined;
}
}
export function getAppBaseUrl(
request?: Request,
appUrlOverride?: string | null
): string {
const fromSettings = normalizeAppUrl(appUrlOverride);
if (fromSettings) return fromSettings;
const fromEnv = normalizeAppUrl(process.env.APP_URL) || normalizeAppUrl(process.env.NEXT_PUBLIC_APP_URL);
if (fromEnv) return fromEnv;
if (request) {
const host = request.headers.get("x-forwarded-host") || request.headers.get("host");
const proto = request.headers.get("x-forwarded-proto") || "http";
if (host) return `${proto}://${host}`;
}
return "http://localhost:3000";
}
export function isPubliclyReachableUrl(url: string): boolean {
try {
const { hostname } = new URL(url);
return !["localhost", "127.0.0.1", "0.0.0.0"].includes(hostname);
} catch {
return false;
}
}