From 184753c43d79e443331d1b1a19e300ecd0aee5f0 Mon Sep 17 00:00:00 2001 From: Richard Park Date: Mon, 22 Jun 2020 16:28:54 -0700 Subject: [PATCH 01/16] Our usage of the crypto package in appconfig causes an issue in browsers. This adds functions to hash and hmac that use the appropriate implementation for browser (WebCrypto) and the node.js crypto package for node. --- .../app-configuration/package.json | 3 + .../src/appConfigCredential.ts | 14 +- .../src/internal/cryptoHelpers.browser.ts | 37 ++++++ .../src/internal/cryptoHelpers.ts | 30 +++++ .../app-configuration/src/internal/shims.d.ts | 121 ++++++++++++++++++ 5 files changed, 196 insertions(+), 9 deletions(-) create mode 100644 sdk/appconfiguration/app-configuration/src/internal/cryptoHelpers.browser.ts create mode 100644 sdk/appconfiguration/app-configuration/src/internal/cryptoHelpers.ts create mode 100644 sdk/appconfiguration/app-configuration/src/internal/shims.d.ts diff --git a/sdk/appconfiguration/app-configuration/package.json b/sdk/appconfiguration/app-configuration/package.json index ee2acab52f37..3d90f6c967de 100644 --- a/sdk/appconfiguration/app-configuration/package.json +++ b/sdk/appconfiguration/app-configuration/package.json @@ -23,6 +23,9 @@ "bugs": { "url": "https://github.com/Azure/azure-sdk-for-js/issues" }, + "browser": { + "./dist-esm/src/internal/cryptoHelpers.js": "./dist-esm/src/internal/cryptoHelpers.browser.js" + }, "files": [ "dist/**/*.js", "dist/**/*.js.map", diff --git a/sdk/appconfiguration/app-configuration/src/appConfigCredential.ts b/sdk/appconfiguration/app-configuration/src/appConfigCredential.ts index 5b5cc7c80613..fa8d94858e5a 100644 --- a/sdk/appconfiguration/app-configuration/src/appConfigCredential.ts +++ b/sdk/appconfiguration/app-configuration/src/appConfigCredential.ts @@ -1,8 +1,8 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. -import { createHash, createHmac } from "crypto"; import { ServiceClientCredentials, WebResource, URLBuilder } from "@azure/core-http"; +import { sha256Digest, sha256Hmac } from "./internal/cryptoHelpers"; /** * @internal @@ -23,12 +23,11 @@ export class AppConfigCredential implements ServiceClientCredentials { * @param {WebResource} webResource The WebResource to be signed. * @returns {Promise} The signed request object. */ - signRequest(webResource: WebResource): Promise { + async signRequest(webResource: WebResource): Promise { const verb = webResource.method.toUpperCase(); const utcNow = new Date().toUTCString(); - const contentHash = createHash("sha256") - .update(webResource.body || "") - .digest("base64"); + + const contentHash = await sha256Digest(webResource.body || ""); const signedHeaders = "x-ms-date;host;x-ms-content-sha256"; @@ -38,10 +37,7 @@ export class AppConfigCredential implements ServiceClientCredentials { const stringToSign = `${verb}\n${urlPathAndQuery}\n${utcNow};${url.getHost()};${contentHash}`; - const decodedSecret = Buffer.from(this.secret, "base64"); - var signature = createHmac("sha256", decodedSecret) - .update(stringToSign) - .digest("base64"); + const signature = await sha256Hmac(this.secret, stringToSign); webResource.headers.set("x-ms-date", utcNow); webResource.headers.set("x-ms-content-sha256", contentHash); diff --git a/sdk/appconfiguration/app-configuration/src/internal/cryptoHelpers.browser.ts b/sdk/appconfiguration/app-configuration/src/internal/cryptoHelpers.browser.ts new file mode 100644 index 000000000000..68b9ea081fb3 --- /dev/null +++ b/sdk/appconfiguration/app-configuration/src/internal/cryptoHelpers.browser.ts @@ -0,0 +1,37 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +/** + * @internal + * @ignore + */ +export async function sha256Digest(body: any): Promise { + const digest = await window.crypto.subtle.digest("SHA-256", new TextEncoder().encode(body || "")); + + return btoa(String.fromCharCode(...new Uint8Array(digest))); +} + +/** + * @internal + * @ignore + */ +export async function sha256Hmac(secret: string, stringToSign: string): Promise { + const key = await window.crypto.subtle.importKey( + "raw", + Uint8Array.from(atob(secret), (c) => c.charCodeAt(0)), + { + name: "HMAC", + hash: "SHA-256" + }, + false, + ["sign"] + ); + + const sigArray = await window.crypto.subtle.sign( + "HMAC", + key, + new TextEncoder().encode(stringToSign) + ); + + return btoa(String.fromCharCode(...new Uint8Array(sigArray))); +} diff --git a/sdk/appconfiguration/app-configuration/src/internal/cryptoHelpers.ts b/sdk/appconfiguration/app-configuration/src/internal/cryptoHelpers.ts new file mode 100644 index 000000000000..3e2b8d64a509 --- /dev/null +++ b/sdk/appconfiguration/app-configuration/src/internal/cryptoHelpers.ts @@ -0,0 +1,30 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +import { createHash, createHmac } from "crypto"; + +/** + * @internal + * @ignore + */ +export function sha256Digest(body: any): Promise { + return Promise.resolve( + createHash("sha256") + .update(body || "") + .digest("base64") + ); +} + +/** + * @internal + * @ignore + */ +export function sha256Hmac(secret: string, stringToSign: string): Promise { + const decodedSecret = Buffer.from(secret, "base64"); + + return Promise.resolve( + createHmac("sha256", decodedSecret) + .update(stringToSign) + .digest("base64") + ); +} diff --git a/sdk/appconfiguration/app-configuration/src/internal/shims.d.ts b/sdk/appconfiguration/app-configuration/src/internal/shims.d.ts new file mode 100644 index 000000000000..5df259ba38ed --- /dev/null +++ b/sdk/appconfiguration/app-configuration/src/internal/shims.d.ts @@ -0,0 +1,121 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +// d.ts shims provide types for things we use internally but are not part +// of @azure/app-configuration's surface area. + +interface Window { + readonly crypto: Crypto; +} + +declare let window: Window; + +declare var TextEncoder: { + prototype: TextEncoder; + new (): TextEncoder; +}; + +interface TextEncoder { + encode(input?: string): Uint8Array; +} + +interface SubtleCrypto { + digest( + algorithm: string, + data: + | Int8Array + | Int16Array + | Int32Array + | Uint8Array + | Uint16Array + | Uint32Array + | Uint8ClampedArray + | Float32Array + | Float64Array + | DataView + | ArrayBuffer + ): PromiseLike; + importKey( + format: "raw", + keyData: Uint8Array, + algorithm: HmacImportParams, + extractable: boolean, + keyUsages: string[] + ): PromiseLike; + sign( + algorithm: "HMAC", + key: CryptoKey, + data: + | Int8Array + | Int16Array + | Int32Array + | Uint8Array + | Uint16Array + | Uint32Array + | Uint8ClampedArray + | Float32Array + | Float64Array + | DataView + | ArrayBuffer + ): PromiseLike; +} + +interface HmacImportParams { + name: "HMAC"; + hash: "SHA-256"; +} + +interface CryptoKey {} + +interface Crypto { + readonly subtle: SubtleCrypto; +} + +declare var Crypto: { + prototype: Crypto; + new (): Crypto; +}; + +interface StringConstructor { + new (value?: any): String; + fromCharCode(...codes: number[]): string; +} + +/** + * Allows manipulation and formatting of text strings and determination and location of substrings within strings. + */ +declare var String: StringConstructor; + +interface Uint8Array { + [Symbol.iterator](): IterableIterator; +} + +interface Uint8ArrayConstructor { + new (elements: Iterable): Uint8Array; + + /** + * Creates an array from an array-like or iterable object. + * @param arrayLike An array-like or iterable object to convert to an array. + * @param mapfn A mapping function to call on every element of the array. + * @param thisArg Value of 'this' used to invoke the mapfn. + */ + from( + arrayLike: Iterable, + mapfn?: (v: number, k: number) => number, + thisArg?: any + ): Uint8Array; +} + +declare function atob(data: string): string; +declare function btoa(data: string): string; + +interface ArrayBufferTypes { + ArrayBuffer: ArrayBuffer; +} + +interface ArrayBufferConstructor { + readonly prototype: ArrayBuffer; + new (byteLength: number): ArrayBuffer; + isView(arg: any): arg is ArrayBufferView; +} +declare var ArrayBuffer: ArrayBufferConstructor; From 19ab09042b57976d5f87b6e608307152b911bcbb Mon Sep 17 00:00:00 2001 From: Richard Park Date: Tue, 23 Jun 2020 17:10:59 -0700 Subject: [PATCH 02/16] Adding in browser testing! - There was actually a "can't work in browser" function (util.isArray) that I replaced with the proper browser equivalent - A user agent test that I used to have to force to be node or browser now just "works" since it uses the core-http method for figuring out the environment. Also, got to remove some code for that. --- .../app-configuration/karma.conf.js | 134 ++++++++++++++++++ .../app-configuration/package.json | 42 ++++-- .../app-configuration/rollup.base.config.js | 104 +++++++++++++- .../app-configuration/rollup.config.js | 8 +- .../app-configuration/rollup.test.config.js | 15 +- .../src/appConfigurationClient.ts | 13 +- .../app-configuration/src/internal/helpers.ts | 3 +- .../app-configuration/test/index.spec.ts | 7 +- .../test/internal/http.spec.ts | 36 ++--- .../app-configuration/test/package.spec.ts | 7 +- 10 files changed, 317 insertions(+), 52 deletions(-) create mode 100644 sdk/appconfiguration/app-configuration/karma.conf.js diff --git a/sdk/appconfiguration/app-configuration/karma.conf.js b/sdk/appconfiguration/app-configuration/karma.conf.js new file mode 100644 index 000000000000..4c1b8d1316a5 --- /dev/null +++ b/sdk/appconfiguration/app-configuration/karma.conf.js @@ -0,0 +1,134 @@ +// https://github.com/karma-runner/karma-chrome-launcher +process.env.CHROME_BIN = require("puppeteer").executablePath(); +require("dotenv").config(); + +module.exports = function(config) { + config.set({ + // base path that will be used to resolve all patterns (eg. files, exclude) + basePath: "./", + + // frameworks to use + // available frameworks: https://npmjs.org/browse/keyword/karma-adapter + frameworks: ["mocha"], + + plugins: [ + "karma-mocha", + "karma-mocha-reporter", + "karma-chrome-launcher", + "karma-edge-launcher", + "karma-firefox-launcher", + "karma-ie-launcher", + "karma-env-preprocessor", + "karma-coverage", + "karma-remap-istanbul", + "karma-junit-reporter" + ], + + // list of files / patterns to load in the browser + files: [ + // polyfill service supporting IE11 missing features + // Promise,String.prototype.startsWith,String.prototype.endsWith,String.prototype.repeat,String.prototype.includes,Array.prototype.includes,Object.keys + "https://cdn.polyfill.io/v2/polyfill.js?features=Promise,String.prototype.startsWith,String.prototype.endsWith,String.prototype.repeat,String.prototype.includes,Array.prototype.includes,Object.keys|always", + "test-browser/index.js", + { pattern: "test-browser/index.js.map", type: "html", included: false, served: true } + ], + + // list of files / patterns to exclude + exclude: [], + + // preprocess matching files before serving them to the browser + // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor + preprocessors: { + "**/*.js": ["env"] + // IMPORTANT: COMMENT following line if you want to debug in your browsers!! + // Preprocess source file to calculate code coverage, however this will make source file unreadable + // "test-browser/index.js": ["coverage"] + }, + + // inject following environment values into browser testing with window.__env__ + // environment values MUST be exported or set with same console running "karma start" + // https://www.npmjs.com/package/karma-env-preprocessor + envPreprocessor: [ + "AZ_CONFIG_CONNECTION", + "AZURE_CLIENT_ID", + "AZURE_CLIENT_SECRET", + "AZURE_TENANT_ID", + "TEST_MODE" + ], + + // test results reporter to use + // possible values: 'dots', 'progress' + // available reporters: https://npmjs.org/browse/keyword/karma-reporter + reporters: ["mocha", "coverage", "karma-remap-istanbul", "junit"], + + coverageReporter: { + // specify a common output directory + dir: "coverage-browser/", + reporters: [{ type: "json", subdir: ".", file: "coverage.json" }] + }, + + remapIstanbulReporter: { + src: "coverage-browser/coverage.json", + reports: { + lcovonly: "coverage-browser/lcov.info", + html: "coverage-browser/html/report", + "text-summary": null, + cobertura: "./coverage-browser/cobertura-coverage.xml" + } + }, + + junitReporter: { + outputDir: "", // results will be saved as $outputDir/$browserName.xml + outputFile: "test-results.browser.xml", // if included, results will be saved as $outputDir/$browserName/$outputFile + suite: "", // suite will become the package name attribute in xml testsuite element + useBrowserName: false, // add browser name to report and classes names + nameFormatter: undefined, // function (browser, result) to customize the name attribute in xml testcase element + classNameFormatter: undefined, // function (browser, result) to customize the classname attribute in xml testcase element + properties: {} // key value pair of properties to add to the section of the report + }, + + // web server port + port: 9876, + + // enable / disable colors in the output (reporters and logs) + colors: true, + + // level of logging + // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG + logLevel: config.LOG_INFO, + + // enable / disable watching file and executing tests whenever any file changes + autoWatch: false, + + // start these browsers + // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher + // 'ChromeHeadless', 'Chrome', 'Firefox', 'Edge', 'IE' + browsers: ["ChromeHeadlessNoSandbox"], + customLaunchers: { + ChromeHeadlessNoSandbox: { + base: "ChromeHeadless", + flags: ["--no-sandbox"] + } + }, + + // Continuous Integration mode + // if true, Karma captures browsers, runs the tests and exits + singleRun: false, + + // Concurrency level + // how many browser should be started simultaneous + concurrency: 1, + + browserNoActivityTimeout: 600000, + browserDisconnectTimeout: 10000, + browserDisconnectTolerance: 3, + + client: { + mocha: { + // change Karma's debug.html to the mocha web reporter + reporter: "html", + timeout: "600000" + } + } + }); +}; diff --git a/sdk/appconfiguration/app-configuration/package.json b/sdk/appconfiguration/app-configuration/package.json index 3d90f6c967de..d1cc33960bd1 100644 --- a/sdk/appconfiguration/app-configuration/package.json +++ b/sdk/appconfiguration/app-configuration/package.json @@ -41,12 +41,16 @@ ], "scripts": { "audit": "node ../../../common/scripts/rush-audit.js && rimraf node_modules package-lock.json && npm i --package-lock-only 2>&1 && npm audit", - "build": "tsc -p . && rollup -c 2>&1 && npm run extract-api", - "build:test": "tsc -p . && rollup -c rollup.test.config.js 2>&1", + "build": "npm run build:node && npm run build:browser", + "build:node": "tsc -p . && cross-env ONLY_NODE=true rollup -c 2>&1 && npm run extract-api", + "build:browser": "tsc -p . && cross-env ONLY_BROWSER=true rollup -c 2>&1", + "build:test": "npm run build:test:node && npm run build:test:browser", + "build:test:node": "tsc -p . && cross-env ONLY_NODE=true rollup -c rollup.test.config.js 2>&1", + "build:test:browser": "tsc -p . && cross-env ONLY_BROWSER=true rollup -c rollup.test.config.js 2>&1", "build:samples": "node ../../../common/scripts/prep-samples.js && cd samples && tsc", "check-format": "prettier --list-different --config ../../.prettierrc.json \"src/**/*.ts\" \"test/**/*.ts\" \"*.{js,json}\"", - "clean": "rimraf dist dist-esm dist-test types *.tgz *.log", - "coverage": "nyc --reporter=lcov --exclude-after-remap=false mocha -t 120000 dist-test/index.js --reporter ../../../common/tools/mocha-multi-reporter.js", + "clean": "rimraf dist dist-esm test-dist test-browser types *.tgz *.log", + "coverage": "nyc --reporter=lcov --exclude-after-remap=false mocha -t 120000 test-dist/index.js --reporter ../../../common/tools/mocha-multi-reporter.js", "execute:samples": "npm run build:samples && echo Skipped.", "extract-api": "tsc -p . && api-extractor run --local", "format": "prettier --write --config ../../.prettierrc.json \"src/**/*.ts\" \"test/**/*.ts\" \"*.{js,json}\"", @@ -55,14 +59,14 @@ "prebuild": "npm run clean", "pack": "npm pack 2>&1", "swagger": "autorest --typescript swagger/swagger.md", - "integration-test:browser": "echo skipped", - "integration-test:node": "nyc mocha -r esm --require source-map-support/register --reporter ../../../common/tools/mocha-multi-reporter.js --timeout 180000 --full-trace dist-esm/test/*.spec.js dist-esm/test/**/*.spec.js", - "integration-test": "npm run unit-test:node && npm run unit-test:browser", - "test:browser": "npm run clean && npm run build:test && npm run unit-test:browser", - "test:node": "npm run clean && npm run build:test && npm run unit-test:node", + "integration-test": "npm run integration-test:node && npm run integration-test:browser", + "integration-test:browser": "npm run build:test:browser && karma start --single-run", + "integration-test:node": "npm run build:test:node && nyc mocha -r esm --require source-map-support/register --reporter ../../../common/tools/mocha-multi-reporter.js --timeout 180000 --full-trace dist-esm/test/*.spec.js dist-esm/test/**/*.spec.js", + "test:browser": "npm run clean && npm run build:test:browser && npm run unit-test:browser", + "test:node": "npm run clean && npm run build:test:node && npm run unit-test:node", "test": "npm run clean && npm run build:test && npm run unit-test", - "unit-test:browser": "echo skipped", - "unit-test:node": "mocha --require source-map-support/register --reporter ../../../common/tools/mocha-multi-reporter.js --timeout 180000 --full-trace dist-test/index.node.js", + "unit-test:browser": "", + "unit-test:node": "mocha --require source-map-support/register --reporter ../../../common/tools/mocha-multi-reporter.js --timeout 180000 --full-trace test-dist/index.node.js", "unit-test": "npm run unit-test:node && npm run unit-test:browser" }, "sideEffects": false, @@ -88,6 +92,8 @@ "@azure/test-utils-recorder": "^1.0.0", "@microsoft/api-extractor": "7.7.11", "@rollup/plugin-commonjs": "11.0.2", + "@rollup/plugin-inject": "^4.0.0", + "@rollup/plugin-json": "^4.0.0", "@rollup/plugin-multi-entry": "^3.0.0", "@rollup/plugin-node-resolve": "^8.0.0", "@rollup/plugin-replace": "^2.2.0", @@ -104,6 +110,17 @@ "eslint-plugin-no-only-tests": "^2.3.0", "eslint-plugin-promise": "^4.1.1", "esm": "^3.2.18", + "karma": "^4.0.1", + "karma-chrome-launcher": "^3.0.0", + "karma-coverage": "^2.0.0", + "karma-edge-launcher": "^0.4.2", + "karma-env-preprocessor": "^0.1.1", + "karma-firefox-launcher": "^1.1.0", + "karma-ie-launcher": "^1.0.0", + "karma-junit-reporter": "^2.0.1", + "karma-mocha": "^1.3.0", + "karma-mocha-reporter": "^2.2.5", + "karma-remap-istanbul": "^0.6.0", "mocha": "^7.1.1", "mocha-junit-reporter": "^1.18.0", "nock": "^12.0.3", @@ -111,12 +128,13 @@ "prettier": "^1.16.4", "rimraf": "^3.0.0", "rollup": "^1.16.3", + "rollup-plugin-shim": "^1.0.0", "rollup-plugin-sourcemaps": "^0.4.2", "rollup-plugin-terser": "^5.1.1", "sinon": "^9.0.2", "ts-node": "^8.3.0", "typescript": "~3.9.3", "uglify-js": "^3.4.9", - "@rollup/plugin-json": "^4.0.0" + "cross-env": "^7.0.2" } } diff --git a/sdk/appconfiguration/app-configuration/rollup.base.config.js b/sdk/appconfiguration/app-configuration/rollup.base.config.js index c7a5ebf75508..124caf2f7ed8 100644 --- a/sdk/appconfiguration/app-configuration/rollup.base.config.js +++ b/sdk/appconfiguration/app-configuration/rollup.base.config.js @@ -6,6 +6,8 @@ import { terser } from "rollup-plugin-terser"; import sourcemaps from "rollup-plugin-sourcemaps"; import shim from "rollup-plugin-shim"; import json from "@rollup/plugin-json"; +import * as path from "path"; +import inject from "@rollup/plugin-inject"; const pkg = require("./package.json"); const depNames = Object.keys(pkg.dependencies); @@ -23,8 +25,27 @@ const banner = [ " */" ].join("\n"); +const ignoreKnownWarnings = (warning) => { + if (warning.code === "THIS_IS_UNDEFINED") { + // This error happens frequently due to TypeScript emitting `this` at the + // top-level of a module. In this case its fine if it gets rewritten to + // undefined, so ignore this error. + return; + } + + if ( + warning.code === "CIRCULAR_DEPENDENCY" && + warning.importer.indexOf(path.normalize("node_modules/chai/lib") === 0) + ) { + // Chai contains circular references, but they are not fatal and can be ignored. + return; + } + + console.error(`(!) ${warning.message}`); +}; + export function nodeConfig(test = false) { - const externalNodeBuiltins = ["events"]; + const externalNodeBuiltins = ["events", "crypto", "path"]; const baseConfig = { input: input, external: depNames.concat(externalNodeBuiltins), @@ -39,10 +60,7 @@ export function nodeConfig(test = false) { plugins: [ sourcemaps(), replace({ - delimiters: ["", ""], - // replace dynamic checks with if (true) since this is for node only. - // Allows rollup's dead code elimination to be more aggressive. - "if (isNode)": "if (true)" + delimiters: ["", ""] }), nodeResolve({ preferBuiltins: true }), cjs() @@ -58,7 +76,7 @@ export function nodeConfig(test = false) { ); // different output file - baseConfig.output.file = "dist-test/index.node.js"; + baseConfig.output.file = "test-dist/index.node.js"; // mark assert packages we use as external baseConfig.external.push("assert"); @@ -75,3 +93,77 @@ export function nodeConfig(test = false) { return baseConfig; } + +export function browserConfig(test = false) { + const baseConfig = { + input: input, + output: { + file: "dist-browser/appconfiguration.js", + format: "umd", + name: "Azure.AppConfiguration", + globals: { + "@azure/core-http": "Azure.Core.HTTP", + nock: "nock", + fs: "fs-extra" + }, + sourcemap: true + }, + external: ["nock", "fs-extra"], + preserveSymlinks: false, + plugins: [ + sourcemaps(), + replace({ + delimiters: ["", ""] + }), + + shim({ + constants: `export default {}`, + fs: `export default {}`, + os: `export default {}`, + dotenv: `export function config() { }`, + path: `export function join() {}`, + timers: `export default {}`, + stream: `export default {}`, + process: `export default {}` + }), + + nodeResolve({ + mainFields: ["module", "browser"], + preferBuiltins: false + }), + cjs({ + namedExports: { + chai: ["assert", "expect", "use"], + assert: ["ok", "equal", "strictEqual", "deepEqual", "fail", "throws", "notEqual"], + events: ["EventEmitter"], + "@opentelemetry/api": ["CanonicalCode", "SpanKind", "TraceFlags"] + } + }), + + inject({ + modules: { + // Buffer: ["buffer", "Buffer"], + process: "process" + }, + exclude: ["./**/package.json"] + }), + + json() + ] + }; + + baseConfig.onwarn = ignoreKnownWarnings; + + if (test) { + baseConfig.input = ["dist-esm/test/*.spec.js", "dist-esm/test/internal/*.spec.js"]; + baseConfig.plugins.unshift(multiEntry({ exports: false })); + baseConfig.output.file = "test-browser/index.js"; + + // Disable tree-shaking of test code. In rollup-plugin-node-resolve@5.0.0, rollup started respecting + // the "sideEffects" field in package.json. Since our package.json sets "sideEffects=false", this also + // applies to test code, which causes all tests to be removed by tree-shaking. + baseConfig.treeshake = false; + } + + return baseConfig; +} diff --git a/sdk/appconfiguration/app-configuration/rollup.config.js b/sdk/appconfiguration/app-configuration/rollup.config.js index de6b48740722..14652aa67ed8 100644 --- a/sdk/appconfiguration/app-configuration/rollup.config.js +++ b/sdk/appconfiguration/app-configuration/rollup.config.js @@ -2,6 +2,12 @@ import * as base from "./rollup.base.config"; const inputs = []; -inputs.push(base.nodeConfig()); +if (!process.env.ONLY_BROWSER) { + inputs.push(base.nodeConfig()); +} + +if (!process.env.ONLY_NODE) { + inputs.push(base.browserConfig()); +} export default inputs; diff --git a/sdk/appconfiguration/app-configuration/rollup.test.config.js b/sdk/appconfiguration/app-configuration/rollup.test.config.js index c6723abed962..48ea762bf9e0 100644 --- a/sdk/appconfiguration/app-configuration/rollup.test.config.js +++ b/sdk/appconfiguration/app-configuration/rollup.test.config.js @@ -1,3 +1,16 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + import * as base from "./rollup.base.config"; -export default [base.nodeConfig(true)]; +const inputs = []; + +if (!process.env.ONLY_BROWSER) { + inputs.push(base.nodeConfig({ test: true })); +} + +if (!process.env.ONLY_NODE) { + inputs.push(base.browserConfig({ test: true })); +} + +export default inputs; diff --git a/sdk/appconfiguration/app-configuration/src/appConfigurationClient.ts b/sdk/appconfiguration/app-configuration/src/appConfigurationClient.ts index f8e638a2e0ac..9e43d5ef0b99 100644 --- a/sdk/appconfiguration/app-configuration/src/appConfigurationClient.ts +++ b/sdk/appconfiguration/app-configuration/src/appConfigurationClient.ts @@ -109,11 +109,6 @@ export interface InternalAppConfigurationClientOptions extends AppConfigurationC * NOTE: this is an internal option, not for general client usage. */ syncTokens?: SyncTokens; - /** - * Whether we want to run as if we're in node or in the browser. - * (currently only affects which name we use for the user agent header) - */ - isNodeOverride?: boolean; } /** @@ -543,7 +538,7 @@ export function getGeneratedClientOptions( ...defaults ], generateClientRequestIdHeader: true, - userAgentHeaderName: getUserAgentHeaderName(internalAppConfigOptions.isNodeOverride), + userAgentHeaderName: getUserAgentHeaderName(), userAgent }; } @@ -566,10 +561,8 @@ export function getUserAgentPrefix(userSuppliedUserAgent: string | undefined): s * @ignore * @internal */ -function getUserAgentHeaderName(isNodeOverride: boolean | undefined): string { - const definitelyIsNode = isNodeOverride != null ? isNodeOverride : coreHttpIsNode; - - if (definitelyIsNode) { +function getUserAgentHeaderName(): string { + if (coreHttpIsNode) { return "User-Agent"; } else { // we only need to override this when we're in the browser diff --git a/sdk/appconfiguration/app-configuration/src/internal/helpers.ts b/sdk/appconfiguration/app-configuration/src/internal/helpers.ts index be1d0b7bbd55..7c0db6410985 100644 --- a/sdk/appconfiguration/app-configuration/src/internal/helpers.ts +++ b/sdk/appconfiguration/app-configuration/src/internal/helpers.ts @@ -3,7 +3,6 @@ import { ListConfigurationSettingsOptions } from ".."; import { URLBuilder } from "@azure/core-http"; -import { isArray } from "util"; import { ListRevisionsOptions, ConfigurationSettingId, @@ -129,7 +128,7 @@ export function extractAfterTokenFromNextLink(nextLink: string) { let parsedLink = URLBuilder.parse(nextLink); let afterToken = parsedLink.getQueryParameterValue("after"); - if (afterToken == null || isArray(afterToken)) { + if (afterToken == null || Array.isArray(afterToken)) { throw new Error("Invalid nextLink - invalid after token"); } diff --git a/sdk/appconfiguration/app-configuration/test/index.spec.ts b/sdk/appconfiguration/app-configuration/test/index.spec.ts index 1cb599030b9c..dc513a573cb2 100644 --- a/sdk/appconfiguration/app-configuration/test/index.spec.ts +++ b/sdk/appconfiguration/app-configuration/test/index.spec.ts @@ -659,7 +659,7 @@ describe("AppConfigurationClient", () => { // this number is arbitrarily chosen to match the size of a page + 1 const expectedNumberOfLabels = 200; - const addSettingPromises = []; + let addSettingPromises = []; for (let i = 0; i < expectedNumberOfLabels; i++) { addSettingPromises.push( @@ -669,6 +669,11 @@ describe("AppConfigurationClient", () => { label: i.toString() }) ); + + if (i !== 0 && i % 10 === 0) { + await Promise.all(addSettingPromises); + addSettingPromises = []; + } } await Promise.all(addSettingPromises); diff --git a/sdk/appconfiguration/app-configuration/test/internal/http.spec.ts b/sdk/appconfiguration/app-configuration/test/internal/http.spec.ts index ab17dcf71499..a19495c655c7 100644 --- a/sdk/appconfiguration/app-configuration/test/internal/http.spec.ts +++ b/sdk/appconfiguration/app-configuration/test/internal/http.spec.ts @@ -13,6 +13,7 @@ import { } from "../testHelpers"; import * as chai from "chai"; import { Recorder } from "@azure/test-utils-recorder"; +import { isNode } from "@azure/core-http"; describe("http request related tests", function() { describe("unit tests", () => { @@ -35,27 +36,17 @@ describe("http request related tests", function() { }); it("useragentheadername", () => { - let options = getGeneratedClientOptions("base-uri", new SyncTokens(), { - isNodeOverride: false - }); - - assert.equal( - options.userAgentHeaderName, - "x-ms-useragent", - "Pretending we're running in a browser." - ); - - options = getGeneratedClientOptions("base-uri", new SyncTokens(), { - isNodeOverride: true - }); + let expectedUserHeaderName; - assert.equal(options.userAgentHeaderName, "User-Agent", "Pretending we're running in node."); + if (isNode) { + expectedUserHeaderName = "User-Agent"; + } else { + expectedUserHeaderName = "x-ms-useragent"; + } - // since we're only running these tests in node this will be the same as the - // case above (undefined, thus using the normal User-Agent header) - options = getGeneratedClientOptions("base-uri", new SyncTokens(), {}); + let options = getGeneratedClientOptions("base-uri", new SyncTokens(), {}); - assert.equal(options.userAgentHeaderName, "User-Agent", "We know that we're running node."); + assert.equal(options.userAgentHeaderName, expectedUserHeaderName); }); it("useragent", () => { @@ -161,6 +152,11 @@ describe("http request related tests", function() { let scope: nock.Scope; beforeEach(function() { + if (nock == null || nock.recorder == null) { + this.skip(); + return; + } + syncTokens = new SyncTokens(); client = @@ -178,6 +174,10 @@ describe("http request related tests", function() { }); afterEach(function() { + if (nock == null || nock.recorder == null) { + return; + } + if (!this.currentTest?.isPending()) { assert.ok(scope.isDone()); } diff --git a/sdk/appconfiguration/app-configuration/test/package.spec.ts b/sdk/appconfiguration/app-configuration/test/package.spec.ts index 14f5891c68d1..bdcfba4b28fb 100644 --- a/sdk/appconfiguration/app-configuration/test/package.spec.ts +++ b/sdk/appconfiguration/app-configuration/test/package.spec.ts @@ -1,12 +1,17 @@ import { join } from "path"; import * as assert from "assert"; +import { isNode } from "@azure/core-http"; import { packageVersion } from "../src/appConfigurationClient"; describe("packagejson related tests", () => { // if this test is failing you need to update the contant `packageVersion` referenced above // in the generated code. - it("user agent string matches the package version", () => { + it("user agent string matches the package version", function() { + if (!isNode) { + this.skip(); + } + let packageJsonContents: { [property: string]: string; }; From 9f1e645be0c00a02ce36ecc27f099bc5c5cfbd98 Mon Sep 17 00:00:00 2001 From: Richard Park Date: Wed, 24 Jun 2020 09:59:10 -0700 Subject: [PATCH 03/16] Enable browser testing --- sdk/appconfiguration/app-configuration/tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk/appconfiguration/app-configuration/tests.yml b/sdk/appconfiguration/app-configuration/tests.yml index 05592d751807..2f1e6d7fe2af 100644 --- a/sdk/appconfiguration/app-configuration/tests.yml +++ b/sdk/appconfiguration/app-configuration/tests.yml @@ -5,7 +5,7 @@ extends: parameters: PackageName: "@azure/app-configuration" ResourceServiceDirectory: appconfiguration - TestBrowser: false + TestBrowser: true TestSamples: false EnvVars: AZURE_CLIENT_ID: $(aad-azure-sdk-test-client-id) From b58883c4556d0c2f824fdbed716ab488ecebdd43 Mon Sep 17 00:00:00 2001 From: Richard Park Date: Wed, 24 Jun 2020 11:23:23 -0700 Subject: [PATCH 04/16] Trim down shims. --- .../app-configuration/src/internal/shims.d.ts | 30 ------------------- 1 file changed, 30 deletions(-) diff --git a/sdk/appconfiguration/app-configuration/src/internal/shims.d.ts b/sdk/appconfiguration/app-configuration/src/internal/shims.d.ts index 5df259ba38ed..a876b9653ad8 100644 --- a/sdk/appconfiguration/app-configuration/src/internal/shims.d.ts +++ b/sdk/appconfiguration/app-configuration/src/internal/shims.d.ts @@ -76,36 +76,6 @@ declare var Crypto: { new (): Crypto; }; -interface StringConstructor { - new (value?: any): String; - fromCharCode(...codes: number[]): string; -} - -/** - * Allows manipulation and formatting of text strings and determination and location of substrings within strings. - */ -declare var String: StringConstructor; - -interface Uint8Array { - [Symbol.iterator](): IterableIterator; -} - -interface Uint8ArrayConstructor { - new (elements: Iterable): Uint8Array; - - /** - * Creates an array from an array-like or iterable object. - * @param arrayLike An array-like or iterable object to convert to an array. - * @param mapfn A mapping function to call on every element of the array. - * @param thisArg Value of 'this' used to invoke the mapfn. - */ - from( - arrayLike: Iterable, - mapfn?: (v: number, k: number) => number, - thisArg?: any - ): Uint8Array; -} - declare function atob(data: string): string; declare function btoa(data: string): string; From 6fc1687fe70ed0b486eecab3b5db58281521a12c Mon Sep 17 00:00:00 2001 From: Richard Park Date: Wed, 24 Jun 2020 13:42:04 -0700 Subject: [PATCH 05/16] Remove the shims altogether - we can just `reference` the dom types as per guidelines. --- .../src/internal/cryptoHelpers.browser.ts | 2 + .../app-configuration/src/internal/shims.d.ts | 91 ------------------- 2 files changed, 2 insertions(+), 91 deletions(-) delete mode 100644 sdk/appconfiguration/app-configuration/src/internal/shims.d.ts diff --git a/sdk/appconfiguration/app-configuration/src/internal/cryptoHelpers.browser.ts b/sdk/appconfiguration/app-configuration/src/internal/cryptoHelpers.browser.ts index 68b9ea081fb3..7cd465bc9ca7 100644 --- a/sdk/appconfiguration/app-configuration/src/internal/cryptoHelpers.browser.ts +++ b/sdk/appconfiguration/app-configuration/src/internal/cryptoHelpers.browser.ts @@ -1,6 +1,8 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. +/// + /** * @internal * @ignore diff --git a/sdk/appconfiguration/app-configuration/src/internal/shims.d.ts b/sdk/appconfiguration/app-configuration/src/internal/shims.d.ts deleted file mode 100644 index a876b9653ad8..000000000000 --- a/sdk/appconfiguration/app-configuration/src/internal/shims.d.ts +++ /dev/null @@ -1,91 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -// d.ts shims provide types for things we use internally but are not part -// of @azure/app-configuration's surface area. - -interface Window { - readonly crypto: Crypto; -} - -declare let window: Window; - -declare var TextEncoder: { - prototype: TextEncoder; - new (): TextEncoder; -}; - -interface TextEncoder { - encode(input?: string): Uint8Array; -} - -interface SubtleCrypto { - digest( - algorithm: string, - data: - | Int8Array - | Int16Array - | Int32Array - | Uint8Array - | Uint16Array - | Uint32Array - | Uint8ClampedArray - | Float32Array - | Float64Array - | DataView - | ArrayBuffer - ): PromiseLike; - importKey( - format: "raw", - keyData: Uint8Array, - algorithm: HmacImportParams, - extractable: boolean, - keyUsages: string[] - ): PromiseLike; - sign( - algorithm: "HMAC", - key: CryptoKey, - data: - | Int8Array - | Int16Array - | Int32Array - | Uint8Array - | Uint16Array - | Uint32Array - | Uint8ClampedArray - | Float32Array - | Float64Array - | DataView - | ArrayBuffer - ): PromiseLike; -} - -interface HmacImportParams { - name: "HMAC"; - hash: "SHA-256"; -} - -interface CryptoKey {} - -interface Crypto { - readonly subtle: SubtleCrypto; -} - -declare var Crypto: { - prototype: Crypto; - new (): Crypto; -}; - -declare function atob(data: string): string; -declare function btoa(data: string): string; - -interface ArrayBufferTypes { - ArrayBuffer: ArrayBuffer; -} - -interface ArrayBufferConstructor { - readonly prototype: ArrayBuffer; - new (byteLength: number): ArrayBuffer; - isView(arg: any): arg is ArrayBufferView; -} -declare var ArrayBuffer: ArrayBufferConstructor; From 6c4d8ed589cc1cc72ea97df596f5741c4c2d9a07 Mon Sep 17 00:00:00 2001 From: Richard Park Date: Wed, 24 Jun 2020 14:01:48 -0700 Subject: [PATCH 06/16] - Fix the parameter type to be what it is (string | undefined) rather than any - Use async to better effect --- .../src/internal/cryptoHelpers.browser.ts | 2 +- .../src/internal/cryptoHelpers.ts | 20 ++++++++----------- 2 files changed, 9 insertions(+), 13 deletions(-) diff --git a/sdk/appconfiguration/app-configuration/src/internal/cryptoHelpers.browser.ts b/sdk/appconfiguration/app-configuration/src/internal/cryptoHelpers.browser.ts index 7cd465bc9ca7..62deda199383 100644 --- a/sdk/appconfiguration/app-configuration/src/internal/cryptoHelpers.browser.ts +++ b/sdk/appconfiguration/app-configuration/src/internal/cryptoHelpers.browser.ts @@ -7,7 +7,7 @@ * @internal * @ignore */ -export async function sha256Digest(body: any): Promise { +export async function sha256Digest(body: string | undefined): Promise { const digest = await window.crypto.subtle.digest("SHA-256", new TextEncoder().encode(body || "")); return btoa(String.fromCharCode(...new Uint8Array(digest))); diff --git a/sdk/appconfiguration/app-configuration/src/internal/cryptoHelpers.ts b/sdk/appconfiguration/app-configuration/src/internal/cryptoHelpers.ts index 3e2b8d64a509..ff57a4d48172 100644 --- a/sdk/appconfiguration/app-configuration/src/internal/cryptoHelpers.ts +++ b/sdk/appconfiguration/app-configuration/src/internal/cryptoHelpers.ts @@ -7,24 +7,20 @@ import { createHash, createHmac } from "crypto"; * @internal * @ignore */ -export function sha256Digest(body: any): Promise { - return Promise.resolve( - createHash("sha256") - .update(body || "") - .digest("base64") - ); +export async function sha256Digest(body: string | undefined): Promise { + return createHash("sha256") + .update(body || "") + .digest("base64"); } /** * @internal * @ignore */ -export function sha256Hmac(secret: string, stringToSign: string): Promise { +export async function sha256Hmac(secret: string, stringToSign: string): Promise { const decodedSecret = Buffer.from(secret, "base64"); - return Promise.resolve( - createHmac("sha256", decodedSecret) - .update(stringToSign) - .digest("base64") - ); + return createHmac("sha256", decodedSecret) + .update(stringToSign) + .digest("base64"); } From 8954d310002b5dea0397b3ddf31632c260e2b00d Mon Sep 17 00:00:00 2001 From: Richard Park Date: Wed, 24 Jun 2020 15:40:51 -0700 Subject: [PATCH 07/16] Remove unneeded Promise.resolve() --- .../app-configuration/src/appConfigCredential.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk/appconfiguration/app-configuration/src/appConfigCredential.ts b/sdk/appconfiguration/app-configuration/src/appConfigCredential.ts index fa8d94858e5a..78042d3217ca 100644 --- a/sdk/appconfiguration/app-configuration/src/appConfigCredential.ts +++ b/sdk/appconfiguration/app-configuration/src/appConfigCredential.ts @@ -46,6 +46,6 @@ export class AppConfigCredential implements ServiceClientCredentials { `HMAC-SHA256 Credential=${this.credential}, SignedHeaders=${signedHeaders}, Signature=${signature}` ); - return Promise.resolve(webResource); + return webResource; } } From 13ec3b86be8057e63097621d5a1a73e77d74f334 Mon Sep 17 00:00:00 2001 From: Richard Park Date: Wed, 24 Jun 2020 16:21:04 -0700 Subject: [PATCH 08/16] Adding changelog entry for browser support being added. --- sdk/appconfiguration/app-configuration/CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/sdk/appconfiguration/app-configuration/CHANGELOG.md b/sdk/appconfiguration/app-configuration/CHANGELOG.md index a195cd64a9c0..791420928dba 100644 --- a/sdk/appconfiguration/app-configuration/CHANGELOG.md +++ b/sdk/appconfiguration/app-configuration/CHANGELOG.md @@ -2,6 +2,7 @@ ## 1.0.2 (Unreleased) +- Adding browser support for the latest versions of Chrome, Edge and Firefox. ## 1.0.1 (2020-02-19) @@ -18,6 +19,7 @@ This release marks the general availability of the `@azure/app-configuration` pa - Allow developers to prepend additional information to the user agent header. Example: + ```typescript new AppConfigurationClient(connectionString, { userAgentOptions: { @@ -28,7 +30,6 @@ This release marks the general availability of the `@azure/app-configuration` pa ## 1.0.0-preview.10 (2019-12-10) - - Specifying filters for listConfigurationSettings() or listRevisions() is now done with the `keyFilter` or `labelFilter` strings rather than `keys` and `labels` as they were previously. From 12449a4bbbf72d8b7a931a0de2c8b139c717867b Mon Sep 17 00:00:00 2001 From: Richard Park Date: Thu, 25 Jun 2020 11:49:32 -0700 Subject: [PATCH 09/16] Browser support warrants a minor version bump! --- sdk/appconfiguration/app-configuration/CHANGELOG.md | 2 +- sdk/appconfiguration/app-configuration/package.json | 2 +- .../app-configuration/src/appConfigurationClient.ts | 2 +- .../app-configuration/src/internal/cryptoHelpers.browser.ts | 2 ++ 4 files changed, 5 insertions(+), 3 deletions(-) diff --git a/sdk/appconfiguration/app-configuration/CHANGELOG.md b/sdk/appconfiguration/app-configuration/CHANGELOG.md index 791420928dba..5107c0de71bb 100644 --- a/sdk/appconfiguration/app-configuration/CHANGELOG.md +++ b/sdk/appconfiguration/app-configuration/CHANGELOG.md @@ -1,6 +1,6 @@ # Release History -## 1.0.2 (Unreleased) +## 1.1.0 (Unreleased) - Adding browser support for the latest versions of Chrome, Edge and Firefox. diff --git a/sdk/appconfiguration/app-configuration/package.json b/sdk/appconfiguration/app-configuration/package.json index d1cc33960bd1..23669bd40b66 100644 --- a/sdk/appconfiguration/app-configuration/package.json +++ b/sdk/appconfiguration/app-configuration/package.json @@ -2,7 +2,7 @@ "name": "@azure/app-configuration", "author": "Microsoft Corporation", "description": "An isomorphic client library for the Azure App Configuration service.", - "version": "1.0.2", + "version": "1.1.0", "sdk-type": "client", "keywords": [ "node", diff --git a/sdk/appconfiguration/app-configuration/src/appConfigurationClient.ts b/sdk/appconfiguration/app-configuration/src/appConfigurationClient.ts index 9e43d5ef0b99..853be34aeb42 100644 --- a/sdk/appconfiguration/app-configuration/src/appConfigurationClient.ts +++ b/sdk/appconfiguration/app-configuration/src/appConfigurationClient.ts @@ -67,7 +67,7 @@ const packageName = "azsdk-js-app-configuration"; * @internal * @ignore */ -export const packageVersion = "1.0.2"; +export const packageVersion = "1.1.0"; const apiVersion = "1.0"; const ConnectionStringRegex = /Endpoint=(.*);Id=(.*);Secret=(.*)/; const deserializationContentTypes = { diff --git a/sdk/appconfiguration/app-configuration/src/internal/cryptoHelpers.browser.ts b/sdk/appconfiguration/app-configuration/src/internal/cryptoHelpers.browser.ts index 62deda199383..8dac4bcda951 100644 --- a/sdk/appconfiguration/app-configuration/src/internal/cryptoHelpers.browser.ts +++ b/sdk/appconfiguration/app-configuration/src/internal/cryptoHelpers.browser.ts @@ -10,6 +10,8 @@ export async function sha256Digest(body: string | undefined): Promise { const digest = await window.crypto.subtle.digest("SHA-256", new TextEncoder().encode(body || "")); + // The conversions here are a bit odd but necessary (see "Unicode strings" in the link below) + // https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/btoa return btoa(String.fromCharCode(...new Uint8Array(digest))); } From 209b1c681b9cbc7587027fb7b7a99339bed6970f Mon Sep 17 00:00:00 2001 From: Richard Park Date: Thu, 25 Jun 2020 11:50:57 -0700 Subject: [PATCH 10/16] This rollup config was just copied over, not needed here. (not sure _why_ since we do use 'chai' but no reason to have it if it doesn't get used). --- .../app-configuration/rollup.base.config.js | 21 ------------------- 1 file changed, 21 deletions(-) diff --git a/sdk/appconfiguration/app-configuration/rollup.base.config.js b/sdk/appconfiguration/app-configuration/rollup.base.config.js index 124caf2f7ed8..d3aad8fc078a 100644 --- a/sdk/appconfiguration/app-configuration/rollup.base.config.js +++ b/sdk/appconfiguration/app-configuration/rollup.base.config.js @@ -25,25 +25,6 @@ const banner = [ " */" ].join("\n"); -const ignoreKnownWarnings = (warning) => { - if (warning.code === "THIS_IS_UNDEFINED") { - // This error happens frequently due to TypeScript emitting `this` at the - // top-level of a module. In this case its fine if it gets rewritten to - // undefined, so ignore this error. - return; - } - - if ( - warning.code === "CIRCULAR_DEPENDENCY" && - warning.importer.indexOf(path.normalize("node_modules/chai/lib") === 0) - ) { - // Chai contains circular references, but they are not fatal and can be ignored. - return; - } - - console.error(`(!) ${warning.message}`); -}; - export function nodeConfig(test = false) { const externalNodeBuiltins = ["events", "crypto", "path"]; const baseConfig = { @@ -152,8 +133,6 @@ export function browserConfig(test = false) { ] }; - baseConfig.onwarn = ignoreKnownWarnings; - if (test) { baseConfig.input = ["dist-esm/test/*.spec.js", "dist-esm/test/internal/*.spec.js"]; baseConfig.plugins.unshift(multiEntry({ exports: false })); From 194c452d47f39cf71d895f826b2cbe99ad7dd63c Mon Sep 17 00:00:00 2001 From: Richard Park Date: Thu, 25 Jun 2020 13:13:20 -0700 Subject: [PATCH 11/16] Remove commented text --- sdk/appconfiguration/app-configuration/rollup.base.config.js | 1 - 1 file changed, 1 deletion(-) diff --git a/sdk/appconfiguration/app-configuration/rollup.base.config.js b/sdk/appconfiguration/app-configuration/rollup.base.config.js index d3aad8fc078a..9fa62d092b98 100644 --- a/sdk/appconfiguration/app-configuration/rollup.base.config.js +++ b/sdk/appconfiguration/app-configuration/rollup.base.config.js @@ -123,7 +123,6 @@ export function browserConfig(test = false) { inject({ modules: { - // Buffer: ["buffer", "Buffer"], process: "process" }, exclude: ["./**/package.json"] From 9a234044673c35e68de9c9e2edc53c52b46a2134 Mon Sep 17 00:00:00 2001 From: Richard Park Date: Thu, 25 Jun 2020 13:36:44 -0700 Subject: [PATCH 12/16] Switch over to using 'self' --- .../app-configuration/src/internal/cryptoHelpers.browser.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/sdk/appconfiguration/app-configuration/src/internal/cryptoHelpers.browser.ts b/sdk/appconfiguration/app-configuration/src/internal/cryptoHelpers.browser.ts index 8dac4bcda951..b62321bc88a4 100644 --- a/sdk/appconfiguration/app-configuration/src/internal/cryptoHelpers.browser.ts +++ b/sdk/appconfiguration/app-configuration/src/internal/cryptoHelpers.browser.ts @@ -8,7 +8,7 @@ * @ignore */ export async function sha256Digest(body: string | undefined): Promise { - const digest = await window.crypto.subtle.digest("SHA-256", new TextEncoder().encode(body || "")); + const digest = await self.crypto.subtle.digest("SHA-256", new TextEncoder().encode(body || "")); // The conversions here are a bit odd but necessary (see "Unicode strings" in the link below) // https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/btoa @@ -20,7 +20,7 @@ export async function sha256Digest(body: string | undefined): Promise { * @ignore */ export async function sha256Hmac(secret: string, stringToSign: string): Promise { - const key = await window.crypto.subtle.importKey( + const key = await self.crypto.subtle.importKey( "raw", Uint8Array.from(atob(secret), (c) => c.charCodeAt(0)), { @@ -31,7 +31,7 @@ export async function sha256Hmac(secret: string, stringToSign: string): Promise< ["sign"] ); - const sigArray = await window.crypto.subtle.sign( + const sigArray = await self.crypto.subtle.sign( "HMAC", key, new TextEncoder().encode(stringToSign) From ff9fa62d063a30742def24ce84e9de1b91b3baa7 Mon Sep 17 00:00:00 2001 From: Richard Park Date: Thu, 25 Jun 2020 13:49:44 -0700 Subject: [PATCH 13/16] Run the browser tests in live mode (no recordings in place at the moment) --- sdk/appconfiguration/app-configuration/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk/appconfiguration/app-configuration/package.json b/sdk/appconfiguration/app-configuration/package.json index 23669bd40b66..fcffc621b8d8 100644 --- a/sdk/appconfiguration/app-configuration/package.json +++ b/sdk/appconfiguration/app-configuration/package.json @@ -60,7 +60,7 @@ "pack": "npm pack 2>&1", "swagger": "autorest --typescript swagger/swagger.md", "integration-test": "npm run integration-test:node && npm run integration-test:browser", - "integration-test:browser": "npm run build:test:browser && karma start --single-run", + "integration-test:browser": "npm run build:test:browser && cross-env TEST_MODE=live karma start --single-run --log-level=debug", "integration-test:node": "npm run build:test:node && nyc mocha -r esm --require source-map-support/register --reporter ../../../common/tools/mocha-multi-reporter.js --timeout 180000 --full-trace dist-esm/test/*.spec.js dist-esm/test/**/*.spec.js", "test:browser": "npm run clean && npm run build:test:browser && npm run unit-test:browser", "test:node": "npm run clean && npm run build:test:node && npm run unit-test:node", From e48d693d8acc8dbc9a982206b9acb9fd2673ce0f Mon Sep 17 00:00:00 2001 From: Richard Park Date: Thu, 25 Jun 2020 13:53:04 -0700 Subject: [PATCH 14/16] Remove the debug log level for the karma command for now. --- sdk/appconfiguration/app-configuration/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk/appconfiguration/app-configuration/package.json b/sdk/appconfiguration/app-configuration/package.json index fcffc621b8d8..35be1baff2fe 100644 --- a/sdk/appconfiguration/app-configuration/package.json +++ b/sdk/appconfiguration/app-configuration/package.json @@ -60,7 +60,7 @@ "pack": "npm pack 2>&1", "swagger": "autorest --typescript swagger/swagger.md", "integration-test": "npm run integration-test:node && npm run integration-test:browser", - "integration-test:browser": "npm run build:test:browser && cross-env TEST_MODE=live karma start --single-run --log-level=debug", + "integration-test:browser": "npm run build:test:browser && cross-env TEST_MODE=live karma start --single-run", "integration-test:node": "npm run build:test:node && nyc mocha -r esm --require source-map-support/register --reporter ../../../common/tools/mocha-multi-reporter.js --timeout 180000 --full-trace dist-esm/test/*.spec.js dist-esm/test/**/*.spec.js", "test:browser": "npm run clean && npm run build:test:browser && npm run unit-test:browser", "test:node": "npm run clean && npm run build:test:node && npm run unit-test:node", From 661748d70b6206cb4262e12abc86c95a9cfd4452 Mon Sep 17 00:00:00 2001 From: Richard Park Date: Thu, 25 Jun 2020 14:12:09 -0700 Subject: [PATCH 15/16] Trim out a bunch of unneeded shim()'s. We only needed the dotenv shim after all. --- .../app-configuration/rollup.base.config.js | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/sdk/appconfiguration/app-configuration/rollup.base.config.js b/sdk/appconfiguration/app-configuration/rollup.base.config.js index 9fa62d092b98..2d1843a85832 100644 --- a/sdk/appconfiguration/app-configuration/rollup.base.config.js +++ b/sdk/appconfiguration/app-configuration/rollup.base.config.js @@ -98,14 +98,7 @@ export function browserConfig(test = false) { }), shim({ - constants: `export default {}`, - fs: `export default {}`, - os: `export default {}`, - dotenv: `export function config() { }`, - path: `export function join() {}`, - timers: `export default {}`, - stream: `export default {}`, - process: `export default {}` + dotenv: `export function config() { }` }), nodeResolve({ From 64d7b73948b3a2cf416dc113e65e666692355d6b Mon Sep 17 00:00:00 2001 From: Richard Park Date: Thu, 25 Jun 2020 14:41:02 -0700 Subject: [PATCH 16/16] Move configuration that was only for tests (to compensate for the recorder and nock) into the test branch so we can ensure that our browser bundle is clean of node dependencies. --- .../app-configuration/rollup.base.config.js | 45 ++++++++++++++++--- 1 file changed, 38 insertions(+), 7 deletions(-) diff --git a/sdk/appconfiguration/app-configuration/rollup.base.config.js b/sdk/appconfiguration/app-configuration/rollup.base.config.js index 2d1843a85832..38f0910b7b52 100644 --- a/sdk/appconfiguration/app-configuration/rollup.base.config.js +++ b/sdk/appconfiguration/app-configuration/rollup.base.config.js @@ -25,6 +25,25 @@ const banner = [ " */" ].join("\n"); +const ignoreKnownWarnings = (warning) => { + if (warning.code === "THIS_IS_UNDEFINED") { + // This error happens frequently due to TypeScript emitting `this` at the + // top-level of a module. In this case its fine if it gets rewritten to + // undefined, so ignore this error. + return; + } + + if ( + warning.code === "CIRCULAR_DEPENDENCY" && + warning.importer.indexOf(path.normalize("node_modules/chai/lib") === 0) + ) { + // Chai contains circular references, but they are not fatal and can be ignored. + return; + } + + console.error(`(!) ${warning.message}`); +}; + export function nodeConfig(test = false) { const externalNodeBuiltins = ["events", "crypto", "path"]; const baseConfig = { @@ -83,9 +102,7 @@ export function browserConfig(test = false) { format: "umd", name: "Azure.AppConfiguration", globals: { - "@azure/core-http": "Azure.Core.HTTP", - nock: "nock", - fs: "fs-extra" + "@azure/core-http": "Azure.Core.HTTP" }, sourcemap: true }, @@ -97,10 +114,6 @@ export function browserConfig(test = false) { delimiters: ["", ""] }), - shim({ - dotenv: `export function config() { }` - }), - nodeResolve({ mainFields: ["module", "browser"], preferBuiltins: false @@ -125,9 +138,27 @@ export function browserConfig(test = false) { ] }; + baseConfig.onwarn = ignoreKnownWarnings; + if (test) { baseConfig.input = ["dist-esm/test/*.spec.js", "dist-esm/test/internal/*.spec.js"]; + + baseConfig.external.unshift(...["process"]); + + baseConfig.output.globals = { + ...baseConfig.output.globals, + nock: "nock", + fs: "fs-extra", + "fs-extra": "fs", + process: "process", + path: "path" + }; + baseConfig.plugins.unshift(multiEntry({ exports: false })); + baseConfig.plugins.unshift( + ...[shim({ path: `export function join() {}`, dotenv: `export function config() { }` })] + ); + baseConfig.output.file = "test-browser/index.js"; // Disable tree-shaking of test code. In rollup-plugin-node-resolve@5.0.0, rollup started respecting