push增加详细消息
This commit is contained in:
parent
6a65d956a8
commit
3134aab4dc
@ -7,6 +7,14 @@ type PushTestPanelProps = {
|
|||||||
deviceCount: number;
|
deviceCount: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type PushFailureDetail = {
|
||||||
|
endpointHost?: string;
|
||||||
|
statusCode?: number | null;
|
||||||
|
message?: string;
|
||||||
|
body?: string | null;
|
||||||
|
reason?: string | null;
|
||||||
|
};
|
||||||
|
|
||||||
export function PushTestPanel({
|
export function PushTestPanel({
|
||||||
initialSubscriptionCount,
|
initialSubscriptionCount,
|
||||||
deviceCount,
|
deviceCount,
|
||||||
@ -16,10 +24,12 @@ export function PushTestPanel({
|
|||||||
initialSubscriptionCount,
|
initialSubscriptionCount,
|
||||||
);
|
);
|
||||||
const [notice, setNotice] = useState<string | null>(null);
|
const [notice, setNotice] = useState<string | null>(null);
|
||||||
|
const [failureDetails, setFailureDetails] = useState<PushFailureDetail[]>([]);
|
||||||
|
|
||||||
async function handleTestPush() {
|
async function handleTestPush() {
|
||||||
setSubmitting(true);
|
setSubmitting(true);
|
||||||
setNotice(null);
|
setNotice(null);
|
||||||
|
setFailureDetails([]);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch("/api/push/test", {
|
const response = await fetch("/api/push/test", {
|
||||||
@ -33,6 +43,7 @@ export function PushTestPanel({
|
|||||||
sentCount?: number;
|
sentCount?: number;
|
||||||
removedCount?: number;
|
removedCount?: number;
|
||||||
failedCount?: number;
|
failedCount?: number;
|
||||||
|
failureDetails?: PushFailureDetail[];
|
||||||
}
|
}
|
||||||
| null;
|
| null;
|
||||||
|
|
||||||
@ -44,8 +55,12 @@ export function PushTestPanel({
|
|||||||
const removedCount = payload?.removedCount || 0;
|
const removedCount = payload?.removedCount || 0;
|
||||||
const sentCount = payload?.sentCount || 0;
|
const sentCount = payload?.sentCount || 0;
|
||||||
const failedCount = payload?.failedCount || 0;
|
const failedCount = payload?.failedCount || 0;
|
||||||
|
const nextFailureDetails = Array.isArray(payload?.failureDetails)
|
||||||
|
? payload.failureDetails
|
||||||
|
: [];
|
||||||
|
|
||||||
setSubscriptionCount(Math.max(0, targetedCount - removedCount));
|
setSubscriptionCount(Math.max(0, targetedCount - removedCount));
|
||||||
|
setFailureDetails(nextFailureDetails);
|
||||||
|
|
||||||
if (targetedCount === 0) {
|
if (targetedCount === 0) {
|
||||||
setNotice("当前账号还没有任何开启消息提醒的设备,先在要接收通知的设备上打开消息提醒。\n");
|
setNotice("当前账号还没有任何开启消息提醒的设备,先在要接收通知的设备上打开消息提醒。\n");
|
||||||
@ -115,6 +130,28 @@ export function PushTestPanel({
|
|||||||
{notice}
|
{notice}
|
||||||
</p>
|
</p>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
|
{failureDetails.length > 0 ? (
|
||||||
|
<div className="mt-4 space-y-3">
|
||||||
|
{failureDetails.map((detail, index) => (
|
||||||
|
<div
|
||||||
|
key={`${detail.endpointHost || "unknown"}-${index}`}
|
||||||
|
className="rounded-[20px] bg-[var(--paper-soft)] px-4 py-4 text-sm leading-7 text-[var(--muted)]"
|
||||||
|
>
|
||||||
|
<p className="font-semibold text-[var(--ink)]">
|
||||||
|
{detail.endpointHost || "unknown"}
|
||||||
|
{typeof detail.statusCode === "number"
|
||||||
|
? ` · HTTP ${detail.statusCode}`
|
||||||
|
: ""}
|
||||||
|
</p>
|
||||||
|
<p className="mt-1">
|
||||||
|
{detail.reason || detail.message || "推送发送失败"}
|
||||||
|
</p>
|
||||||
|
{detail.body ? <p className="mt-1 break-all">{detail.body}</p> : null}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
</section>
|
</section>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
76
lib/push.ts
76
lib/push.ts
@ -27,6 +27,18 @@ type PushDeliverySummary = {
|
|||||||
failedCount: number;
|
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 = (
|
const APP_BASE_URL = (
|
||||||
process.env.NEXT_PUBLIC_APP_URL || "https://digital-human.xn--876a.net"
|
process.env.NEXT_PUBLIC_APP_URL || "https://digital-human.xn--876a.net"
|
||||||
).replace(/\/+$/, "");
|
).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: {
|
async function deliverPushToSubscriptions(input: {
|
||||||
subscriptions: StoredPushSubscription[];
|
subscriptions: StoredPushSubscription[];
|
||||||
payload: string;
|
payload: string;
|
||||||
topic: string;
|
topic: string;
|
||||||
|
collectFailureDetails?: boolean;
|
||||||
}) {
|
}) {
|
||||||
if (input.subscriptions.length === 0) {
|
if (input.subscriptions.length === 0) {
|
||||||
return {
|
return {
|
||||||
@ -144,11 +199,14 @@ async function deliverPushToSubscriptions(input: {
|
|||||||
sentCount: 0,
|
sentCount: 0,
|
||||||
removedCount: 0,
|
removedCount: 0,
|
||||||
failedCount: 0,
|
failedCount: 0,
|
||||||
} satisfies PushDeliverySummary;
|
failureDetails: [],
|
||||||
|
} satisfies PushDeliveryResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
ensureVapidDetails();
|
ensureVapidDetails();
|
||||||
|
|
||||||
|
const failureDetails: PushFailureDetail[] = [];
|
||||||
|
|
||||||
const results = await Promise.all(
|
const results = await Promise.all(
|
||||||
input.subscriptions.map(async (subscription) => {
|
input.subscriptions.map(async (subscription) => {
|
||||||
try {
|
try {
|
||||||
@ -171,10 +229,14 @@ async function deliverPushToSubscriptions(input: {
|
|||||||
|
|
||||||
return "sent" as const;
|
return "sent" as const;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const statusCode =
|
const detail = getPushFailureDetail(subscription, error);
|
||||||
error && typeof error === "object" && "statusCode" in error
|
const statusCode = detail.statusCode || 0;
|
||||||
? Number((error as { statusCode?: number }).statusCode)
|
|
||||||
: 0;
|
console.error("Push notification delivery failed", detail);
|
||||||
|
|
||||||
|
if (input.collectFailureDetails) {
|
||||||
|
failureDetails.push(detail);
|
||||||
|
}
|
||||||
|
|
||||||
if (statusCode === 404 || statusCode === 410) {
|
if (statusCode === 404 || statusCode === 410) {
|
||||||
await prisma.pushSubscription.deleteMany({
|
await prisma.pushSubscription.deleteMany({
|
||||||
@ -194,7 +256,8 @@ async function deliverPushToSubscriptions(input: {
|
|||||||
sentCount: results.filter((result) => result === "sent").length,
|
sentCount: results.filter((result) => result === "sent").length,
|
||||||
removedCount: results.filter((result) => result === "removed").length,
|
removedCount: results.filter((result) => result === "removed").length,
|
||||||
failedCount: results.filter((result) => result === "failed").length,
|
failedCount: results.filter((result) => result === "failed").length,
|
||||||
} satisfies PushDeliverySummary;
|
failureDetails,
|
||||||
|
} satisfies PushDeliveryResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function sendIncomingMessagePush(publicId: number) {
|
export async function sendIncomingMessagePush(publicId: number) {
|
||||||
@ -291,5 +354,6 @@ export async function sendCaregiverTestPush(input: {
|
|||||||
subscriptions,
|
subscriptions,
|
||||||
payload,
|
payload,
|
||||||
topic: `test-${input.caregiverId.slice(0, 20)}`,
|
topic: `test-${input.caregiverId.slice(0, 20)}`,
|
||||||
|
collectFailureDetails: true,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user