Open History to see the last commit
1// @effect-diagnostics-next-line nodeBuiltinImport:off -- Effect's Crypto has no generateKeyPairSync.
2import * as NodeCrypto from "node:crypto";
3
4import { describe, expect, it } from "@effect/vitest";
5import * as Effect from "effect/Effect";
6import * as Redacted from "effect/Redacted";
7import * as Schema from "effect/Schema";
8
9import * as ApnsProviderTokens from "./ApnsProviderTokens.ts";
10
11const { privateKey } = NodeCrypto.generateKeyPairSync("ec", {
12 namedCurve: "prime256v1",
13 privateKeyEncoding: { type: "pkcs8", format: "pem" },
14 publicKeyEncoding: { type: "spki", format: "pem" },
15});
16
17const signingInput = {
18 teamId: "team-1",
19 keyId: "key-1",
20 privateKey: Redacted.make(privateKey),
21};
22
23const WINDOW = ApnsProviderTokens.APNS_JWT_REUSE_SECONDS;
24
25const decodeJwtPayload = Schema.decodeEffect(
26 Schema.fromJsonString(Schema.Struct({ iat: Schema.Number })),
27);
28
29describe("ApnsProviderTokens", () => {
30 it.effect("derives the byte-identical token across isolates within a window", () => {
31 ApnsProviderTokens.__resetApnsProviderTokenCacheForTest();
32 return Effect.gen(function* () {
33 const tokens = yield* ApnsProviderTokens.ApnsProviderTokens;
34 const first = yield* tokens.getJwt({ ...signingInput, issuedAtUnixSeconds: WINDOW + 10 });
35
36 ApnsProviderTokens.__resetApnsProviderTokenCacheForTest();
37 const second = yield* tokens.getJwt({
38 ...signingInput,
39 issuedAtUnixSeconds: WINDOW * 2 - 1,
40 });
41 expect(second).toBe(first);
42
43 const payload = yield* decodeJwtPayload(
44 Buffer.from(first.split(".")[1]!, "base64url").toString("utf8"),
45 );
46 expect(payload.iat).toBe(WINDOW);
47 ApnsProviderTokens.__resetApnsProviderTokenCacheForTest();
48 }).pipe(Effect.provide(ApnsProviderTokens.layer));
49 });
50
51 it.effect("rolls to a new token at the window boundary", () => {
52 ApnsProviderTokens.__resetApnsProviderTokenCacheForTest();
53 return Effect.gen(function* () {
54 const tokens = yield* ApnsProviderTokens.ApnsProviderTokens;
55 const first = yield* tokens.getJwt({ ...signingInput, issuedAtUnixSeconds: WINDOW + 10 });
56 const next = yield* tokens.getJwt({ ...signingInput, issuedAtUnixSeconds: WINDOW * 2 });
57 expect(next).not.toBe(first);
58 ApnsProviderTokens.__resetApnsProviderTokenCacheForTest();
59 }).pipe(Effect.provide(ApnsProviderTokens.layer));
60 });
61});