import hasOwnProperty from 'shared/hasOwnProperty';
export type Destination = ReadableStreamController;
export type PrecomputedChunk = Uint8Array;
export opaque type Chunk = Uint8Array;
export type BinaryChunk = Uint8Array;
const supportsArrayBufferDetached = hasOwnProperty.call(
ArrayBuffer.prototype,
'detached',
);
let didWarnAboutMissingArrayBufferDetached = false;
function handleErrorInNextTick(error: any) {
setTimeout(() => {
throw error;
});
}
const LocalPromise = Promise;
export const scheduleMicrotask: (callback: () => void) => void =
typeof queueMicrotask === 'function'
? queueMicrotask
: callback => {
LocalPromise.resolve(null).then(callback).catch(handleErrorInNextTick);
};
export function scheduleWork(callback: () => void) {
setTimeout(callback, 0);
}
export function flushBuffered(destination: Destination) {
}
const VIEW_SIZE = 4096;
let currentView = null;
let writtenBytes = 0;
export function beginWriting(destination: Destination) {
currentView = new Uint8Array(VIEW_SIZE);
writtenBytes = 0;
}
export function writeChunk(
destination: Destination,
chunk: PrecomputedChunk | Chunk | BinaryChunk,
): void {
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 use a modern Edge runtime that supports ArrayBuffer.prototype.detached.',
);
didWarnAboutMissingArrayBufferDetached = true;
}
}
return;
}
if (chunk.byteLength > VIEW_SIZE) {
if (writtenBytes > 0) {
destination.enqueue(
new Uint8Array(
(currentView as any as Uint8Array).buffer,
0,
writtenBytes,
),
);
currentView = new Uint8Array(VIEW_SIZE);
writtenBytes = 0;
}
destination.enqueue(chunk);
return;
}
let bytesToWrite = chunk;
const allowableBytes =
(currentView as any as Uint8Array).length - writtenBytes;
if (allowableBytes < bytesToWrite.byteLength) {
if (allowableBytes === 0) {
destination.enqueue(currentView);
} else {
(currentView as any as Uint8Array).set(
bytesToWrite.subarray(0, allowableBytes),
writtenBytes,
);
destination.enqueue(currentView);
bytesToWrite = bytesToWrite.subarray(allowableBytes);
}
currentView = new Uint8Array(VIEW_SIZE);
writtenBytes = 0;
}
(currentView as any as Uint8Array).set(bytesToWrite, writtenBytes);
writtenBytes += bytesToWrite.byteLength;
}
export function writeChunkAndReturn(
destination: Destination,
chunk: PrecomputedChunk | Chunk | BinaryChunk,
): boolean {
writeChunk(destination, chunk);
return true;
}
export function completeWriting(destination: Destination) {
if (currentView && writtenBytes > 0) {
destination.enqueue(new Uint8Array(currentView.buffer, 0, writtenBytes));
currentView = null;
writtenBytes = 0;
}
}
export function close(destination: Destination) {
destination.close();
}
const textEncoder = new TextEncoder();
export function stringToChunk(content: string): Chunk {
return textEncoder.encode(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 Edge runtime.',
);
didWarnAboutMissingArrayBufferDetached = true;
}
}
return view.slice();
}
}
return view;
}
export function byteLengthOfChunk(chunk: Chunk | PrecomputedChunk): number {
return chunk.byteLength;
}
export function byteLengthOfBinaryChunk(chunk: BinaryChunk): number {
return chunk.byteLength;
}
export function closeWithError(destination: Destination, error: mixed): void {
if (typeof destination.error === 'function') {
destination.error(error);
} else {
destination.close();
}
}
export {createFastHashJS as createFastHash} from 'react-server/src/createFastHashJS';
export function readAsDataURL(blob: Blob): Promise<string> {
return blob.arrayBuffer().then(arrayBuffer => {
const encoded =
typeof Buffer === 'function' && typeof Buffer.from === 'function'
? Buffer.from(arrayBuffer).toString('base64')
: btoa(String.fromCharCode.apply(String, new Uint8Array(arrayBuffer)));
const mimeType = blob.type || 'application/octet-stream';
return 'data:' + mimeType + ';base64,' + encoded;
});
}