Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(client-utils): fix trace.js import of performance #21257

Merged
merged 10 commits into from
Jun 4, 2024
21 changes: 21 additions & 0 deletions packages/common/client-utils/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,27 @@ If you want to add code that does not meet these requirements, these other packa
**core-interfaces** package.
- **Zero-dependency shared code** should be put in the **core-utils** package.

## Isomorphic Code

One of the primary reasons for this package's existence is to provide isomorphic implementations of
Buffer and related utilities that work in both browser and Node.js environments.

Our general strategy for this is as follows:

- We use the export map in package.json to provide different entrypoints for browser (indexBrowser.js)
vs. Node.js (indexNode.js).

- Because the browser ecosystem is more complex (bunders, etc.), we improve our odds of success by making
the browser the default. Only Node.js relies on remapping via the export map.

- We further simplify things by only using the export map to resolve the initial entrypoint. We do not
rely on export maps to remap imports within the module. (Basically, the browser / node.js specific
implementations fork at the entrypoint and from that point on explicitly import browser or node
specific files.)

One thing it is important to be aware of is that our CJS support relies on copying a stub package.json
file to to dist/package.json to set the module type to commonjs. This prevents internal imports
jason-ha marked this conversation as resolved.
Show resolved Hide resolved

<!-- AUTO-GENERATED-CONTENT:START (README_DEPENDENCY_GUIDELINES_SECTION:includeHeading=TRUE) -->

