one
Version:
One is a React framework for web, iOS, and Android apps.
86 lines (81 loc) • 2.68 kB
text/typescript
// the native source snapshots the input before returning, then hashes off the
// js thread. only the global webcrypto surface consumes this private adapter.
export type DigestSource = (
algorithm: string,
buffer: ArrayBuffer,
offset: number,
length: number
) => Promise<ArrayBuffer>
type DigestTarget = {
crypto?: {
subtle?: { digest?: unknown } | null
} | null
}
function namedError(name: string, message: string): Error {
// hermes has no DOMException. preserve the standard rejection name, as
// the secure random polyfill does for QuotaExceededError.
const error = new Error(message)
error.name = name
return error
}
// the exact byte range a BufferSource names, or a TypeError. detached
// buffers throw from the view construction, before any native call.
export function bufferSourceRange(
data: BufferSource,
verb: string
): { buffer: ArrayBuffer; offset: number; length: number } {
let buffer: ArrayBuffer
let offset = 0
let length: number
if (data instanceof ArrayBuffer) {
buffer = data
length = data.byteLength
} else if (ArrayBuffer.isView(data) && data.buffer instanceof ArrayBuffer) {
buffer = data.buffer
offset = data.byteOffset
length = data.byteLength
} else {
throw new TypeError(`${verb}: expected an ArrayBuffer or view.`)
}
new Uint8Array(buffer, offset, length)
return { buffer, offset, length }
}
export function installDigestPolyfill(
source: DigestSource,
target: DigestTarget = globalThis
): void {
const crypto = target.crypto
if (crypto == null || typeof crypto.subtle?.digest === 'function') return
const digest = async (
algorithm: AlgorithmIdentifier,
data: BufferSource
): Promise<ArrayBuffer> => {
const { buffer, offset, length } = bufferSourceRange(data, 'crypto.subtle.digest')
let name: string
if (
algorithm !== null &&
(typeof algorithm === 'object' || typeof algorithm === 'function')
) {
const value = (algorithm as Algorithm).name
if (value === undefined) {
throw new TypeError('crypto.subtle.digest: algorithm.name is required.')
}
name = `${value}`
} else {
name = `${algorithm}`
}
if (!/^sha-(1|256|384|512)$/i.test(name)) {
throw namedError('NotSupportedError', `Unsupported digest algorithm: ${name}.`)
}
try {
return await source(name.toUpperCase(), buffer, offset, length)
} catch (error) {
throw namedError(
'OperationError',
error instanceof Error ? error.message : 'Native digest failed.'
)
}
}
if (crypto.subtle == null) crypto.subtle = { digest }
else crypto.subtle.digest = digest
}