push增加详细消息

This commit is contained in:
2026-04-17 14:44:27 +08:00
parent 6a65d956a8
commit 3134aab4dc
2 changed files with 107 additions and 6 deletions
+70 -6
View File
@@ -27,6 +27,18 @@ type PushDeliverySummary = {
failedCount: number;
};
type PushFailureDetail = {
endpointHost: string;
statusCode: number | null;
message: string;
body: string | null;
reason: string | null;
};
type PushDeliveryResult = PushDeliverySummary & {
failureDetails?: PushFailureDetail[];
};
const APP_BASE_URL = (
process.env.NEXT_PUBLIC_APP_URL || "https://digital-human.xn--876a.net"
).replace(/\/+$/, "");
@@ -133,10 +145,53 @@ export async function removePushSubscription(input: {
});
}
function getEndpointHost(endpoint: string) {
try {
return new URL(endpoint).host;
} catch {
return "unknown";
}
}
function getPushFailureDetail(subscription: StoredPushSubscription, error: unknown) {
const statusCode =
error && typeof error === "object" && "statusCode" in error
? Number((error as { statusCode?: number }).statusCode)
: null;
const message =
error && typeof error === "object" && "message" in error
? String((error as { message?: string }).message || "推送发送失败")
: "推送发送失败";
const body =
error && typeof error === "object" && "body" in error
? String((error as { body?: string }).body || "") || null
: null;
let reason: string | null = null;
if (body) {
try {
const parsedBody = JSON.parse(body) as { reason?: unknown };
reason = typeof parsedBody.reason === "string" ? parsedBody.reason : null;
} catch {
reason = null;
}
}
return {
endpointHost: getEndpointHost(subscription.endpoint),
statusCode,
message,
body,
reason,
} satisfies PushFailureDetail;
}
async function deliverPushToSubscriptions(input: {
subscriptions: StoredPushSubscription[];
payload: string;
topic: string;
collectFailureDetails?: boolean;
}) {
if (input.subscriptions.length === 0) {
return {
@@ -144,11 +199,14 @@ async function deliverPushToSubscriptions(input: {
sentCount: 0,
removedCount: 0,
failedCount: 0,
} satisfies PushDeliverySummary;
failureDetails: [],
} satisfies PushDeliveryResult;
}
ensureVapidDetails();
const failureDetails: PushFailureDetail[] = [];
const results = await Promise.all(
input.subscriptions.map(async (subscription) => {
try {
@@ -171,10 +229,14 @@ async function deliverPushToSubscriptions(input: {
return "sent" as const;
} catch (error) {
const statusCode =
error && typeof error === "object" && "statusCode" in error
? Number((error as { statusCode?: number }).statusCode)
: 0;
const detail = getPushFailureDetail(subscription, error);
const statusCode = detail.statusCode || 0;
console.error("Push notification delivery failed", detail);
if (input.collectFailureDetails) {
failureDetails.push(detail);
}
if (statusCode === 404 || statusCode === 410) {
await prisma.pushSubscription.deleteMany({
@@ -194,7 +256,8 @@ async function deliverPushToSubscriptions(input: {
sentCount: results.filter((result) => result === "sent").length,
removedCount: results.filter((result) => result === "removed").length,
failedCount: results.filter((result) => result === "failed").length,
} satisfies PushDeliverySummary;
failureDetails,
} satisfies PushDeliveryResult;
}
export async function sendIncomingMessagePush(publicId: number) {
@@ -291,5 +354,6 @@ export async function sendCaregiverTestPush(input: {
subscriptions,
payload,
topic: `test-${input.caregiverId.slice(0, 20)}`,
collectFailureDetails: true,
});
}