100 lines
2.6 KiB
TypeScript
100 lines
2.6 KiB
TypeScript
import { env } from "../../config/env.js";
|
||
import { logger } from "../observability/logger.js";
|
||
|
||
/**
|
||
* Push Gateway HTTP 客户端。
|
||
*
|
||
* 仲裁依据:
|
||
* - M4:HTTP POST /internal/push(豁免 gRPC)
|
||
* - PushGateway 软失败:不可用时返回 false,不阻断主流程
|
||
* - 鉴权头:X-Internal-Key = PUSH_INTERNAL_TOKEN
|
||
*
|
||
* 请求体对齐 push-gateway handler.go PushHandler:
|
||
* { userId, event, data }
|
||
*/
|
||
|
||
export interface PushRequest {
|
||
userId: string;
|
||
event: string;
|
||
data: Record<string, unknown>;
|
||
}
|
||
|
||
export interface PushResult {
|
||
sent: boolean;
|
||
error?: string;
|
||
}
|
||
|
||
/**
|
||
* 向 push-gateway 发送实时推送。
|
||
*
|
||
* 软失败语义:
|
||
* - PUSH_GATEWAY_URL 未配置 → 返回 { sent: false },不报错
|
||
* - 网络错误/非 2xx → 返回 { sent: false, error },logger.warn
|
||
* - 成功 → 返回 { sent: true }
|
||
*/
|
||
export async function sendPush(req: PushRequest): Promise<PushResult> {
|
||
if (!env.PUSH_GATEWAY_URL) {
|
||
return { sent: false };
|
||
}
|
||
|
||
const url = `${env.PUSH_GATEWAY_URL}/internal/push`;
|
||
const headers: Record<string, string> = {
|
||
"Content-Type": "application/json",
|
||
};
|
||
if (env.PUSH_INTERNAL_TOKEN) {
|
||
headers["X-Internal-Key"] = env.PUSH_INTERNAL_TOKEN;
|
||
}
|
||
|
||
try {
|
||
const res = await fetch(url, {
|
||
method: "POST",
|
||
headers,
|
||
body: JSON.stringify({
|
||
userId: req.userId,
|
||
event: req.event,
|
||
data: req.data,
|
||
}),
|
||
});
|
||
if (!res.ok) {
|
||
const error = `push-gateway returned ${res.status}`;
|
||
logger.warn({ status: res.status, userId: req.userId }, error);
|
||
return { sent: false, error };
|
||
}
|
||
return { sent: true };
|
||
} catch (err) {
|
||
const error = err instanceof Error ? err.message : String(err);
|
||
logger.warn({ err, userId: req.userId }, "Push gateway unavailable");
|
||
return { sent: false, error };
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 批量推送(逐条发送,push-gateway 无批量 API)。
|
||
* 任一失败不影响其他,返回每条结果。
|
||
*/
|
||
export async function sendPushBatch(
|
||
requests: PushRequest[],
|
||
): Promise<PushResult[]> {
|
||
const results: PushResult[] = [];
|
||
for (const req of requests) {
|
||
results.push(await sendPush(req));
|
||
}
|
||
return results;
|
||
}
|
||
|
||
/**
|
||
* 健康检查:探测 push-gateway /readyz。
|
||
* 软失败:不可用返回 false,不阻断 /readyz(仲裁 M2)。
|
||
*/
|
||
export async function checkPushGateway(): Promise<boolean> {
|
||
if (!env.PUSH_GATEWAY_URL) return false;
|
||
try {
|
||
const res = await fetch(`${env.PUSH_GATEWAY_URL}/readyz`, {
|
||
signal: AbortSignal.timeout(2000),
|
||
});
|
||
return res.ok;
|
||
} catch {
|
||
return false;
|
||
}
|
||
}
|