import type {Writable} from 'stream';
import {TextEncoder} from 'util';
import {createHash} from 'crypto';
import hasOwnProperty from 'shared/hasOwnProperty';
interface MightBeFlushable {
flush?: () => void;
}
export type Destination = Writable & MightBeFlushable;
export type PrecomputedChunk = Uint8Array;
export opaque type Chunk = string;
export type BinaryChunk = Uint8Array;
const supportsArrayBufferDetached = hasOwnProperty.call(
ArrayBuffer.prototype,
'detached',
);
let didWarnAboutMissingArrayBufferDetached = false;
export function scheduleWork(callback: () => void) {
setImmediate(callback);
}
export const scheduleMicrotask = queueMicrotask;
export function flushBuffered(destination: Destination) {
if (typeof destination.flush === 'function') {
destination.flush();
}
}
const VIEW_SIZE = 4096;
let currentView = null;
let writtenBytes = 0;
let destinationHasCapacity = true;
export function beginWriting(destination: Destination) {
currentView = new Uint8Array(VIEW_SIZE);
writtenBytes = 0;
destinationHasCapacity = true;
}
function writeStringChunk(destination: Destination, stringChunk: string) {
if (stringChunk.length === 0) {
return;
}
if (stringChunk.length * 3 > VIEW_SIZE) {
if (writtenBytes > 0) {
writeToDestination(
destination,
(currentView as any as Uint8Array).subarray(0, writtenBytes),
);
currentView = new Uint8Array(VIEW_SIZE);
writtenBytes = 0;
}
writeToDestination(destination, stringChunk);
return;
}
let target: Uint8Array = currentView as any;
if (writtenBytes > 0) {
target = (currentView as any as Uint8Array).subarray(writtenBytes);
}
const {read, written} = textEncoder.encodeInto(stringChunk, target);
writtenBytes += written;
if (read < stringChunk.length) {
writeToDestination(
destination,
(currentView as any).subarray(0, writtenBytes),
);
currentView = new Uint8Array(VIEW_SIZE);
writtenBytes = textEncoder.encodeInto(
stringChunk.slice(read),
currentView as any,
).written;
}
if (writtenBytes === VIEW_SIZE) {
writeToDestination(destination, currentView as any);
currentView = new Uint8Array(VIEW_SIZE);
writtenBytes = 0;
}
}
function writeViewChunk(
destination: Destination,
chunk: PrecomputedChunk | BinaryChunk,
) {
if (chunk.byteLength === 0) {
if (supportsArrayBufferDetached) {
if (chunk.buffer.detached) {
throw new Error(
'Cannot transport a detached buffer. ' +
'This usually happens when trying to transport multiple buffer-spanning views on the same underyling buffer. ' +
'Either reuse the exact same view by identity or copy the underlying buffer manually via `view.slice()`.',
);
}
} else if (__DEV__) {
if (!didWarnAboutMissingArrayBufferDetached) {
console.error(
'React encountered an empty buffer when writing in an environment that ' +
'does not support ArrayBuffer.prototype.detached. ' +
'This could be a security issue if the buffer was not actually empty. ' +
'Please upgrade to a Node.js version that supports ArrayBuffer.prototype.detached.',
);
didWarnAboutMissingArrayBufferDetached = true;
}
}
return;
}
if (chunk.byteLength > VIEW_SIZE) {
if (writtenBytes > 0) {
writeToDestination(
destination,
(currentView as any as Uint8Array).subarray(0, writtenBytes),
);
currentView = new Uint8Array(VIEW_SIZE);
writtenBytes = 0;
}
writeToDestination(destination, chunk);
return;
}
let bytesToWrite = chunk;
const allowableBytes =
(currentView as any as Uint8Array).length - writtenBytes;
if (allowableBytes < bytesToWrite.byteLength) {
if (allowableBytes === 0) {
writeToDestination(destination, currentView as any);
} else {
(currentView as any as Uint8Array).set(
bytesToWrite.subarray(0, allowableBytes),
writtenBytes,
);
writtenBytes += allowableBytes;
writeToDestination(destination, currentView as any);
bytesToWrite = bytesToWrite.subarray(allowableBytes);
}
currentView = new Uint8Array(VIEW_SIZE);
writtenBytes = 0;
}
(currentView as any as Uint8Array).set(bytesToWrite, writtenBytes);
writtenBytes += bytesToWrite.byteLength;
if (writtenBytes === VIEW_SIZE) {
writeToDestination(destination, currentView as any);
currentView = new Uint8Array(VIEW_SIZE);
writtenBytes = 0;
}
}
export function writeChunk(
destination: Destination,
chunk: PrecomputedChunk | Chunk | BinaryChunk,
): void {
if (typeof chunk === 'string') {
writeStringChunk(destination, chunk);
} else {
writeViewChunk(destination, chunk as any as PrecomputedChunk | BinaryChunk);
}
}
function writeToDestination(
destination: Destination,
view: string | Uint8Array,
) {
const currentHasCapacity = destination.write(view);
destinationHasCapacity = destinationHasCapacity && currentHasCapacity;
}
export function writeChunkAndReturn(
destination: Destination,
chunk: PrecomputedChunk | Chunk,
): boolean {
writeChunk(destination, chunk);
return destinationHasCapacity;
}
export function completeWriting(destination: Destination) {
if (currentView && writtenBytes > 0) {
destination.write(currentView.subarray(0, writtenBytes));
}
currentView = null;
writtenBytes = 0;
destinationHasCapacity = true;
}
export function close(destination: Destination) {
destination.end();
}
export const textEncoder: TextEncoder = new TextEncoder();
export function stringToChunk(content: string): Chunk {
return content;
}
export function stringToPrecomputedChunk(content: string): PrecomputedChunk {
const precomputedChunk = textEncoder.encode(content);
if (__DEV__) {
if (precomputedChunk.byteLength > VIEW_SIZE) {
console.error(
'precomputed chunks must be smaller than the view size configured for this host. This is a bug in React.',
);
}
}
return precomputedChunk;
}
export function typedArrayToBinaryChunk(
content: $ArrayBufferView,
): BinaryChunk {
const view = new Uint8Array(
content.buffer,
content.byteOffset,
content.byteLength,
);
if (content.byteLength > VIEW_SIZE) {
const isViewBufferSpanning =
content.byteOffset === 0 &&
content.byteLength === content.buffer.byteLength;
if (
!isViewBufferSpanning ||
content.buffer.resizable
) {
return view.slice();
}
if (!supportsArrayBufferDetached) {
if (__DEV__) {
if (!didWarnAboutMissingArrayBufferDetached) {
console.error(
'React copied a partial view into a fresh buffer as a safety precaution ' +
'because the environment does not implement ArrayBuffer.prototype.detached. ' +
'This negatively impacts performance. ' +
'Please upgrade to a modern Node.js version.',
);
didWarnAboutMissingArrayBufferDetached = true;
}
}
return view.slice();
}
}
return view;
}
export function byteLengthOfChunk(chunk: Chunk | PrecomputedChunk): number {
return typeof chunk === 'string'
? Buffer.byteLength(chunk, 'utf8')
: chunk.byteLength;
}
export function byteLengthOfBinaryChunk(chunk: BinaryChunk): number {
return chunk.byteLength;
}
export function closeWithError(destination: Destination, error: mixed): void {
destination.destroy(error);
}
export function createFastHash(input: string): string | number {
const hash = createHash('md5');
hash.update(input);
return hash.digest('hex');
}
export function readAsDataURL(blob: Blob): Promise<string> {
return blob.arrayBuffer().then(arrayBuffer => {
const encoded = Buffer.from(arrayBuffer).toString('base64');
const mimeType = blob.type || 'application/octet-stream';
return 'data:' + mimeType + ';base64,' + encoded;
});
}