authorgravatar for julius0216@outlook.comJulius Marminge <julius0216@outlook.com> 2026-09-08 18:51:10-07:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-09-08 18:51:10-07:00
log1862686f9e42bf1d77e7f2ad03e24d7b0c7908f1
treeb738a7a63a9b28c55d87bdfe77c1fa17e2da41fb
parent3e6f856f2359421958a3aa046f2c393e00f3dc6a
signature Signed by PGP key B5690EEEBB952194

fix(relay): use current APNs registration routing for queued jobs (#10859)


2 files changed, 82 insertions(+), 2 deletions(-)

infra/relay/src/agentActivity/ApnsDeliveries.test.ts+76
...@@ -486,6 +486,9 @@ describe("ApnsDeliveries", () => {...@@ -486,6 +486,9 @@ describe("ApnsDeliveries", () => {
486 Effect.provide(486 Effect.provide(
487 makeLayer({487 makeLayer({
488 attempts,488 attempts,
489 currentTargets: [
490 { ...target, bundle_id: "com.t3tools.t3code.preview", aps_environment: "sandbox" },
491 ],
489 config: signingConfig,492 config: signingConfig,
490 execute,493 execute,
491 }),494 }),
...@@ -1972,3 +1975,76 @@ describe("fast completion delivery", () => {...@@ -1972,3 +1975,76 @@ describe("fast completion delivery", () => {
1972 }).pipe(Effect.provide(makeLayer({ attempts: [], queuedJobs })));1975 }).pipe(Effect.provide(makeLayer({ attempts: [], queuedJobs })));
1973 });1976 });
1974});1977});
1978
1979describe("signed APNs registration metadata", () => {
1980 for (const kind of ["live_activity_update", "push_notification"] as const) {
1981 for (const changed of ["bundle", "environment", "legacy"] as const) {
1982 it.effect(`routes ${kind} using current registration with ${changed} job metadata`, () => {
1983 const attempts: DeliveryAttempts.DeliveryAttemptInput[] = [];
1984 const requests: HttpClientRequest.HttpClientRequest[] = [];
1985 const payload = makeApnsDeliveryJobPayload({
1986 kind,
1987 userId: target.user_id,
1988 deviceId: target.device_id,
1989 token: "unchanged-token",
1990 ...(changed === "legacy"
1991 ? {}
1992 : { bundleId: "com.t3tools.t3code.dev", apsEnvironment: "sandbox" as const }),
1993 aggregate: kind === "live_activity_update" ? aggregate : null,
1994 ...(kind === "push_notification"
1995 ? {
1996 notification: {
1997 title: "Thread",
1998 body: "Input: Project",
1999 environmentId: "env",
2000 threadId: "thread",
2001 deepLink: "/",
2002 },
2003 }
2004 : {}),
2005 createdAt: "1970-01-01T00:00:00.000Z",
2006 expiresAt: "1970-01-01T00:10:00.000Z",
2007 jobId: `metadata-${kind}-${changed}`,
2008 });
2009 const signed = signApnsDeliveryJob({
2010 secret: config.apnsDeliveryJobSigningSecret,
2011 payload,
2012 });
2013 return Effect.gen(function* () {
2014 const deliveries = yield* ApnsDeliveries.ApnsDeliveries;
2015 const result = yield* deliveries.processSignedJob(signed);
2016 expect(result.ok).toBe(true);
2017 expect(requests).toHaveLength(1);
2018 expect(requests[0]?.url).toBe(
2019 `${changed === "environment" ? "https://api.push.apple.com" : "https://api.sandbox.push.apple.com"}/3/device/unchanged-token`,
2020 );
2021 expect(requests[0]?.headers["apns-topic"]).toBe(
2022 `${changed === "bundle" ? "com.t3tools.t3code.preview" : "com.t3tools.t3code.dev"}${kind === "live_activity_update" ? ".push-type.liveactivity" : ""}`,
2023 );
2024 }).pipe(
2025 Effect.provide(
2026 makeLayer({
2027 attempts,
2028 config: signingConfig,
2029 currentTargets: [
2030 {
2031 ...target,
2032 push_token: "unchanged-token",
2033 activity_push_token: "unchanged-token",
2034 bundle_id:
2035 changed === "bundle" ? "com.t3tools.t3code.preview" : "com.t3tools.t3code.dev",
2036 aps_environment: changed === "environment" ? "production" : "sandbox",
2037 },
2038 ],
2039 execute: (request) =>
2040 Effect.sync(() => {
2041 requests.push(request);
2042 return HttpClientResponse.fromWeb(request, new Response("", { status: 200 }));
2043 }),
2044 }),
2045 ),
2046 );
2047 });
2048 }
2049 }
2050});
infra/relay/src/agentActivity/ApnsDeliveries.ts+6-2
...@@ -681,6 +681,7 @@ export const make = Effect.gen(function* () {...@@ -681,6 +681,7 @@ export const make = Effect.gen(function* () {
681 "relay.delivery.kind": input.kind,681 "relay.delivery.kind": input.kind,
682 ...(input.sourceJobId ? { "relay.delivery.job_id": input.sourceJobId } : {}),682 ...(input.sourceJobId ? { "relay.delivery.job_id": input.sourceJobId } : {}),
683 });683 });
684 let deliveryTarget = input.target;
684 const now = yield* DateTime.now;685 const now = yield* DateTime.now;
685 const aggregate =686 const aggregate =
686 input.aggregate === null ? null : sanitizeAgentActivityAggregateState(input.aggregate);687 input.aggregate === null ? null : sanitizeAgentActivityAggregateState(input.aggregate);
...@@ -722,6 +723,7 @@ export const make = Effect.gen(function* () {...@@ -722,6 +723,7 @@ export const make = Effect.gen(function* () {
722 });723 });
723 return staleJobResult({ deviceId: input.target.device_id, kind: input.kind });724 return staleJobResult({ deviceId: input.target.device_id, kind: input.kind });
724 }725 }
726 deliveryTarget = currentTarget;
725 if (alert) {727 if (alert) {
726 const preferences = parsePreferences(currentTarget.preferences_json);728 const preferences = parsePreferences(currentTarget.preferences_json);
727 const previousAggregate = parseAggregate(currentTarget.last_aggregate_json);729 const previousAggregate = parseAggregate(currentTarget.last_aggregate_json);
...@@ -779,7 +781,7 @@ export const make = Effect.gen(function* () {...@@ -779,7 +781,7 @@ export const make = Effect.gen(function* () {
779 );781 );
780 const result = yield* apns782 const result = yield* apns
781 .sendLiveActivityRequest({783 .sendLiveActivityRequest({
782 credentials: credentialsForTarget(config.apns, input.target),784 credentials: credentialsForTarget(config.apns, deliveryTarget),
783 request,785 request,
784 issuedAtUnixSeconds: epochSeconds,786 issuedAtUnixSeconds: epochSeconds,
785 })787 })
...@@ -845,6 +847,7 @@ export const make = Effect.gen(function* () {...@@ -845,6 +847,7 @@ export const make = Effect.gen(function* () {
845 "relay.delivery.kind": "push_notification",847 "relay.delivery.kind": "push_notification",
846 ...(input.sourceJobId ? { "relay.delivery.job_id": input.sourceJobId } : {}),848 ...(input.sourceJobId ? { "relay.delivery.job_id": input.sourceJobId } : {}),
847 });849 });
850 let deliveryTarget = input.target;
848 const now = yield* DateTime.now;851 const now = yield* DateTime.now;
849 const epochSeconds = Math.floor(now.epochMilliseconds / 1_000);852 const epochSeconds = Math.floor(now.epochMilliseconds / 1_000);
850 const notification = sanitizeApnsNotificationPayload(input.notification);853 const notification = sanitizeApnsNotificationPayload(input.notification);
...@@ -914,6 +917,7 @@ export const make = Effect.gen(function* () {...@@ -914,6 +917,7 @@ export const make = Effect.gen(function* () {
914 kind: "push_notification",917 kind: "push_notification",
915 });918 });
916 }919 }
920 deliveryTarget = currentTarget;
917 const preferences = parsePreferences(currentTarget.preferences_json);921 const preferences = parsePreferences(currentTarget.preferences_json);
918 const alertAllowed =922 const alertAllowed =
919 notification.phase !== undefined && notification.updatedAt !== undefined923 notification.phase !== undefined && notification.updatedAt !== undefined
...@@ -935,7 +939,7 @@ export const make = Effect.gen(function* () {...@@ -935,7 +939,7 @@ export const make = Effect.gen(function* () {
935 }939 }
936 const result = yield* apns940 const result = yield* apns
937 .sendPushNotificationRequest({941 .sendPushNotificationRequest({
938 credentials: credentialsForTarget(config.apns, input.target),942 credentials: credentialsForTarget(config.apns, deliveryTarget),
939 request,943 request,
940 issuedAtUnixSeconds: epochSeconds,944 issuedAtUnixSeconds: epochSeconds,
941 })945 })