refactor: dedupe APNs push send flow and add wake default test

This commit is contained in:
Peter Steinberger
2026-02-19 13:45:34 +00:00
parent 722a898f20
commit 177654f526
2 changed files with 88 additions and 45 deletions

View File

@@ -190,4 +190,38 @@ describe("push APNs send semantics", () => {
expect(result.ok).toBe(true); expect(result.ok).toBe(true);
expect(result.environment).toBe("production"); expect(result.environment).toBe("production");
}); });
it("defaults background wake reason when not provided", async () => {
const send = vi.fn().mockResolvedValue({
status: 200,
apnsId: "apns-wake-default-reason-id",
body: "",
});
await sendApnsBackgroundWake({
auth: {
teamId: "TEAM123",
keyId: "KEY123",
privateKey: testAuthPrivateKey,
},
registration: {
nodeId: "ios-node-wake-default-reason",
token: "ABCD1234ABCD1234ABCD1234ABCD1234",
topic: "ai.openclaw.ios",
environment: "sandbox",
updatedAtMs: 1,
},
nodeId: "ios-node-wake-default-reason",
requestSender: send,
});
const sent = send.mock.calls[0]?.[0];
expect(sent?.payload).toMatchObject({
openclaw: {
kind: "node.wake",
reason: "node.invoke",
nodeId: "ios-node-wake-default-reason",
},
});
});
}); });

View File

@@ -425,6 +425,46 @@ function toApnsPushResult(params: {
}; };
} }
function createOpenClawPushMetadata(params: {
kind: "push.test" | "node.wake";
nodeId: string;
reason?: string;
}): { kind: "push.test" | "node.wake"; nodeId: string; ts: number; reason?: string } {
return {
kind: params.kind,
nodeId: params.nodeId,
ts: Date.now(),
...(params.reason ? { reason: params.reason } : {}),
};
}
async function sendApnsPush(params: {
auth: ApnsAuthConfig;
registration: ApnsRegistration;
payload: object;
timeoutMs?: number;
requestSender?: ApnsRequestSender;
pushType: ApnsPushType;
priority: "10" | "5";
}): Promise<ApnsPushWakeResult> {
const { token, topic, environment, bearerToken } = resolveApnsSendContext({
auth: params.auth,
registration: params.registration,
});
const sender = params.requestSender ?? sendApnsRequest;
const response = await sender({
token,
topic,
environment,
bearerToken,
payload: params.payload,
timeoutMs: resolveApnsTimeoutMs(params.timeoutMs),
pushType: params.pushType,
priority: params.priority,
});
return toApnsPushResult({ response, token, topic, environment });
}
export async function sendApnsAlert(params: { export async function sendApnsAlert(params: {
auth: ApnsAuthConfig; auth: ApnsAuthConfig;
registration: ApnsRegistration; registration: ApnsRegistration;
@@ -434,11 +474,6 @@ export async function sendApnsAlert(params: {
timeoutMs?: number; timeoutMs?: number;
requestSender?: ApnsRequestSender; requestSender?: ApnsRequestSender;
}): Promise<ApnsPushAlertResult> { }): Promise<ApnsPushAlertResult> {
const { token, topic, environment, bearerToken } = resolveApnsSendContext({
auth: params.auth,
registration: params.registration,
});
const payload = { const payload = {
aps: { aps: {
alert: { alert: {
@@ -447,31 +482,21 @@ export async function sendApnsAlert(params: {
}, },
sound: "default", sound: "default",
}, },
openclaw: { openclaw: createOpenClawPushMetadata({
kind: "push.test", kind: "push.test",
nodeId: params.nodeId, nodeId: params.nodeId,
ts: Date.now(), }),
},
}; };
const sender = params.requestSender ?? sendApnsRequest; return await sendApnsPush({
const response = await sender({ auth: params.auth,
token, registration: params.registration,
topic,
environment,
bearerToken,
payload, payload,
timeoutMs: resolveApnsTimeoutMs(params.timeoutMs), timeoutMs: params.timeoutMs,
requestSender: params.requestSender,
pushType: "alert", pushType: "alert",
priority: "10", priority: "10",
}); });
return toApnsPushResult({
response,
token,
topic,
environment,
});
} }
export async function sendApnsBackgroundWake(params: { export async function sendApnsBackgroundWake(params: {
@@ -482,39 +507,23 @@ export async function sendApnsBackgroundWake(params: {
timeoutMs?: number; timeoutMs?: number;
requestSender?: ApnsRequestSender; requestSender?: ApnsRequestSender;
}): Promise<ApnsPushWakeResult> { }): Promise<ApnsPushWakeResult> {
const { token, topic, environment, bearerToken } = resolveApnsSendContext({
auth: params.auth,
registration: params.registration,
});
const payload = { const payload = {
aps: { aps: {
"content-available": 1, "content-available": 1,
}, },
openclaw: { openclaw: createOpenClawPushMetadata({
kind: "node.wake", kind: "node.wake",
reason: params.wakeReason ?? "node.invoke", reason: params.wakeReason ?? "node.invoke",
nodeId: params.nodeId, nodeId: params.nodeId,
ts: Date.now(), }),
},
}; };
return await sendApnsPush({
const sender = params.requestSender ?? sendApnsRequest; auth: params.auth,
const response = await sender({ registration: params.registration,
token,
topic,
environment,
bearerToken,
payload, payload,
timeoutMs: resolveApnsTimeoutMs(params.timeoutMs), timeoutMs: params.timeoutMs,
requestSender: params.requestSender,
pushType: "background", pushType: "background",
priority: "5", priority: "5",
}); });
return toApnsPushResult({
response,
token,
topic,
environment,
});
} }