1import { ManagedRelay } from "@t3tools/client-runtime/relay";
2import { RelayMobileClientId } from "@t3tools/contracts/relay";
3import * as Cache from "effect/Cache";
4import * as Crypto from "effect/Crypto";
5import * as Duration from "effect/Duration";
6import * as Effect from "effect/Effect";
7import * as Exit from "effect/Exit";
8import * as Layer from "effect/Layer";
9
10import { createDpopProof, loadOrCreateDpopProofKeyPair } from "./dpop";
11import { managedRelayAccessTokenStore } from "./managedRelayTokenStore";
12
13const layerRelayDpopSigner = Layer.effect(
14 ManagedRelay.ManagedRelayDpopSigner,
15 Effect.gen(function* () {
16 const crypto = yield* Crypto.Crypto;
17 // Keeps the loaded key for the app's lifetime. A failed or interrupted
18 // load is not kept, so the next relay request loads it again.
19 const proofKeyCache = yield* Cache.makeWith(
20 () => loadOrCreateDpopProofKeyPair().pipe(Effect.provideService(Crypto.Crypto, crypto)),
21 {
22 capacity: 1,
23 timeToLive: (exit) => (Exit.isSuccess(exit) ? Duration.infinity : Duration.zero),
24 },
25 );
26 const loadProofKey = Cache.get(proofKeyCache, undefined);
27 return ManagedRelay.ManagedRelayDpopSigner.of({
28 thumbprint: loadProofKey.pipe(
29 Effect.map((proofKey) => proofKey.thumbprint),
30 Effect.mapError(
31 (error) =>
32 new ManagedRelay.ManagedRelayDpopKeyLoadError({
33 keyStore: "expo-secure-store",
34 cause: error,
35 }),
36 ),
37 Effect.withSpan("mobile.managedRelayDpopSigner.loadThumbprint"),
38 ),
39 createProof: Effect.fn("mobile.managedRelayDpopSigner.createProof")(function* (input) {
40 const proofKey = yield* loadProofKey.pipe(
41 Effect.mapError(
42 (error) =>
43 new ManagedRelay.ManagedRelayDpopProofCreationError({
44 method: input.method,
45 url: input.url,
46 cause: error,
47 }),
48 ),
49 );
50 return yield* createDpopProof({ ...input, proofKey }).pipe(
51 Effect.provideService(Crypto.Crypto, crypto),
52 Effect.map((proof) => proof.proof),
53 Effect.mapError(
54 (error) =>
55 new ManagedRelay.ManagedRelayDpopProofCreationError({
56 method: input.method,
57 url: input.url,
58 cause: error,
59 }),
60 ),
61 );
62 }),
63 });
64 }),
65);
66
67export const layer = (relayUrl: string) =>
68 ManagedRelay.layer({
69 relayUrl,
70 clientId: RelayMobileClientId,
71 accessTokenStore: managedRelayAccessTokenStore,
72 }).pipe(Layer.provideMerge(layerRelayDpopSigner));