1/**
2 * Browser-backed implementation of Effect's Crypto service.
3 *
4 * This module provides a `Crypto.Crypto` layer backed by the Web Crypto API.
5 * The {@link WebCrypto} context reference defaults to `globalThis.crypto`, so
6 * browser programs can use the standard implementation while tests or embedded
7 * runtimes can provide their own `Crypto` object.
8 *
9 * @since 1.0.0
10 */
11import * as Context from "effect/Context"
12import * as EffectCrypto from "effect/Crypto"
13import * as Effect from "effect/Effect"
14import * as Layer from "effect/Layer"
15import * as PlatformError from "effect/PlatformError"
16
17/**
18 * Provides Browser Web Crypto APIs used by the Crypto service implementation.
19 *
20 * **When to use**
21 *
22 * Use to override the browser `Crypto` object used by the platform crypto
23 * layer.
24 *
25 * @category services
26 * @since 1.0.0
27 */
28export const WebCrypto = Context.Reference<Crypto>("@effect/platform-browser/Crypto/WebCrypto", {
29 defaultValue: () => globalThis.crypto
30})
31
32/**
33 * Layer that directly interfaces with the Web Crypto API.
34 *
35 * **When to use**
36 *
37 * Use to provide cryptographic randomness, UUID generation, and digest
38 * operations in browser runtimes backed by `globalThis.crypto`.
39 *
40 * **Details**
41 *
42 * Random bytes are produced with `crypto.getRandomValues`. Digests are computed
43 * with `crypto.subtle.digest` and returned as `Uint8Array` values.
44 *
45 * **Gotchas**
46 *
47 * The layer dies if the Web Crypto object is unavailable. Digest operations
48 * fail with `PlatformError` when `crypto.subtle.digest` is unavailable or the
49 * browser rejects the digest request.
50 *
51 * @category layers
52 * @since 1.0.0
53 */
54export const layer: Layer.Layer<EffectCrypto.Crypto> = Layer.effect(
55 EffectCrypto.Crypto,
56 Effect.gen(function*() {
57 const crypto = yield* WebCrypto
58 if (!crypto) {
59 return yield* Effect.die(new Error("Web Crypto API is not available"))
60 }
61 const randomBytes = (size: number): Uint8Array => {
62 const bytes = new Uint8Array(size)
63 for (let i = 0; i < bytes.length; i += 65_536) {
64 crypto.getRandomValues(bytes.subarray(i, i + 65_536))
65 }
66 return bytes
67 }
68
69 const digest: EffectCrypto.Crypto["digest"] = (algorithm, data) => {
70 if (typeof crypto.subtle?.digest !== "function") {
71 return Effect.fail(PlatformError.systemError({
72 module: "Crypto",
73 method: "digest",
74 _tag: "Unknown",
75 description: "crypto.subtle.digest is not available"
76 }))
77 }
78 return Effect.map(
79 Effect.tryPromise({
80 try: () => crypto.subtle.digest(algorithm, new Uint8Array(data)),
81 catch: (cause) =>
82 PlatformError.systemError({
83 module: "Crypto",
84 method: "digest",
85 _tag: "Unknown",
86 description: "Could not compute digest",
87 cause
88 })
89 }),
90 (buffer) => new Uint8Array(buffer)
91 )
92 }
93
94 return EffectCrypto.make({
95 randomBytes,
96 digest
97 })
98 })
99)