| 1 | import { ManagedRelay } from "@t3tools/client-runtime/relay"; |
| 2 | import { RelayMobileClientId } from "@t3tools/contracts/relay"; |
| 3 | import * as Cache from "effect/Cache"; |
| 4 | import * as Crypto from "effect/Crypto"; |
| 5 | import * as Duration from "effect/Duration"; |
| 6 | import * as Effect from "effect/Effect"; |
| 7 | import * as Exit from "effect/Exit"; |
| 8 | import * as Layer from "effect/Layer"; |
| 9 | |
| 10 | import { createDpopProof, loadOrCreateDpopProofKeyPair } from "./dpop"; |
| 11 | import { managedRelayAccessTokenStore } from "./managedRelayTokenStore"; |
| 12 | |
| 13 | const 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 | |
| 67 | export const layer = (relayUrl: string) => |
| 68 | ManagedRelay.layer({ |
| 69 | relayUrl, |
| 70 | clientId: RelayMobileClientId, |
| 71 | accessTokenStore: managedRelayAccessTokenStore, |
| 72 | }).pipe(Layer.provideMerge(layerRelayDpopSigner)); |