All files bufferBrowser.ts

88.63% Statements 39/44
75% Branches 27/36
90% Functions 9/10
88.09% Lines 37/42

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189          1x                         1x 74x   33x         41x                               1x 4x                     1x 22x                               1x 42x 42x                               1x                 72x                 66x 32x   34x       26x 8x 8x                     34x 34x 34x                 34x       32x   17x 17x 17x         15x 15x                                   17x     17x       17x       17x 9x 9x   17x      
/*!
 * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
 * Licensed under the MIT License.
 */
 
import * as base64js from "base64-js";
 
/**
 * Converts a Uint8Array to a string of the provided encoding
 * Useful when the array might be an {@link IsoBuffer}.
 *
 * @param arr - The array to convert.
 * @param encoding - Optional target encoding; only "utf8" and "base64" are
 * supported, with "utf8" being default.
 * @returns The converted string.
 *
 * @deprecated Moved to the `@fluidframework-internal/client-utils` package.
 */
export function Uint8ArrayToString(arr: Uint8Array, encoding?: string): string {
	switch (encoding) {
		case "base64": {
			return base64js.fromByteArray(arr);
		}
		case "utf8":
		case "utf-8":
		case undefined: {
			return new TextDecoder().decode(arr);
		}
		default: {
			throw new Error("invalid/unsupported encoding");
		}
	}
}
 
/**
 * Converts a {@link https://en.wikipedia.org/wiki/Base64 | base64} or
 * {@link https://en.wikipedia.org/wiki/UTF-8 | utf-8} string to array buffer.
 *
 * @param encoding - The input string's encoding.
 *
 * @deprecated Moved to the `@fluidframework-internal/client-utils` package.
 */
export const stringToBuffer = (input: string, encoding: string): ArrayBufferLike =>
	IsoBuffer.from(input, encoding).buffer;
 
/**
 * Convert binary blob to string format
 *
 * @param blob - the binary blob
 * @param encoding - output string's encoding
 * @returns the blob in string format
 *
 * @deprecated Moved to the `@fluidframework-internal/client-utils` package.
 */
export const bufferToString = (blob: ArrayBufferLike, encoding: string): string =>
	IsoBuffer.from(blob).toString(encoding);
 
/**
 * Determines if an object is an array buffer.
 *
 * @remarks Will detect and reject TypedArrays, like Uint8Array.
 * Reason - they can be viewport into Array, they can be accepted, but caller has to deal with
 * math properly (i.e. Take into account byteOffset at minimum).
 * For example, construction of new TypedArray can be in the form of new TypedArray(typedArray) or
 * new TypedArray(buffer, byteOffset, length), but passing TypedArray will result in fist path (and
 * ignoring byteOffice, length).
 *
 * @param obj - The object to determine if it is an ArrayBuffer.
 *
 * @deprecated Moved to the `@fluidframework-internal/client-utils` package.
 */
export function isArrayBuffer(obj: any): obj is ArrayBuffer {
	const maybe = obj as (Partial<ArrayBuffer> & Partial<Uint8Array>) | undefined;
	return (
		obj instanceof ArrayBuffer ||
		(typeof maybe === "object" &&
			maybe !== null &&
			typeof maybe.byteLength === "number" &&
			typeof maybe.slice === "function" &&
			maybe.byteOffset === undefined &&
			maybe.buffer === undefined)
	);
}
 
/**
 * Minimal implementation of Buffer for our usages in the browser environment.
 *
 * @deprecated Moved to the `@fluidframework-internal/client-utils` package.
 */
export class IsoBuffer extends Uint8Array {
	/**
	 * Convert the buffer to a string.
	 * Only supports encoding the whole string (unlike the Node Buffer equivalent)
	 * and only utf8 and base64 encodings.
	 *
	 * @param encoding - The encoding to use.
	 */
	public toString(encoding?: string): string {
		return Uint8ArrayToString(this, encoding);
	}
 
	/**
	 * @param value - (string | ArrayBuffer)
	 * @param encodingOrOffset - (string | number)
	 * @param length - (number)
	 */
	static from(value, encodingOrOffset?, length?): IsoBuffer {
		if (typeof value === "string") {
			return IsoBuffer.fromString(value, encodingOrOffset as string | undefined);
			// Capture any typed arrays, including Uint8Array (and thus - IsoBuffer!)
		} else if (value !== null && typeof value === "object" && isArrayBuffer(value.buffer)) {
			// The version of the from function for the node buffer, which takes a buffer or typed array
			// as first parameter, does not have any offset or length parameters. Those are just silently
			// ignored and not taken into account
			return IsoBuffer.fromArrayBuffer(value.buffer, value.byteOffset, value.byteLength);
		} else if (isArrayBuffer(value)) {
			return IsoBuffer.fromArrayBuffer(value, encodingOrOffset as number | undefined, length);
		} else E{
			throw new TypeError();
		}
	}
 
	static fromArrayBuffer(
		arrayBuffer: ArrayBuffer,
		byteOffset?: number,
		byteLength?: number,
	): IsoBuffer {
		const offset = byteOffset ?? 0;
		const validLength = byteLength ?? arrayBuffer.byteLength - offset;
		Iif (
			offset < 0 ||
			offset > arrayBuffer.byteLength ||
			validLength < 0 ||
			validLength + offset > arrayBuffer.byteLength
		) {
			throw new RangeError();
		}
 
		return new IsoBuffer(arrayBuffer, offset, validLength);
	}
 
	static fromString(str: string, encoding?: string): IsoBuffer {
		switch (encoding) {
			case "base64": {
				const sanitizedString = this.sanitizeBase64(str);
				const encoded = base64js.toByteArray(sanitizedString);
				return new IsoBuffer(encoded.buffer);
			}
			case "utf8":
			case "utf-8":
			case undefined: {
				const encoded = new TextEncoder().encode(str);
				return new IsoBuffer(encoded.buffer);
			}
			default: {
				throw new Error("invalid/unsupported encoding");
			}
		}
	}
 
	static isBuffer(obj: any): boolean {
		throw new Error("unimplemented");
	}
 
	/**
	 * Sanitize a base64 string to provide to base64-js library.
	 * {@link https://www.npmjs.com/package/base64-js} is not as tolerant of the same malformed base64 as Node'
	 * Buffer is.
	 */
	private static sanitizeBase64(str: string): string {
		let sanitizedStr = str;
		// Remove everything after padding - Node buffer ignores everything
		// after any padding whereas base64-js does not
		sanitizedStr = sanitizedStr.split("=")[0];
 
		// Remove invalid characters - Node buffer strips invalid characters
		// whereas base64-js replaces them with "A"
		sanitizedStr = sanitizedStr.replace(/[^\w+-/]/g, "");
 
		// Check for missing padding - Node buffer tolerates missing padding
		// whereas base64-js does not
		if (sanitizedStr.length % 4 !== 0) {
			const paddingArray = ["", "===", "==", "="];
			sanitizedStr += paddingArray[sanitizedStr.length % 4];
		}
		return sanitizedStr;
	}
}