63 lines · 2.4 KB · TypeScript
RawDownload
import * as Context from "effect/Context";import * as Effect from "effect/Effect";import * as Layer from "effect/Layer"; import {  apnsProviderTokenCacheKey,  makeApnsJwt,  type ApnsJwtError,  type ApnsJwtSigningInput,} from "./apnsJwt.ts"; // APNs requires REUSING the provider token: refreshing it more than roughly// once per 20 minutes returns 429 TooManyProviderTokenUpdates and drops the// push (observed live: bursty Live Activity updates got 429'd, leaving stale// lock-screen state). Reuse each signed JWT for most of its 60-minute// validity.export const APNS_JWT_REUSE_SECONDS = 45 * 60; export class ApnsProviderTokens extends Context.Service<  ApnsProviderTokens,  {    readonly getJwt: (input: ApnsJwtSigningInput) => Effect.Effect<string, ApnsJwtError>;  }>()("t3code-relay/agentActivity/ApnsProviderTokens") {} interface CachedProviderToken {  readonly jwt: string;  readonly issuedAtUnixSeconds: number;} // Signing is deterministic (RFC 6979) and iat is quantized below, so every// isolate independently derives the byte-identical token for a window; this// map only avoids re-signing on every push. No shared storage is needed, and// no provider token is ever written anywhere.const isolateTokenCache = new Map<string, CachedProviderToken>(); export function __resetApnsProviderTokenCacheForTest(): void {  isolateTokenCache.clear();} // Quantize iat to the reuse window so all isolates agree on it. The token's// age stays under APNs' 60-minute limit, and the whole fleet rolls to the// next token at the same instant — one provider-token update per window.export function quantizedApnsJwtIssuedAt(nowUnixSeconds: number): number {  return Math.floor(nowUnixSeconds / APNS_JWT_REUSE_SECONDS) * APNS_JWT_REUSE_SECONDS;} export const make = () =>  ApnsProviderTokens.of({    getJwt: Effect.fnUntraced(function* (input) {      const issuedAtUnixSeconds = quantizedApnsJwtIssuedAt(input.issuedAtUnixSeconds);      const cacheKey = apnsProviderTokenCacheKey(input);      const cached = isolateTokenCache.get(cacheKey);      if (cached && cached.issuedAtUnixSeconds === issuedAtUnixSeconds) {        return cached.jwt;      }      const jwt = yield* makeApnsJwt({ ...input, issuedAtUnixSeconds });      isolateTokenCache.set(cacheKey, { jwt, issuedAtUnixSeconds });      return jwt;    }),  }); export const layer = Layer.succeed(ApnsProviderTokens, make());