@fluid-internal/client-utils 2.0.0-internal.6.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.eslintrc.js +20 -0
- package/.mocharc.js +12 -0
- package/CHANGELOG.md +3 -0
- package/LICENSE +21 -0
- package/README.md +70 -0
- package/api-extractor.json +16 -0
- package/dist/base64Encoding.d.ts +28 -0
- package/dist/base64Encoding.d.ts.map +1 -0
- package/dist/base64Encoding.js +43 -0
- package/dist/base64Encoding.js.map +1 -0
- package/dist/bufferBrowser.d.ts +81 -0
- package/dist/bufferBrowser.d.ts.map +1 -0
- package/dist/bufferBrowser.js +195 -0
- package/dist/bufferBrowser.js.map +1 -0
- package/dist/bufferNode.d.ts +58 -0
- package/dist/bufferNode.d.ts.map +1 -0
- package/dist/bufferNode.js +54 -0
- package/dist/bufferNode.js.map +1 -0
- package/dist/bufferShared.d.ts +12 -0
- package/dist/bufferShared.d.ts.map +1 -0
- package/dist/bufferShared.js +21 -0
- package/dist/bufferShared.js.map +1 -0
- package/dist/eventForwarder.d.ts +33 -0
- package/dist/eventForwarder.d.ts.map +1 -0
- package/dist/eventForwarder.js +98 -0
- package/dist/eventForwarder.js.map +1 -0
- package/dist/hashFileBrowser.d.ts +30 -0
- package/dist/hashFileBrowser.d.ts.map +1 -0
- package/dist/hashFileBrowser.js +95 -0
- package/dist/hashFileBrowser.js.map +1 -0
- package/dist/hashFileNode.d.ts +30 -0
- package/dist/hashFileNode.d.ts.map +1 -0
- package/dist/hashFileNode.js +60 -0
- package/dist/hashFileNode.js.map +1 -0
- package/dist/index.d.ts +20 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +41 -0
- package/dist/index.js.map +1 -0
- package/dist/indexBrowser.d.ts +8 -0
- package/dist/indexBrowser.d.ts.map +1 -0
- package/dist/indexBrowser.js +19 -0
- package/dist/indexBrowser.js.map +1 -0
- package/dist/indexNode.d.ts +8 -0
- package/dist/indexNode.d.ts.map +1 -0
- package/dist/indexNode.js +19 -0
- package/dist/indexNode.js.map +1 -0
- package/dist/performanceBrowser.d.ts +10 -0
- package/dist/performanceBrowser.d.ts.map +1 -0
- package/dist/performanceBrowser.js +12 -0
- package/dist/performanceBrowser.js.map +1 -0
- package/dist/performanceIsomorphic.d.ts +12 -0
- package/dist/performanceIsomorphic.d.ts.map +1 -0
- package/dist/performanceIsomorphic.js +7 -0
- package/dist/performanceIsomorphic.js.map +1 -0
- package/dist/performanceNode.d.ts +10 -0
- package/dist/performanceNode.d.ts.map +1 -0
- package/dist/performanceNode.js +14 -0
- package/dist/performanceNode.js.map +1 -0
- package/dist/trace.d.ts +40 -0
- package/dist/trace.d.ts.map +1 -0
- package/dist/trace.js +36 -0
- package/dist/trace.js.map +1 -0
- package/dist/tsdoc-metadata.json +11 -0
- package/dist/typedEventEmitter.d.ts +37 -0
- package/dist/typedEventEmitter.d.ts.map +1 -0
- package/dist/typedEventEmitter.js +27 -0
- package/dist/typedEventEmitter.js.map +1 -0
- package/jest-puppeteer.config.js +13 -0
- package/jest.config.js +20 -0
- package/lib/base64Encoding.d.ts +28 -0
- package/lib/base64Encoding.d.ts.map +1 -0
- package/lib/base64Encoding.js +37 -0
- package/lib/base64Encoding.js.map +1 -0
- package/lib/bufferBrowser.d.ts +81 -0
- package/lib/bufferBrowser.d.ts.map +1 -0
- package/lib/bufferBrowser.js +168 -0
- package/lib/bufferBrowser.js.map +1 -0
- package/lib/bufferNode.d.ts +58 -0
- package/lib/bufferNode.d.ts.map +1 -0
- package/lib/bufferNode.js +48 -0
- package/lib/bufferNode.js.map +1 -0
- package/lib/bufferShared.d.ts +12 -0
- package/lib/bufferShared.d.ts.map +1 -0
- package/lib/bufferShared.js +17 -0
- package/lib/bufferShared.js.map +1 -0
- package/lib/eventForwarder.d.ts +33 -0
- package/lib/eventForwarder.d.ts.map +1 -0
- package/lib/eventForwarder.js +94 -0
- package/lib/eventForwarder.js.map +1 -0
- package/lib/hashFileBrowser.d.ts +30 -0
- package/lib/hashFileBrowser.d.ts.map +1 -0
- package/lib/hashFileBrowser.js +71 -0
- package/lib/hashFileBrowser.js.map +1 -0
- package/lib/hashFileNode.d.ts +30 -0
- package/lib/hashFileNode.d.ts.map +1 -0
- package/lib/hashFileNode.js +52 -0
- package/lib/hashFileNode.js.map +1 -0
- package/lib/index.d.ts +20 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +21 -0
- package/lib/index.js.map +1 -0
- package/lib/indexBrowser.d.ts +8 -0
- package/lib/indexBrowser.d.ts.map +1 -0
- package/lib/indexBrowser.js +8 -0
- package/lib/indexBrowser.js.map +1 -0
- package/lib/indexNode.d.ts +8 -0
- package/lib/indexNode.d.ts.map +1 -0
- package/lib/indexNode.js +8 -0
- package/lib/indexNode.js.map +1 -0
- package/lib/performanceBrowser.d.ts +10 -0
- package/lib/performanceBrowser.d.ts.map +1 -0
- package/lib/performanceBrowser.js +9 -0
- package/lib/performanceBrowser.js.map +1 -0
- package/lib/performanceIsomorphic.d.ts +12 -0
- package/lib/performanceIsomorphic.d.ts.map +1 -0
- package/lib/performanceIsomorphic.js +6 -0
- package/lib/performanceIsomorphic.js.map +1 -0
- package/lib/performanceNode.d.ts +10 -0
- package/lib/performanceNode.d.ts.map +1 -0
- package/lib/performanceNode.js +11 -0
- package/lib/performanceNode.js.map +1 -0
- package/lib/trace.d.ts +40 -0
- package/lib/trace.d.ts.map +1 -0
- package/lib/trace.js +32 -0
- package/lib/trace.js.map +1 -0
- package/lib/typedEventEmitter.d.ts +37 -0
- package/lib/typedEventEmitter.d.ts.map +1 -0
- package/lib/typedEventEmitter.js +23 -0
- package/lib/typedEventEmitter.js.map +1 -0
- package/package.json +140 -0
- package/prettier.config.cjs +8 -0
- package/src/base64Encoding.ts +42 -0
- package/src/bufferBrowser.ts +188 -0
- package/src/bufferNode.ts +75 -0
- package/src/bufferShared.ts +17 -0
- package/src/eventForwarder.ts +120 -0
- package/src/hashFileBrowser.ts +82 -0
- package/src/hashFileNode.ts +59 -0
- package/src/index.ts +22 -0
- package/src/indexBrowser.ts +14 -0
- package/src/indexNode.ts +14 -0
- package/src/performanceBrowser.ts +11 -0
- package/src/performanceIsomorphic.ts +13 -0
- package/src/performanceNode.ts +13 -0
- package/src/trace.ts +58 -0
- package/src/typedEventEmitter.ts +76 -0
- package/tsconfig.esnext.json +7 -0
- package/tsconfig.json +14 -0
- package/types/perf_hooks.d.ts +10 -0
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* Copyright (c) Microsoft Corporation and contributors. All rights reserved.
|
|
3
|
+
* Licensed under the MIT License.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import * as base64js from "base64-js";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Converts a Uint8Array to a string of the provided encoding
|
|
10
|
+
* Useful when the array might be an {@link IsoBuffer}.
|
|
11
|
+
*
|
|
12
|
+
* @param arr - The array to convert.
|
|
13
|
+
* @param encoding - Optional target encoding; only "utf8" and "base64" are
|
|
14
|
+
* supported, with "utf8" being default.
|
|
15
|
+
* @returns The converted string.
|
|
16
|
+
*
|
|
17
|
+
* @internal
|
|
18
|
+
*/
|
|
19
|
+
export function Uint8ArrayToString(arr: Uint8Array, encoding?: string): string {
|
|
20
|
+
switch (encoding) {
|
|
21
|
+
case "base64": {
|
|
22
|
+
return base64js.fromByteArray(arr);
|
|
23
|
+
}
|
|
24
|
+
case "utf8":
|
|
25
|
+
case "utf-8":
|
|
26
|
+
case undefined: {
|
|
27
|
+
return new TextDecoder().decode(arr);
|
|
28
|
+
}
|
|
29
|
+
default: {
|
|
30
|
+
throw new Error("invalid/unsupported encoding");
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Converts a {@link https://en.wikipedia.org/wiki/Base64 | base64} or
|
|
37
|
+
* {@link https://en.wikipedia.org/wiki/UTF-8 | utf-8} string to array buffer.
|
|
38
|
+
*
|
|
39
|
+
* @param encoding - The input string's encoding.
|
|
40
|
+
*
|
|
41
|
+
* @internal
|
|
42
|
+
*/
|
|
43
|
+
export const stringToBuffer = (input: string, encoding: string): ArrayBufferLike =>
|
|
44
|
+
IsoBuffer.from(input, encoding).buffer;
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Convert binary blob to string format
|
|
48
|
+
*
|
|
49
|
+
* @param blob - the binary blob
|
|
50
|
+
* @param encoding - output string's encoding
|
|
51
|
+
* @returns the blob in string format
|
|
52
|
+
*
|
|
53
|
+
* @internal
|
|
54
|
+
*/
|
|
55
|
+
export const bufferToString = (blob: ArrayBufferLike, encoding: string): string =>
|
|
56
|
+
IsoBuffer.from(blob).toString(encoding);
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Determines if an object is an array buffer.
|
|
60
|
+
*
|
|
61
|
+
* @remarks Will detect and reject TypedArrays, like Uint8Array.
|
|
62
|
+
* Reason - they can be viewport into Array, they can be accepted, but caller has to deal with
|
|
63
|
+
* math properly (i.e. Take into account byteOffset at minimum).
|
|
64
|
+
* For example, construction of new TypedArray can be in the form of new TypedArray(typedArray) or
|
|
65
|
+
* new TypedArray(buffer, byteOffset, length), but passing TypedArray will result in fist path (and
|
|
66
|
+
* ignoring byteOffice, length).
|
|
67
|
+
*
|
|
68
|
+
* @param obj - The object to determine if it is an ArrayBuffer.
|
|
69
|
+
*
|
|
70
|
+
* @internal
|
|
71
|
+
*/
|
|
72
|
+
export function isArrayBuffer(obj: any): obj is ArrayBuffer {
|
|
73
|
+
const maybe = obj as (Partial<ArrayBuffer> & Partial<Uint8Array>) | undefined;
|
|
74
|
+
return (
|
|
75
|
+
obj instanceof ArrayBuffer ||
|
|
76
|
+
(typeof maybe === "object" &&
|
|
77
|
+
maybe !== null &&
|
|
78
|
+
typeof maybe.byteLength === "number" &&
|
|
79
|
+
typeof maybe.slice === "function" &&
|
|
80
|
+
maybe.byteOffset === undefined &&
|
|
81
|
+
maybe.buffer === undefined)
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Minimal implementation of Buffer for our usages in the browser environment.
|
|
87
|
+
*
|
|
88
|
+
* @internal
|
|
89
|
+
*/
|
|
90
|
+
export class IsoBuffer extends Uint8Array {
|
|
91
|
+
/**
|
|
92
|
+
* Convert the buffer to a string.
|
|
93
|
+
* Only supports encoding the whole string (unlike the Node Buffer equivalent)
|
|
94
|
+
* and only utf8 and base64 encodings.
|
|
95
|
+
*
|
|
96
|
+
* @param encoding - The encoding to use.
|
|
97
|
+
*/
|
|
98
|
+
public toString(encoding?: string): string {
|
|
99
|
+
return Uint8ArrayToString(this, encoding);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* @param value - (string | ArrayBuffer)
|
|
104
|
+
* @param encodingOrOffset - (string | number)
|
|
105
|
+
* @param length - (number)
|
|
106
|
+
*/
|
|
107
|
+
static from(value, encodingOrOffset?, length?): IsoBuffer {
|
|
108
|
+
if (typeof value === "string") {
|
|
109
|
+
return IsoBuffer.fromString(value, encodingOrOffset as string | undefined);
|
|
110
|
+
// Capture any typed arrays, including Uint8Array (and thus - IsoBuffer!)
|
|
111
|
+
} else if (value !== null && typeof value === "object" && isArrayBuffer(value.buffer)) {
|
|
112
|
+
// The version of the from function for the node buffer, which takes a buffer or typed array
|
|
113
|
+
// as first parameter, does not have any offset or length parameters. Those are just silently
|
|
114
|
+
// ignored and not taken into account
|
|
115
|
+
return IsoBuffer.fromArrayBuffer(value.buffer, value.byteOffset, value.byteLength);
|
|
116
|
+
} else if (isArrayBuffer(value)) {
|
|
117
|
+
return IsoBuffer.fromArrayBuffer(value, encodingOrOffset as number | undefined, length);
|
|
118
|
+
} else {
|
|
119
|
+
throw new TypeError();
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
static fromArrayBuffer(
|
|
124
|
+
arrayBuffer: ArrayBuffer,
|
|
125
|
+
byteOffset?: number,
|
|
126
|
+
byteLength?: number,
|
|
127
|
+
): IsoBuffer {
|
|
128
|
+
const offset = byteOffset ?? 0;
|
|
129
|
+
const validLength = byteLength ?? arrayBuffer.byteLength - offset;
|
|
130
|
+
if (
|
|
131
|
+
offset < 0 ||
|
|
132
|
+
offset > arrayBuffer.byteLength ||
|
|
133
|
+
validLength < 0 ||
|
|
134
|
+
validLength + offset > arrayBuffer.byteLength
|
|
135
|
+
) {
|
|
136
|
+
throw new RangeError();
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return new IsoBuffer(arrayBuffer, offset, validLength);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
static fromString(str: string, encoding?: string): IsoBuffer {
|
|
143
|
+
switch (encoding) {
|
|
144
|
+
case "base64": {
|
|
145
|
+
const sanitizedString = this.sanitizeBase64(str);
|
|
146
|
+
const encoded = base64js.toByteArray(sanitizedString);
|
|
147
|
+
return new IsoBuffer(encoded.buffer);
|
|
148
|
+
}
|
|
149
|
+
case "utf8":
|
|
150
|
+
case "utf-8":
|
|
151
|
+
case undefined: {
|
|
152
|
+
const encoded = new TextEncoder().encode(str);
|
|
153
|
+
return new IsoBuffer(encoded.buffer);
|
|
154
|
+
}
|
|
155
|
+
default: {
|
|
156
|
+
throw new Error("invalid/unsupported encoding");
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
static isBuffer(obj: any): boolean {
|
|
162
|
+
throw new Error("unimplemented");
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Sanitize a base64 string to provide to base64-js library.
|
|
167
|
+
* {@link https://www.npmjs.com/package/base64-js} is not as tolerant of the same malformed base64 as Node'
|
|
168
|
+
* Buffer is.
|
|
169
|
+
*/
|
|
170
|
+
private static sanitizeBase64(str: string): string {
|
|
171
|
+
let sanitizedStr = str;
|
|
172
|
+
// Remove everything after padding - Node buffer ignores everything
|
|
173
|
+
// after any padding whereas base64-js does not
|
|
174
|
+
sanitizedStr = sanitizedStr.split("=")[0];
|
|
175
|
+
|
|
176
|
+
// Remove invalid characters - Node buffer strips invalid characters
|
|
177
|
+
// whereas base64-js replaces them with "A"
|
|
178
|
+
sanitizedStr = sanitizedStr.replace(/[^\w+-/]/g, "");
|
|
179
|
+
|
|
180
|
+
// Check for missing padding - Node buffer tolerates missing padding
|
|
181
|
+
// whereas base64-js does not
|
|
182
|
+
if (sanitizedStr.length % 4 !== 0) {
|
|
183
|
+
const paddingArray = ["", "===", "==", "="];
|
|
184
|
+
sanitizedStr += paddingArray[sanitizedStr.length % 4];
|
|
185
|
+
}
|
|
186
|
+
return sanitizedStr;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* Copyright (c) Microsoft Corporation and contributors. All rights reserved.
|
|
3
|
+
* Licensed under the MIT License.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Declare the subset of Buffer functionality we want to make available instead of
|
|
8
|
+
* exposing the entirely of Node's typings. This should match the public interface
|
|
9
|
+
* of the browser implementation, so any changes made in one should be made in both.
|
|
10
|
+
*
|
|
11
|
+
* @internal
|
|
12
|
+
*/
|
|
13
|
+
export declare class Buffer extends Uint8Array {
|
|
14
|
+
toString(encoding?: string): string;
|
|
15
|
+
/**
|
|
16
|
+
* @param value - (string | ArrayBuffer).
|
|
17
|
+
* @param encodingOrOffset - (string | number).
|
|
18
|
+
* @param length - (number).
|
|
19
|
+
*/
|
|
20
|
+
static from(value, encodingOrOffset?, length?): IsoBuffer;
|
|
21
|
+
static isBuffer(obj: any): obj is Buffer;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* @internal
|
|
26
|
+
*/
|
|
27
|
+
export const IsoBuffer = Buffer;
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* @internal
|
|
31
|
+
*/
|
|
32
|
+
export type IsoBuffer = Buffer;
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Converts a Uint8Array to a string of the provided encoding.
|
|
36
|
+
* @remarks Useful when the array might be an IsoBuffer.
|
|
37
|
+
* @param arr - The array to convert.
|
|
38
|
+
* @param encoding - Optional target encoding; only "utf8" and "base64" are
|
|
39
|
+
* supported, with "utf8" being default.
|
|
40
|
+
* @returns The converted string.
|
|
41
|
+
*
|
|
42
|
+
* @internal
|
|
43
|
+
*/
|
|
44
|
+
export function Uint8ArrayToString(arr: Uint8Array, encoding?: string): string {
|
|
45
|
+
// Make this check because Buffer.from(arr) will always do a buffer copy
|
|
46
|
+
return Buffer.isBuffer(arr) ? arr.toString(encoding) : Buffer.from(arr).toString(encoding);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Convert base64 or utf8 string to array buffer.
|
|
51
|
+
* @param encoding - The input string's encoding.
|
|
52
|
+
*
|
|
53
|
+
* @internal
|
|
54
|
+
*/
|
|
55
|
+
export function stringToBuffer(input: string, encoding: string): ArrayBufferLike {
|
|
56
|
+
const iso = IsoBuffer.from(input, encoding);
|
|
57
|
+
// In a Node environment, IsoBuffer may be a Node.js Buffer. Node.js will
|
|
58
|
+
// pool multiple small Buffer instances into a single ArrayBuffer, in which
|
|
59
|
+
// case we need to slice the appropriate span of bytes.
|
|
60
|
+
return iso.byteLength === iso.buffer.byteLength
|
|
61
|
+
? iso.buffer
|
|
62
|
+
: iso.buffer.slice(iso.byteOffset, iso.byteOffset + iso.byteLength);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Convert binary blob to string format
|
|
67
|
+
*
|
|
68
|
+
* @param blob - The binary blob
|
|
69
|
+
* @param encoding - Output string's encoding
|
|
70
|
+
* @returns The blob in string format
|
|
71
|
+
*
|
|
72
|
+
* @internal
|
|
73
|
+
*/
|
|
74
|
+
export const bufferToString = (blob: ArrayBufferLike, encoding: string): string =>
|
|
75
|
+
IsoBuffer.from(blob).toString(encoding);
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* Copyright (c) Microsoft Corporation and contributors. All rights reserved.
|
|
3
|
+
* Licensed under the MIT License.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Converts a Uint8Array array to an ArrayBuffer.
|
|
8
|
+
* @param array - Array to convert to ArrayBuffer.
|
|
9
|
+
*
|
|
10
|
+
* @internal
|
|
11
|
+
*/
|
|
12
|
+
export function Uint8ArrayToArrayBuffer(array: Uint8Array): ArrayBuffer {
|
|
13
|
+
if (array.byteOffset === 0 && array.byteLength === array.buffer.byteLength) {
|
|
14
|
+
return array.buffer;
|
|
15
|
+
}
|
|
16
|
+
return array.buffer.slice(array.byteOffset, array.byteOffset + array.byteLength);
|
|
17
|
+
}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* Copyright (c) Microsoft Corporation and contributors. All rights reserved.
|
|
3
|
+
* Licensed under the MIT License.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { EventEmitter } from "events";
|
|
7
|
+
import { IDisposable, IEvent, IEventProvider } from "@fluidframework/core-interfaces";
|
|
8
|
+
import { TypedEventEmitter } from "./typedEventEmitter";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Base class used for forwarding events from a source EventEmitter.
|
|
12
|
+
* This can be useful when all arbitrary listeners need to be removed,
|
|
13
|
+
* but the primary source needs to stay intact.
|
|
14
|
+
*
|
|
15
|
+
* @internal
|
|
16
|
+
*/
|
|
17
|
+
export class EventForwarder<TEvent = IEvent>
|
|
18
|
+
extends TypedEventEmitter<TEvent>
|
|
19
|
+
implements IDisposable
|
|
20
|
+
{
|
|
21
|
+
protected static isEmitterEvent(event: string): boolean {
|
|
22
|
+
return (
|
|
23
|
+
event === EventForwarder.newListenerEvent ||
|
|
24
|
+
event === EventForwarder.removeListenerEvent
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
private static readonly newListenerEvent = "newListener";
|
|
29
|
+
private static readonly removeListenerEvent = "removeListener";
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* {@inheritDoc @fluidframework/core-interfaces#IDisposable.disposed}
|
|
33
|
+
*/
|
|
34
|
+
public get disposed(): boolean {
|
|
35
|
+
return this.isDisposed;
|
|
36
|
+
}
|
|
37
|
+
private isDisposed: boolean = false;
|
|
38
|
+
|
|
39
|
+
private readonly forwardingEvents = new Map<
|
|
40
|
+
string,
|
|
41
|
+
Map<EventEmitter | IEventProvider<TEvent & IEvent>, () => void>
|
|
42
|
+
>();
|
|
43
|
+
|
|
44
|
+
constructor(source?: EventEmitter | IEventProvider<TEvent & IEvent>) {
|
|
45
|
+
super();
|
|
46
|
+
if (source !== undefined) {
|
|
47
|
+
// NewListener event is raised whenever someone starts listening to this events, so
|
|
48
|
+
// we keep track of events being listened to, and start forwarding from the source
|
|
49
|
+
// event emitter per event listened to on this
|
|
50
|
+
const removeListenerHandler = (event: string): void =>
|
|
51
|
+
this.unforwardEvent(source, event);
|
|
52
|
+
const newListenerHandler = (event: string): void => this.forwardEvent(source, event);
|
|
53
|
+
this.on(EventForwarder.removeListenerEvent, removeListenerHandler);
|
|
54
|
+
this.on(EventForwarder.newListenerEvent, newListenerHandler);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* {@inheritDoc @fluidframework/core-interfaces#IDisposable.dispose}
|
|
60
|
+
*/
|
|
61
|
+
public dispose(): void {
|
|
62
|
+
this.isDisposed = true;
|
|
63
|
+
for (const listenerRemovers of this.forwardingEvents.values()) {
|
|
64
|
+
for (const listenerRemover of listenerRemovers.values()) {
|
|
65
|
+
try {
|
|
66
|
+
listenerRemover();
|
|
67
|
+
} catch {
|
|
68
|
+
// Should be fine because of removeAllListeners below
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
this.removeAllListeners();
|
|
73
|
+
this.forwardingEvents.clear();
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
protected forwardEvent(
|
|
77
|
+
source: EventEmitter | IEventProvider<TEvent & IEvent>,
|
|
78
|
+
...events: string[]
|
|
79
|
+
): void {
|
|
80
|
+
for (const event of events) {
|
|
81
|
+
if (
|
|
82
|
+
source !== undefined &&
|
|
83
|
+
event !== undefined &&
|
|
84
|
+
!EventForwarder.isEmitterEvent(event)
|
|
85
|
+
) {
|
|
86
|
+
let sources = this.forwardingEvents.get(event);
|
|
87
|
+
if (sources === undefined) {
|
|
88
|
+
sources = new Map();
|
|
89
|
+
this.forwardingEvents.set(event, sources);
|
|
90
|
+
}
|
|
91
|
+
if (!sources.has(source)) {
|
|
92
|
+
const listener = (...args: any[]): boolean => this.emit(event, ...args);
|
|
93
|
+
sources.set(source, () => source.off(event, listener));
|
|
94
|
+
source.on(event, listener);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
protected unforwardEvent(
|
|
101
|
+
source: EventEmitter | IEventProvider<TEvent & IEvent>,
|
|
102
|
+
...events: string[]
|
|
103
|
+
): void {
|
|
104
|
+
for (const event of events) {
|
|
105
|
+
if (event !== undefined && !EventForwarder.isEmitterEvent(event)) {
|
|
106
|
+
const sources = this.forwardingEvents.get(event);
|
|
107
|
+
if (sources?.has(source) === true && this.listenerCount(event) === 0) {
|
|
108
|
+
const listenerRemover = sources.get(source);
|
|
109
|
+
if (listenerRemover !== undefined) {
|
|
110
|
+
listenerRemover();
|
|
111
|
+
}
|
|
112
|
+
sources.delete(source);
|
|
113
|
+
if (sources.size === 0) {
|
|
114
|
+
this.forwardingEvents.delete(event);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* Copyright (c) Microsoft Corporation and contributors. All rights reserved.
|
|
3
|
+
* Licensed under the MIT License.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import * as base64js from "base64-js";
|
|
7
|
+
import { IsoBuffer } from "./bufferBrowser";
|
|
8
|
+
|
|
9
|
+
async function digestBuffer(file: IsoBuffer, algorithm: "SHA-1" | "SHA-256"): Promise<Uint8Array> {
|
|
10
|
+
const hash = await crypto.subtle.digest(algorithm, file);
|
|
11
|
+
return new Uint8Array(hash);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function encodeDigest(hashArray: Uint8Array, encoding: "hex" | "base64"): string {
|
|
15
|
+
// eslint-disable-next-line default-case
|
|
16
|
+
switch (encoding) {
|
|
17
|
+
case "hex": {
|
|
18
|
+
const hashHex = Array.prototype.map
|
|
19
|
+
.call(hashArray, (byte) => {
|
|
20
|
+
return byte.toString(16).padStart(2, "0") as string;
|
|
21
|
+
})
|
|
22
|
+
.join("");
|
|
23
|
+
return hashHex;
|
|
24
|
+
}
|
|
25
|
+
case "base64": {
|
|
26
|
+
return base64js.fromByteArray(hashArray);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Hash a file. Consistent within a session, but should not be persisted and
|
|
33
|
+
* is not consistent with git.
|
|
34
|
+
* If called under an insecure context for a browser, this will fallback to
|
|
35
|
+
* using the node implementation.
|
|
36
|
+
*
|
|
37
|
+
* @param file - The contents of the file in a buffer.
|
|
38
|
+
* @param algorithm - The hash algorithm to use, artificially constrained by what is used internally.
|
|
39
|
+
* @param hashEncoding - The encoding of the returned hash, also artificially constrained.
|
|
40
|
+
* @returns The hash of the content of the buffer.
|
|
41
|
+
*
|
|
42
|
+
* @internal
|
|
43
|
+
*/
|
|
44
|
+
export async function hashFile(
|
|
45
|
+
file: IsoBuffer,
|
|
46
|
+
algorithm: "SHA-1" | "SHA-256" = "SHA-1",
|
|
47
|
+
hashEncoding: "hex" | "base64" = "hex",
|
|
48
|
+
): Promise<string> {
|
|
49
|
+
// Handle insecure contexts (e.g. running with local services)
|
|
50
|
+
// by deferring to Node version, which uses a hash polyfill
|
|
51
|
+
// When packed, this chunk will show as "FluidFramework-HashFallback" separately
|
|
52
|
+
// from the main chunk and will be of non-trivial size. It will not be served
|
|
53
|
+
// under normal circumstances.
|
|
54
|
+
if (crypto.subtle === undefined) {
|
|
55
|
+
return import(
|
|
56
|
+
/* webpackChunkName: "FluidFramework-HashFallback" */
|
|
57
|
+
"./hashFileNode"
|
|
58
|
+
).then(async (m) => m.hashFile(file, algorithm, hashEncoding));
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// This is split up this way to facilitate testing (see the test for more info)
|
|
62
|
+
const hashArray = await digestBuffer(file, algorithm);
|
|
63
|
+
return encodeDigest(hashArray, hashEncoding);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Create a github hash (Github hashes the string with blob and size)
|
|
68
|
+
* Must be called under secure context for browsers
|
|
69
|
+
*
|
|
70
|
+
* @param file - The contents of the file in a buffer
|
|
71
|
+
* @returns The sha1 hash of the content of the buffer with the `blob` prefix and size
|
|
72
|
+
*
|
|
73
|
+
* @internal
|
|
74
|
+
*/
|
|
75
|
+
export async function gitHashFile(file: IsoBuffer): Promise<string> {
|
|
76
|
+
const size = file.byteLength;
|
|
77
|
+
const filePrefix = `blob ${size.toString()}${String.fromCharCode(0)}`;
|
|
78
|
+
const hashBuffer = IsoBuffer.from(filePrefix + file.toString());
|
|
79
|
+
|
|
80
|
+
// hashFile uses sha1; if that changes this will need to change too
|
|
81
|
+
return hashFile(hashBuffer);
|
|
82
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* Copyright (c) Microsoft Corporation and contributors. All rights reserved.
|
|
3
|
+
* Licensed under the MIT License.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
// eslint-disable-next-line import/no-internal-modules
|
|
7
|
+
import sha1 from "sha.js/sha1";
|
|
8
|
+
// eslint-disable-next-line import/no-internal-modules
|
|
9
|
+
import sha256 from "sha.js/sha256";
|
|
10
|
+
import { IsoBuffer } from "./bufferNode";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Hash a file. Consistent within a session, but should not be persisted and
|
|
14
|
+
* is not consistent with git.
|
|
15
|
+
* If called under an insecure context for a browser, this will fallback to
|
|
16
|
+
* using the node implementation.
|
|
17
|
+
*
|
|
18
|
+
* @param file - The contents of the file in a buffer.
|
|
19
|
+
* @param algorithm - The hash algorithm to use, artificially constrained by what is used internally.
|
|
20
|
+
* @param hashEncoding - The encoding of the returned hash, also artificially constrained.
|
|
21
|
+
* @returns The hash of the content of the buffer.
|
|
22
|
+
*
|
|
23
|
+
* @internal
|
|
24
|
+
*/
|
|
25
|
+
export async function hashFile(
|
|
26
|
+
file: IsoBuffer,
|
|
27
|
+
algorithm: "SHA-1" | "SHA-256" = "SHA-1",
|
|
28
|
+
hashEncoding: "hex" | "base64" = "hex",
|
|
29
|
+
): Promise<string> {
|
|
30
|
+
let engine;
|
|
31
|
+
// eslint-disable-next-line default-case
|
|
32
|
+
switch (algorithm) {
|
|
33
|
+
case "SHA-1": {
|
|
34
|
+
engine = new sha1();
|
|
35
|
+
break;
|
|
36
|
+
}
|
|
37
|
+
case "SHA-256": {
|
|
38
|
+
engine = new sha256();
|
|
39
|
+
break;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return engine.update(file).digest(hashEncoding) as string;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Create a github hash (Github hashes the string with blob and size)
|
|
47
|
+
* Must be called under secure context for browsers
|
|
48
|
+
*
|
|
49
|
+
* @param file - The contents of the file in a buffer
|
|
50
|
+
* @returns The sha1 hash of the content of the buffer with the `blob` prefix and size
|
|
51
|
+
*
|
|
52
|
+
* @internal
|
|
53
|
+
*/
|
|
54
|
+
export async function gitHashFile(file: IsoBuffer): Promise<string> {
|
|
55
|
+
const size = file.byteLength;
|
|
56
|
+
const filePrefix = `blob ${size.toString()}${String.fromCharCode(0)}`;
|
|
57
|
+
const engine = new sha1();
|
|
58
|
+
return engine.update(filePrefix).update(file).digest("hex") as string;
|
|
59
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* Copyright (c) Microsoft Corporation and contributors. All rights reserved.
|
|
3
|
+
* Licensed under the MIT License.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export { fromBase64ToUtf8, fromUtf8ToBase64, toUtf8 } from "./base64Encoding";
|
|
7
|
+
export { Uint8ArrayToArrayBuffer } from "./bufferShared";
|
|
8
|
+
export { EventForwarder } from "./eventForwarder";
|
|
9
|
+
/**
|
|
10
|
+
* NOTE: This export is remapped to export from "./indexBrowser" in browser environments via package.json.
|
|
11
|
+
* Because the two files don't have fully isomorphic exports, using named exports for the full API surface
|
|
12
|
+
* is problematic if that named export includes values not in their intersection.
|
|
13
|
+
*
|
|
14
|
+
* In a future breaking change of client-utils, we could use a named export for their intersection if we
|
|
15
|
+
* desired.
|
|
16
|
+
*/
|
|
17
|
+
// eslint-disable-next-line no-restricted-syntax
|
|
18
|
+
export * from "./indexNode";
|
|
19
|
+
export { IsomorphicPerformance } from "./performanceIsomorphic";
|
|
20
|
+
// export { IRange, IRangeTrackerSnapshot, RangeTracker } from "./rangeTracker";
|
|
21
|
+
export { ITraceEvent, Trace } from "./trace";
|
|
22
|
+
export { EventEmitterEventType, TypedEventEmitter, TypedEventTransform } from "./typedEventEmitter";
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* Copyright (c) Microsoft Corporation and contributors. All rights reserved.
|
|
3
|
+
* Licensed under the MIT License.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export {
|
|
7
|
+
bufferToString,
|
|
8
|
+
isArrayBuffer,
|
|
9
|
+
IsoBuffer,
|
|
10
|
+
stringToBuffer,
|
|
11
|
+
Uint8ArrayToString,
|
|
12
|
+
} from "./bufferBrowser";
|
|
13
|
+
export { gitHashFile, hashFile } from "./hashFileBrowser";
|
|
14
|
+
export { performance } from "./performanceBrowser";
|
package/src/indexNode.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* Copyright (c) Microsoft Corporation and contributors. All rights reserved.
|
|
3
|
+
* Licensed under the MIT License.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export {
|
|
7
|
+
Buffer,
|
|
8
|
+
bufferToString,
|
|
9
|
+
IsoBuffer,
|
|
10
|
+
stringToBuffer,
|
|
11
|
+
Uint8ArrayToString,
|
|
12
|
+
} from "./bufferNode";
|
|
13
|
+
export { gitHashFile, hashFile } from "./hashFileNode";
|
|
14
|
+
export { performance } from "./performanceNode";
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* Copyright (c) Microsoft Corporation and contributors. All rights reserved.
|
|
3
|
+
* Licensed under the MIT License.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { IsomorphicPerformance } from "./performanceIsomorphic";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* @internal
|
|
10
|
+
*/
|
|
11
|
+
export const performance: IsomorphicPerformance = globalThis.performance;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* Copyright (c) Microsoft Corporation and contributors. All rights reserved.
|
|
3
|
+
* Licensed under the MIT License.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* This type contains all browser performance properties as optional, and some
|
|
8
|
+
* of the intersecting properties of node and browser performance as required.
|
|
9
|
+
*
|
|
10
|
+
* @internal
|
|
11
|
+
*/
|
|
12
|
+
export type IsomorphicPerformance = Partial<Performance> &
|
|
13
|
+
Pick<Performance, "clearMarks" | "mark" | "measure" | "now">;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* Copyright (c) Microsoft Corporation and contributors. All rights reserved.
|
|
3
|
+
* Licensed under the MIT License.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
// eslint-disable-next-line import/no-nodejs-modules
|
|
7
|
+
import { performance as nodePerformance } from "perf_hooks";
|
|
8
|
+
import { IsomorphicPerformance } from "./performanceIsomorphic";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* @internal
|
|
12
|
+
*/
|
|
13
|
+
export const performance: IsomorphicPerformance = nodePerformance;
|
package/src/trace.ts
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* Copyright (c) Microsoft Corporation and contributors. All rights reserved.
|
|
3
|
+
* Licensed under the MIT License.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { performance } from "./indexNode";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Helper class for tracing performance of events
|
|
10
|
+
* Time measurements are in milliseconds as a floating point with a decimal
|
|
11
|
+
*
|
|
12
|
+
* @internal
|
|
13
|
+
*/
|
|
14
|
+
export class Trace {
|
|
15
|
+
public static start(): Trace {
|
|
16
|
+
const startTick = performance.now();
|
|
17
|
+
return new Trace(startTick);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
protected lastTick: number;
|
|
21
|
+
protected constructor(public readonly startTick: number) {
|
|
22
|
+
this.lastTick = startTick;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
public trace(): ITraceEvent {
|
|
26
|
+
const tick = performance.now();
|
|
27
|
+
const event = {
|
|
28
|
+
totalTimeElapsed: tick - this.startTick,
|
|
29
|
+
duration: tick - this.lastTick,
|
|
30
|
+
tick,
|
|
31
|
+
};
|
|
32
|
+
this.lastTick = tick;
|
|
33
|
+
return event;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Event in a performance trace including time elapsed.
|
|
39
|
+
*
|
|
40
|
+
* @internal
|
|
41
|
+
*/
|
|
42
|
+
export interface ITraceEvent {
|
|
43
|
+
/**
|
|
44
|
+
* Total time elapsed since the start of the Trace.
|
|
45
|
+
* Measured in milliseconds as a floating point with a decimal
|
|
46
|
+
*/
|
|
47
|
+
readonly totalTimeElapsed: number;
|
|
48
|
+
/**
|
|
49
|
+
* Time elapsed since the last trace event.
|
|
50
|
+
* Measured in milliseconds as a floating point with a decimal
|
|
51
|
+
*/
|
|
52
|
+
readonly duration: number;
|
|
53
|
+
/**
|
|
54
|
+
* This number represents a relative time which should
|
|
55
|
+
* be consistent for all trace ticks.
|
|
56
|
+
*/
|
|
57
|
+
readonly tick: number;
|
|
58
|
+
}
|