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", () => {
486486 Effect.provide(
487487 makeLayer({
488488 attempts,
489 currentTargets: [
490 { ...target, bundle_id: "com.t3tools.t3code.preview", aps_environment: "sandbox" },
491 ],
489492 config: signingConfig,
490493 execute,
491494 }),
......@@ -1972,3 +1975,76 @@ describe("fast completion delivery", () => {
19721975 }).pipe(Effect.provide(makeLayer({ attempts: [], queuedJobs })));
19731976 });
19741977});
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* () {
681681 "relay.delivery.kind": input.kind,
682682 ...(input.sourceJobId ? { "relay.delivery.job_id": input.sourceJobId } : {}),
683683 });
684 let deliveryTarget = input.target;
684685 const now = yield* DateTime.now;
685686 const aggregate =
686687 input.aggregate === null ? null : sanitizeAgentActivityAggregateState(input.aggregate);
......@@ -722,6 +723,7 @@ export const make = Effect.gen(function* () {
722723 });
723724 return staleJobResult({ deviceId: input.target.device_id, kind: input.kind });
724725 }
726 deliveryTarget = currentTarget;
725727 if (alert) {
726728 const preferences = parsePreferences(currentTarget.preferences_json);
727729 const previousAggregate = parseAggregate(currentTarget.last_aggregate_json);
......@@ -779,7 +781,7 @@ export const make = Effect.gen(function* () {
779781 );
780782 const result = yield* apns
781783 .sendLiveActivityRequest({
782 credentials: credentialsForTarget(config.apns, input.target),
784 credentials: credentialsForTarget(config.apns, deliveryTarget),
783785 request,
784786 issuedAtUnixSeconds: epochSeconds,
785787 })
......@@ -845,6 +847,7 @@ export const make = Effect.gen(function* () {
845847 "relay.delivery.kind": "push_notification",
846848 ...(input.sourceJobId ? { "relay.delivery.job_id": input.sourceJobId } : {}),
847849 });
850 let deliveryTarget = input.target;
848851 const now = yield* DateTime.now;
849852 const epochSeconds = Math.floor(now.epochMilliseconds / 1_000);
850853 const notification = sanitizeApnsNotificationPayload(input.notification);
......@@ -914,6 +917,7 @@ export const make = Effect.gen(function* () {
914917 kind: "push_notification",
915918 });
916919 }
920 deliveryTarget = currentTarget;
917921 const preferences = parsePreferences(currentTarget.preferences_json);
918922 const alertAllowed =
919923 notification.phase !== undefined && notification.updatedAt !== undefined
......@@ -935,7 +939,7 @@ export const make = Effect.gen(function* () {
935939 }
936940 const result = yield* apns
937941 .sendPushNotificationRequest({
938 credentials: credentialsForTarget(config.apns, input.target),
942 credentials: credentialsForTarget(config.apns, deliveryTarget),
939943 request,
940944 issuedAtUnixSeconds: epochSeconds,
941945 })