<!-- prettier-ignore-start -->
Expand Down
28 changes: 3 additions & 25 deletions packages/common/client-utils/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,32 +34,10 @@
"default": "./dist/indexBrowser.js"
}
}
},
"./bufferBrowser.js": {
"node": {
"import": {
"types": "./lib/bufferNode.d.ts",
"default": "./lib/bufferNode.js"
},
"require": {
"types": "./dist/bufferNode.d.ts",
"default": "./dist/bufferNode.js"
}
},
"default": {
"import": {
"types": "./lib/bufferBrowser.d.ts",
"default": "./lib/bufferBrowser.js"
},
"require": {
"types": "./dist/bufferBrowser.d.ts",
"default": "./dist/bufferBrowser.js"
}
}
DLehenbauer marked this conversation as resolved.
Show resolved Hide resolved
}
},
"main": "lib/indexNode.js",
"types": "lib/indexNode.d.ts",
"main": "lib/indexBrowser.js",
"types": "lib/indexBrowser.d.ts",
DLehenbauer marked this conversation as resolved.
Show resolved Hide resolved
"scripts": {
"build": "fluid-build . --task build",
"build:commonjs": "fluid-build . --task commonjs",
Expand Down Expand Up @@ -89,7 +67,7 @@
"test:mocha": "npm run test:mocha:esm && npm run test:mocha:cjs",
"test:mocha:cjs": "mocha --recursive \"dist/test/mocha/**/*.spec.*js\" --exit -r node_modules/@fluid-internal/mocha-test-setup",
"test:mocha:esm": "mocha --recursive \"lib/test/mocha/**/*.spec.*js\" --exit -r node_modules/@fluid-internal/mocha-test-setup",
"tsc": "fluid-tsc commonjs --project ./tsconfig.cjs.json && copyfiles -f ./src/cjs/package.json ./dist",
"tsc": "fluid-tsc commonjs --project ./tsconfig.cjs.json && copyfiles -f ../../../common/build/build-common/src/cjs/package.json ./dist",
"typetests:gen": "flub generate typetests --dir . -v --level public",
"typetests:prepare": "flub typetests --dir . --reset --previous --normalize"
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
* Licensed under the MIT License.
*/

// Note: The exports map in package.json will correct this import to "./bufferNode.js" in node environments
// Note: See 'Isomorphic Code' section in the package README.md
jason-ha marked this conversation as resolved.
Show resolved Hide resolved
import { IsoBuffer } from "./bufferBrowser.js";

/**
Expand Down
46 changes: 46 additions & 0 deletions packages/common/client-utils/src/base64EncodingNode.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/*!
* Copyright (c) Microsoft Corporation and contributors. All rights reserved.
* Licensed under the MIT License.
*/

// Note: See 'Isomorphic Code' section in the package README.md
import { IsoBuffer } from "./bufferNode.js";

/**
* Converts the provided {@link https://en.wikipedia.org/wiki/Base64 | base64}-encoded string
* to {@link https://en.wikipedia.org/wiki/UTF-8 | utf-8}.
*
* @internal
*/
export const fromBase64ToUtf8 = (input: string): string =>
IsoBuffer.from(input, "base64").toString("utf8");

/**
* Converts the provided {@link https://en.wikipedia.org/wiki/UTF-8 | utf-8}-encoded string
* to {@link https://en.wikipedia.org/wiki/Base64 | base64}.
*
* @internal
*/
export const fromUtf8ToBase64 = (input: string): string =>
IsoBuffer.from(input, "utf8").toString("base64");

/**
* Convenience function to convert unknown encoding to utf8 that avoids
* buffer copies/encode ops when no conversion is needed.
* @param input - The source string to convert.
* @param encoding - The source string's encoding.
*
* @internal
*/
export const toUtf8 = (input: string, encoding: string): string => {
switch (encoding) {
case "utf8":
// eslint-disable-next-line unicorn/text-encoding-identifier-case -- this value is supported, just discouraged
case "utf-8": {
return input;
}
default: {
return IsoBuffer.from(input, encoding).toString();
}
}
};
7 changes: 0 additions & 7 deletions packages/common/client-utils/src/cjs/package.json

This file was deleted.

1 change: 1 addition & 0 deletions packages/common/client-utils/src/hashFileBrowser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

import * as base64js from "base64-js";

// Note: See 'Isomorphic Code' section in the package README.md
import { IsoBuffer } from "./bufferBrowser.js";

async function digestBuffer(file: IsoBuffer, algorithm: "SHA-1" | "SHA-256"): Promise<Uint8Array> {
Expand Down
1 change: 1 addition & 0 deletions packages/common/client-utils/src/hashFileNode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

import { sha1, sha256 } from "sha.js";

// Note: See 'Isomorphic Code' section in the package README.md
import type { IsoBuffer } from "./bufferNode.js";

/**
Expand Down
5 changes: 4 additions & 1 deletion packages/common/client-utils/src/indexBrowser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@
* Licensed under the MIT License.
*/

// Entrypoint for browser-specific code in the package.
// (See 'Isomorphic Code' section in the package README.md.)

export {
bufferToString,
isArrayBuffer,
Expand All @@ -13,7 +16,7 @@ export {
export { gitHashFile, hashFile } from "./hashFileBrowser.js";
export { performance } from "./performanceIsomorphic.js";

export { fromBase64ToUtf8, fromUtf8ToBase64, toUtf8 } from "./base64Encoding.js";
export { fromBase64ToUtf8, fromUtf8ToBase64, toUtf8 } from "./base64EncodingBrowser.js";
export { Uint8ArrayToArrayBuffer } from "./bufferShared.js";
export { EventEmitter } from "./eventEmitter.cjs";
export { type IsomorphicPerformance } from "./performanceIsomorphic.js";
Expand Down
5 changes: 4 additions & 1 deletion packages/common/client-utils/src/indexNode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,15 @@
* Licensed under the MIT License.
*/

// Entrypoint for Node.js-specific code in the package.
// (See 'Isomorphic Code' section in the package README.md.)

export { type Buffer } from "./bufferNode.js";
export { bufferToString, IsoBuffer, stringToBuffer, Uint8ArrayToString } from "./bufferNode.js";
export { gitHashFile, hashFile } from "./hashFileNode.js";
export { performance } from "./performanceIsomorphic.js";

export { fromBase64ToUtf8, fromUtf8ToBase64, toUtf8 } from "./base64Encoding.js";
export { fromBase64ToUtf8, fromUtf8ToBase64, toUtf8 } from "./base64EncodingNode.js";
export { Uint8ArrayToArrayBuffer } from "./bufferShared.js";
export { EventEmitter } from "./eventEmitter.cjs";
export { IsomorphicPerformance } from "./performanceIsomorphic.js";
Expand Down
16 changes: 16 additions & 0 deletions packages/common/client-utils/src/test/mocha/base64Encoding.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
/*!
* Copyright (c) Microsoft Corporation and contributors. All rights reserved.
* Licensed under the MIT License.
*/

import { strict as assert } from "node:assert";
import { fromBase64ToUtf8, fromUtf8ToBase64, toUtf8 } from "../../index.js";
DLehenbauer marked this conversation as resolved.
Show resolved Hide resolved

describe("base64Encoding", () => {
it("round-trips correctly", async () => {
const original = "hello world";
const base64 = fromUtf8ToBase64(original);
assert.equal(fromBase64ToUtf8(base64), original);
assert.equal(toUtf8(base64, "base64"), original);
});
});
DLehenbauer marked this conversation as resolved.
Show resolved Hide resolved
33 changes: 33 additions & 0 deletions packages/common/client-utils/src/test/mocha/trace.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/*!
* Copyright (c) Microsoft Corporation and contributors. All rights reserved.
* Licensed under the MIT License.
*/

import { strict as assert } from "node:assert";
import { Trace } from "../../index.js";
jason-ha marked this conversation as resolved.
Show resolved Hide resolved

describe("Trace", () => {
it("measure 2ms timeout", async () => {
const trace = Trace.start();

return new Promise((resolve, reject) => {
setTimeout(() => {
try {
const event = trace.trace();

// While we specify a 2ms timeout, it's possible for performance.now() to measure a
// slightly smaller duration due to timing attack and fingerprinting mitigations.
// (See https://developer.mozilla.org/en-US/docs/Web/API/Performance/now#security_requirements)
//
// Therefore, we conservatively require that only 1ms has elapsed.
assert(event.duration >= 1);
assert(event.totalTimeElapsed >= 1);

resolve(undefined);
} catch (error) {
reject(error);
}
}, 2);
});
});
});
2 changes: 1 addition & 1 deletion packages/common/client-utils/src/trace.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
* Licensed under the MIT License.
*/

import { performance } from "./indexNode.js";
import { performance } from "./performanceIsomorphic.js";
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the actual fix. There is no node/browser specific code involved. PerformanceIsomorphic just narrows the type of the global performance object to the intersection of what browser/node support.


/**
* Helper class for tracing performance of events
Expand Down
Loading