98 lines
2.9 KiB
TypeScript
98 lines
2.9 KiB
TypeScript
/**
|
|
* Browser-backed implementation of Effect's Crypto service.
|
|
*
|
|
* This module provides a `Crypto.Crypto` layer backed by the Web Crypto API.
|
|
* The {@link WebCrypto} context reference defaults to `globalThis.crypto`, so
|
|
* browser programs can use the standard implementation while tests or embedded
|
|
* runtimes can provide their own `Crypto` object.
|
|
*
|
|
* @since 1.0.0
|
|
*/
|
|
import * as Context from "effect/Context"
|
|
import * as EffectCrypto from "effect/Crypto"
|
|
import * as Effect from "effect/Effect"
|
|
import * as Layer from "effect/Layer"
|
|
import * as PlatformError from "effect/PlatformError"
|
|
|
|
/**
|
|
* Provides Browser Web Crypto APIs used by the Crypto service implementation.
|
|
*
|
|
* **When to use**
|
|
*
|
|
* Use to override the browser `Crypto` object used by the platform crypto
|
|
* layer.
|
|
*
|
|
* @category references
|
|
* @since 1.0.0
|
|
*/
|
|
export const WebCrypto = Context.Reference<Crypto>("@effect/platform-browser/Crypto/WebCrypto", {
|
|
defaultValue: () => globalThis.crypto
|
|
})
|
|
|
|
/**
|
|
* Layer that directly interfaces with the Web Crypto API.
|
|
*
|
|
* **When to use**
|
|
*
|
|
* Use to provide cryptographic randomness, UUID generation, and digest
|
|
* operations in browser runtimes backed by `globalThis.crypto`.
|
|
*
|
|
* **Details**
|
|
*
|
|
* Random bytes are produced with `crypto.getRandomValues`. Digests are computed
|
|
* with `crypto.subtle.digest` and returned as `Uint8Array` values.
|
|
*
|
|
* **Gotchas**
|
|
*
|
|
* The layer dies if the Web Crypto object is unavailable. Digest operations
|
|
* fail with `PlatformError` when `crypto.subtle.digest` is unavailable or the
|
|
* browser rejects the digest request.
|
|
*
|
|
* @category layers
|
|
* @since 1.0.0
|
|
*/
|
|
export const layer: Layer.Layer<EffectCrypto.Crypto> = Layer.effect(
|
|
EffectCrypto.Crypto,
|
|
Effect.gen(function*() {
|
|
const crypto = yield* WebCrypto
|
|
if (!crypto) {
|
|
return yield* Effect.die(new Error("Web Crypto API is not available"))
|
|
}
|
|
const randomBytes = (size: number): Uint8Array => {
|
|
const bytes = new Uint8Array(size)
|
|
crypto.getRandomValues(bytes)
|
|
return bytes
|
|
}
|
|
|
|
const digest: EffectCrypto.Crypto["digest"] = (algorithm, data) => {
|
|
if (typeof crypto.subtle.digest !== "function") {
|
|
return Effect.fail(PlatformError.systemError({
|
|
module: "Crypto",
|
|
method: "digest",
|
|
_tag: "Unknown",
|
|
description: "crypto.subtle.digest is not available"
|
|
}))
|
|
}
|
|
return Effect.map(
|
|
Effect.tryPromise({
|
|
try: () => crypto.subtle.digest(algorithm, new Uint8Array(data)),
|
|
catch: (cause) =>
|
|
PlatformError.systemError({
|
|
module: "Crypto",
|
|
method: "digest",
|
|
_tag: "Unknown",
|
|
description: "Could not compute digest",
|
|
cause
|
|
})
|
|
}),
|
|
(buffer) => new Uint8Array(buffer)
|
|
)
|
|
}
|
|
|
|
return EffectCrypto.make({
|
|
randomBytes,
|
|
digest
|
|
})
|
|
})
|
|
)
|