Skip to content
Source
Sign inSign up
Open History to see the last commit

ApnsProviderTokens.ts

53 lines1.8 KB

1import * as Context from "effect/Context";
2import * as Effect from "effect/Effect";
3import * as Layer from "effect/Layer";
4​
5import {
6 apnsProviderTokenCacheKey,
7 makeApnsJwt,
8 type ApnsJwtError,
9 type ApnsJwtSigningInput,
10} from "./apnsJwt.ts";
11​
12// Reuse JWTs within their 60-minute validity; frequent refreshes trigger APNs TooManyProviderTokenUpdates.
13export const APNS_JWT_REUSE_SECONDS = 45 * 60;
14​
15export class ApnsProviderTokens extends Context.Service<
16 ApnsProviderTokens,
17 {
18 readonly getJwt: (input: ApnsJwtSigningInput) => Effect.Effect<string, ApnsJwtError>;
19 }
20>()("t3code-relay/agentActivity/ApnsProviderTokens") {}
21​
22interface CachedProviderToken {
23 readonly jwt: string;
24 readonly issuedAtUnixSeconds: number;
26​
27// Deterministic signing plus quantized iat gives isolates identical tokens; cache only to avoid repeated signing.
28const isolateTokenCache = new Map<string, CachedProviderToken>();
29​
30export function __resetApnsProviderTokenCacheForTest(): void {
31 isolateTokenCache.clear();
33​
34function quantizedApnsJwtIssuedAt(nowUnixSeconds: number): number {
35 return Math.floor(nowUnixSeconds / APNS_JWT_REUSE_SECONDS) * APNS_JWT_REUSE_SECONDS;
37​
38const make = () =>
39 ApnsProviderTokens.of({
40 getJwt: Effect.fnUntraced(function* (input) {
41 const issuedAtUnixSeconds = quantizedApnsJwtIssuedAt(input.issuedAtUnixSeconds);
42 const cacheKey = apnsProviderTokenCacheKey(input);
43 const cached = isolateTokenCache.get(cacheKey);
44 if (cached && cached.issuedAtUnixSeconds === issuedAtUnixSeconds) {
45 return cached.jwt;
46 }
47 const jwt = yield* makeApnsJwt({ ...input, issuedAtUnixSeconds });
48 isolateTokenCache.set(cacheKey, { jwt, issuedAtUnixSeconds });
49 return jwt;
50 }),
51 });
52​
53export const layer = Layer.succeed(ApnsProviderTokens, make());