diff --git a/.gitattributes b/.gitattributes index d7bd9551..c8986de0 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,8 +1,14 @@ .gitattributes text .gitignore text +.npmignore text LICENSE text +*.cmd text +*.js text +*.json text *.md text +*.ts text +*.txt text *.yml text # Bash only with Unix line endings diff --git a/.gitignore b/.gitignore index e69de29b..a6b7becc 100644 --- a/.gitignore +++ b/.gitignore @@ -0,0 +1,12 @@ +# ignore the npm packages +/**/node_modules/ +# ignore auto-generated (transpiled) js files +src/**/*.js +tests/**/*.js +distrib/* +secrets/* +/**/speech.key +.idea/ +test-javascript-junit.xml +coverage/* +*.tgz diff --git a/.npmignore b/.npmignore new file mode 100644 index 00000000..e55ff973 --- /dev/null +++ b/.npmignore @@ -0,0 +1,8 @@ +/.vscode/* +/node_modules/* +/.gitattributes +/.gitignore +/.npmignore +/.npmrc +/.vscode +/web.config diff --git a/BuildTestConfig.cmd b/BuildTestConfig.cmd new file mode 100644 index 00000000..db6541ef --- /dev/null +++ b/BuildTestConfig.cmd @@ -0,0 +1,57 @@ +@REM Copyright (c) Microsoft Corporation. All rights reserved. +@REM Licensed under the MIT license. +@echo off +setlocal +if "%~1" equ "/?" goto :Usage +if "%~1" equ "-?" goto :Usage +if "%~1" equ "?" goto :Usage +if "%~1" equ "/h" goto :Usage +if "%~1" equ "-h" goto :Usage + +set TEST_SETTING_FILE_DIR=%~dp0\secrets +if NOT EXIST "%TEST_SETTING_FILE_DIR%" ( + md "%TEST_SETTING_FILE_DIR%" || ( + echo Error creating directory %TEST_SETTING_FILE_DIR% + exit /b 1 + ) +) + +set TEST_SETTING_FILE_NAME=%TEST_SETTING_FILE_DIR%\TestConfiguration.ts + +if EXIST "%TEST_SETTING_FILE_NAME%" ( + echo Clearing values from settings file. + echo. > "%TEST_SETTING_FILE_NAME%" || ( + echo Error creating file %TEST_SETTING_FILE_NAME% + exit /b 1 + ) +) + +@echo import { Settings } from "../tests/Settings" > "%TEST_SETTING_FILE_NAME%" + +:NextArg +if "%~1" == "" ( + goto :eof +) + +for /f "tokens=1,2 delims=:" %%I in ("%~1") do ( + echo Setting Settings.%%I = "%%J" + echo Settings.%%I = "%%J"; >> "%TEST_SETTING_FILE_NAME%" +) + +shift +goto :NextArg + +exit /b 0 + +:Usage +@echo off +echo. +echo Usage: %~n0 ^:^ +echo. +echo Writes any ^:^ pair to the test settings file for JavaScript bindings tests. +echo. +echo The file will be erased before new values are added. +echo. +echo Current settings available are: [SpeechSubscriptionKey:^] [SpeechRegion:^] [LuisSubscriptionKey:^] [LuisRegion:^] [LuisAppId:^] +echo. +exit /b 1 diff --git a/LICENSE b/LICENSE index 21071075..8d118e1a 100644 --- a/LICENSE +++ b/LICENSE @@ -1,21 +1,14 @@ - MIT License +------------------------------------------- START OF LICENSE ----------------------------------------- +Microsoft Azure Cognitive Services Speech SDK Javascript +Copyright (c) Microsoft Corporation +All rights reserved. - Copyright (c) Microsoft Corporation. All rights reserved. +MIT License - Permission is hereby granted, free of charge, to any person obtaining a copy - of this software and associated documentation files (the "Software"), to deal - in the Software without restriction, including without limitation the rights - to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - copies of the Software, and to permit persons to whom the Software is - furnished to do so, subject to the following conditions: +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the Software), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - The above copyright notice and this permission notice shall be included in all - copies or substantial portions of the Software. +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - SOFTWARE +THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +----------------------------------------------- END OF LICENSE ------------------------------------------ \ No newline at end of file diff --git a/README.md b/README.md index 72f1506a..4877deba 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,77 @@ +# Microsoft Cognitive Services Speech SDK for JavaScript -# Contributing +Visit https://aka.ms/csspeech. + +## Build the project + +To build the project you need to install the required packages, and then run the actual build. + +### Install the required packages + +Installation of the required packages is required once in your enlistment. Our automated build restores the packages with the following command: + +``` +npm ci +``` + +This will install packages exactly matching the package configuration from `package-lock.json`. + +In a development environment you can also use the following command: + +``` +npm install +``` + +### Run the build + +Once the dependencies are installed run the build by + +``` +npm run build +``` + +or + +``` +npx gulp bundle +``` + +or + +``` +npx gulp compress +``` + +> Note: `npx` is packaged with NPM 5.2 or above. Update NPM / Node if you +> don't have it or install it globally with `npm install -g npx` (less +> preferable). + +## Data / Telemetry + +This project collects data and sends it to Microsoft to help monitor our +service performance and improve our products and services. Read the [Microsoft +Privacy Statement](https://aka.ms/csspeech/privacy) to learn more. + +To disable telemetry, you can call the following API: + +```javascript +// disable telemetry data +sdk.Recognizer.enableTelemetry(false); +``` + +This is a global setting and will disable telemetry for all recognizers +(already created or new recognizers). + +We strongly recommend you keep telemetry enabled. With telemetry enabled you +transmit information about your platform (operating system and possibly, Speech +Service relevant information like microphone characteristics, etc.), and +information about the performance of the Speech Service (the time when you did +send data and when you received data). It can be used to tune the service, +monitor service performance and stability, and might help us to analyze +reported problems. Without telemetry enabled, it is not possible for do any +form of detailed analysis in case of a support request. + +## Contributing This project welcomes contributions and suggestions. Most contributions require you to agree to a Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us diff --git a/REDIST.txt b/REDIST.txt new file mode 100644 index 00000000..06a687aa --- /dev/null +++ b/REDIST.txt @@ -0,0 +1,7 @@ +The following libraries can be redistributed if you are using the Cognitive +Services Speech SDK for JavaScript, subject to its license: + +microsoft.cognitiveservices.speech.sdk.bundle.js +microsoft-cognitiveservices-speech-sdk NPM library + +Depending on your usage, you only need to redistribute a subset of the above. diff --git a/RunTests.cmd b/RunTests.cmd new file mode 100644 index 00000000..5cb1d4ac --- /dev/null +++ b/RunTests.cmd @@ -0,0 +1,53 @@ +@REM Copyright (c) Microsoft Corporation. All rights reserved. +@REM Licensed under the MIT license. +@echo off +setlocal +if "%~1" equ "/?" goto :Usage +if "%~1" equ "-?" goto :Usage +if "%~1" equ "?" goto :Usage +if "%~1" equ "/h" goto :Usage +if "%~1" equ "-h" goto :Usage + +set TEST_SETTING_FILE_NAME=%~dp0\secrets\TestConfiguration.ts + +set TEST_SETTING_FILE_EXISTED=0 +if EXIST "%TEST_SETTING_FILE_NAME%" set TEST_SETTING_FILE_EXISTED=1 + +if "%~1" NEQ "" ( + call "%~dp0BuildTestConfig.cmd" %* || ( + echo Error creating test config. + exit /b 1 + ) +) else if %TEST_SETTING_FILE_EXISTED% EQU 0 ( + echo Warning: No test config and no parameters specified. This will probably fail. 1>&2 +) + +pushd "%~dp0" +call npm run test + +set NPM_ERROR=%ERRORLEVEL% + +if %TEST_SETTING_FILE_EXISTED% EQU 0 ( + del "%TEST_SETTING_FILE_NAME%" +) + +popd + +exit /b %NPM_ERROR% + +@REM TODO fix usage +:Usage +@echo off +echo. +echo Usage: %~n0 [^:^] +echo. +echo Writes any ^:^ pair to the test settings file for JavaScript bindings tests. +echo. +echo The file will be erased before new values are added. +echo. +echo If no values are specified, the existing file will not be modified. +echo. +echo Current settings available are: [SpeechSubscriptionKey:^] [SpeechRegion:^] [LuisSubscriptionKey:^] [LuisRegion:^] [LuidAppId:^] +echo. +echo Once settings are written, executes the JS unit tests and if the settings file was created, it will be deleted. +exit /b 1 diff --git a/_config.yml b/_config.yml new file mode 100644 index 00000000..2f7efbea --- /dev/null +++ b/_config.yml @@ -0,0 +1 @@ +theme: jekyll-theme-minimal \ No newline at end of file diff --git a/bundleApp.js b/bundleApp.js new file mode 100644 index 00000000..5e14afd3 --- /dev/null +++ b/bundleApp.js @@ -0,0 +1,5 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +window.SpeechSDK = require('./distrib/lib/microsoft.cognitiveservices.speech.sdk.js'); + diff --git a/ci/build.yml b/ci/build.yml index 8e52fe53..a7b85070 100644 --- a/ci/build.yml +++ b/ci/build.yml @@ -13,6 +13,9 @@ resources: - repo: self clean: true +variables: + SPEECHSDK_JS_ROOT: . + jobs: - job: Pre @@ -22,3 +25,8 @@ jobs: steps: - bash: ./ci/check-git-head.sh displayName: Repository checks + +- template: jsbuild.yml + parameters: + dependsOn: Pre + condition: true diff --git a/ci/jsbuild.yml b/ci/jsbuild.yml new file mode 100644 index 00000000..939a179c --- /dev/null +++ b/ci/jsbuild.yml @@ -0,0 +1,72 @@ +# +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT license. +# + +parameters: + dependsOn: '' + condition: succeeded() + +jobs: + +- job: JsBuild + dependsOn: ${{ parameters.dependsOn }} + condition: ${{ parameters.condition }} + pool: + name: Hosted VS2017 + demands: npm + timeoutInMinutes: 60 + steps: + - bash: npm ci && npm run civersion + displayName: Set version + workingDirectory: $(SPEECHSDK_JS_ROOT) + - bash: | + F=$(SPEECHSDK_JS_ROOT)/src/common.speech/RecognizerConfig.ts + [[ -f $F ]] || exit 1 + perl -i.bak -p -e 'BEGIN { $c = 0 } $c += s/(?<=const SPEECHSDK_CLIENTSDK_VERSION = ")[^"]*/$(SPEECHSDK_SEMVER2NOMETA)/g; END { die "Patched SPEECHSDK_CLIENTSDK_VERSION $c time(s), expected 1.\n" if $c != 1 }' "$F" + E=$? + rm -f "$F.bak" + git diff + exit $E + displayName: Stamp SPEECHSDK_CLIENTSDK_VERSION + - bash: npm run build && npm pack + displayName: Build and pack SDK + workingDirectory: $(SPEECHSDK_JS_ROOT) + - script: | + RunTests.cmd ^ + SpeechSubscriptionKey:$(speech-ne-s0-key1) ^ + SpeechRegion:northeurope ^ + LuisSubscriptionKey:$(luis-westus-s0-201809-key1) ^ + LuisRegion:westus ^ + SpeechTestEndpointId:ec3432b2-8584-4736-865a-556213b9f6fd + displayName: Run tests + workingDirectory: $(SPEECHSDK_JS_ROOT) + - task: PublishTestResults@2 + displayName: Publish test results + - bash: | + set -u -e -o pipefail -x + PACKAGE_BASE=microsoft-cognitiveservices-speech-sdk + PACKAGE_NAME=$PACKAGE_BASE-$SPEECHSDK_SEMVER2NOMETA.tgz + PACKAGE_IN=$(SPEECHSDK_JS_ROOT)/$PACKAGE_NAME + PACKAGE_OUT="$(Build.ArtifactStagingDirectory)/Out/JavaScript/npm" + ZIP_OUT="$(Build.ArtifactStagingDirectory)/Out/JavaScript/SpeechSDK-JavaScript-$SPEECHSDK_SEMVER2NOMETA" + mkdir -p "$PACKAGE_OUT" "$ZIP_OUT" + cp --preserve "$PACKAGE_IN" "$PACKAGE_OUT" + echo SRI hash for microsoft.cognitiveservices.speech.sdk.bundle.js: sha512-"$(openssl dgst -sha512 -binary $(SPEECHSDK_JS_ROOT)/distrib/browser/microsoft.cognitiveservices.speech.sdk.bundle.js | openssl base64 -A)" + cp --preserve $(SPEECHSDK_JS_ROOT)/LICENSE $(SPEECHSDK_JS_ROOT)/REDIST.txt $(SPEECHSDK_JS_ROOT)/distrib/browser/microsoft.cognitiveservices.speech.sdk.bundle.* $(SPEECHSDK_JS_ROOT)/distrib/browser/microsoft.cognitiveservices.speech.sdk.bundle-min.* "$ZIP_OUT" + displayName: Create drop + - task: ArchiveFiles@2 + inputs: + rootFolderOrFile: $(Build.ArtifactStagingDirectory)/Out/JavaScript/SpeechSDK-JavaScript-$(SPEECHSDK_SEMVER2NOMETA) + includeRootFolder: true + archiveType: zip + archiveFile: $(Build.ArtifactStagingDirectory)/Out/JavaScript/SpeechSDK-JavaScript-$(SPEECHSDK_SEMVER2NOMETA).zip + displayName: Create .zip + - bash: rm -rf "$(Build.ArtifactStagingDirectory)/Out/JavaScript/SpeechSDK-JavaScript-$(SPEECHSDK_SEMVER2NOMETA)" + displayName: Remove temporary directory + - task: PublishBuildArtifacts@1 + displayName: Publish drop + inputs: + PathtoPublish: '$(Build.ArtifactStagingDirectory)/Out/JavaScript' + ArtifactName: JavaScript + publishLocation: Container diff --git a/ci/version.js b/ci/version.js new file mode 100644 index 00000000..b81cac8a --- /dev/null +++ b/ci/version.js @@ -0,0 +1,83 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. +// + +(function () { + 'use strict'; + var semver = require("semver"); + var exec = require("child_process").exec; + + if (!process.env.npm_package_version) { + throw "npm_package_version not set; run this via 'npm run'" + } + + var givenVersion = process.env.npm_package_version; + + if (!semver.valid(givenVersion)) { + throw "Invalid version " + givenVersion; + } + + var baseVersion = semver.major(givenVersion) + "." + semver.minor(givenVersion) + "." + semver.patch(givenVersion); + var prerelease = semver.prerelease(givenVersion) + + var buildId = process.env.BUILD_BUILDID || "1" + + var buildType = "dev" + var inAzureDevOps = false + + if (process.env.SYSTEM_COLLECTIONID === "19422243-19b9-4d85-9ca6-bc961861d287" && + (process.env.SYSTEM_DEFINITIONID === "4833" || process.env.SYSTEM_DEFINITIONID === "7863")) { + + inAzureDevOps = true + + if (process.env.BUILD_SOURCEBRANCH.match("^refs/heads/release/")) { + buildType = "prod" + } else if (process.env.BUILD_SOURCEBRANCH === "refs/heads/master" && + (process.env.BUILD_REASON === "Schedule" || + process.env.BUILD_REASON === "Manual")) { + buildType = "int" + } + } + + // Check our version constraints + if (buildType === "prod") { + // Full version give in package.json + if (prerelease.length != 0 && + (prerelease.length != 2 || + !prerelease[0].match("^(?:alpha|beta|rc)$") || + prerelease[1] < 1)) { + throw "For prod build types, version must have no pre-release tag, or alpha / beta / rc with a positive number." + } + } else if (prerelease.length != 3 || + (prerelease[0] !== "alpha" || prerelease[1] !== 0 || prerelease[2] !== 1)) { + throw "For non-prod build types, checked-in version must end in -alpha.0.1" + } + + var versionToUse + + if (buildType === "dev") { + versionToUse = baseVersion + "-alpha.0." + buildId + } else if (buildType === "int") { + versionToUse = baseVersion + "-beta.0." + buildId + } else if (buildType === "prod") { + versionToUse = givenVersion + } else { + throw "Internal error. Unexpected build type: " + buildType + } + + if (inAzureDevOps) { + console.log("##vso[task.setvariable variable=SPEECHSDK_SEMVER2NOMETA]" + versionToUse); + } + + if (givenVersion !== versionToUse) { + console.log("Setting version to " + versionToUse); + exec("npm version --no-git-tag-version " + versionToUse, { cwd: __dirname + "/.." }, + function (error) { + if (error) { + throw error; + } + } + ); + } +}()); diff --git a/gulpfile.js b/gulpfile.js new file mode 100644 index 00000000..b2734bc9 --- /dev/null +++ b/gulpfile.js @@ -0,0 +1,62 @@ +(function () { + 'use strict'; + var gulp = require("gulp"); + var ts = require('gulp-typescript'); + var sourcemaps = require('gulp-sourcemaps'); + var tslint = require("gulp-tslint"); + var uglify = require('gulp-uglify'); + var rename = require('gulp-rename'); + var pump = require('pump'); + var webpack = require('webpack-stream'); + + gulp.task("build", function build () { + return gulp.src([ + "src/**/*.ts", + "microsoft.cognitiveservices.speech.sdk.ts"], + {base: '.'}) + .pipe(tslint({ + formatter: "prose", + configuration: "tslint.json" + })) + .pipe(tslint.report({ + summarizeFailureOutput: true + })) + .pipe(sourcemaps.init()) + .pipe(ts({ + target: "ES5", + declaration: true, + noImplicitAny: true, + removeComments: false, + outDir: 'distrib/lib' + })) + .pipe(sourcemaps.write('.')) + .pipe(gulp.dest('distrib/lib')); + }); + + gulp.task("bundle", gulp.series("build", function bundle () { + return gulp.src('bundleApp.js') + .pipe(webpack({ + output: { filename: 'microsoft.cognitiveservices.speech.sdk.bundle.js' }, + devtool: 'source-map', + module: { + rules: [{ + enforce: 'pre', + test: /\.js$/, + loader: "source-map-loader" + }] + } + })) + .pipe(gulp.dest('distrib/browser')); + })); + + gulp.task('compress', gulp.series("bundle", function(cb) { + return pump([ + gulp.src('distrib/browser/microsoft.cognitiveservices.speech.sdk.bundle.js'), + rename(function(path) { path.basename = "microsoft.cognitiveservices.speech.sdk.bundle-min"; }), + uglify(), + gulp.dest('distrib/browser') + ], + cb + ); +})); +}()); diff --git a/jest.config.js b/jest.config.js new file mode 100644 index 00000000..aec37776 --- /dev/null +++ b/jest.config.js @@ -0,0 +1,13 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. +module.exports = { + transform: { + "^.+\\.ts$": "ts-jest", + }, + testRegex: "tests/.*Tests\\.ts$", + testPathIgnorePatterns: ["/lib/", "/node_modules/", "/src/"], + moduleFileExtensions: ["ts", "js", "jsx", "json", "node"], + collectCoverage: true, + "reporters": [ "default", "jest-junit" ], + setupTestFrameworkScriptFile:"./secrets/TestConfiguration.ts" +}; diff --git a/jsdoc-conf.json b/jsdoc-conf.json new file mode 100644 index 00000000..42857c84 --- /dev/null +++ b/jsdoc-conf.json @@ -0,0 +1,35 @@ +{ + "templates": { + "applicationName": "Microsoft Cognitive Services Speech SDK", + "disqus": "", + "googleAnalytics": "", + "openGraph": { + "title": "", + "type": "website", + "image": "", + "site_name": "", + "url": "" + } + }, + "meta": { + "title": "Microsoft Cognitive Services Speech SDK", + "description": "Microsoft Cognitive Services Speech SDK", + "keyword": "" + }, + "linenums": true, + "source": { + "include": [ + "./distrib/src/sdk" + ], + "includePattern": ".+\\.js(doc)?$", + "excludePattern": "(^|\\/|\\\\)_" + }, + "opts": { + "encoding": "utf8", + "recurse": true, + "private": false, + "lenient": true, + "destination": "./outjs", + "template": "templates/default" + } +} diff --git a/microsoft.cognitiveservices.speech.sdk.ts b/microsoft.cognitiveservices.speech.sdk.ts new file mode 100644 index 00000000..29340711 --- /dev/null +++ b/microsoft.cognitiveservices.speech.sdk.ts @@ -0,0 +1,11 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. +import { ConsoleLoggingListener, LocalStorage, SessionStorage } from "./src/common.browser/Exports"; +import { Events, Storage } from "./src/common/Exports"; + +// Common.Storage.SetLocalStorage(new Common.Browser.LocalStorage()); +// Common.Storage.SetSessionStorage(new Common.Browser.SessionStorage()); +Events.instance.attachListener(new ConsoleLoggingListener()); + +// Speech SDK API +export * from "./src/sdk/Exports"; diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 00000000..7fca81dd --- /dev/null +++ b/package-lock.json @@ -0,0 +1,11871 @@ +{ + "name": "microsoft-cognitiveservices-speech-sdk", + "version": "1.2.0-alpha.0.1", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "@babel/code-frame": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.0.0.tgz", + "integrity": "sha512-OfC2uemaknXr87bdLUkWog7nYuliM9Ij5HUcajsVcMCpQrcLmtxRbVFTIqmcSkSeYRBFBRxs2FiUqFJDLdiebA==", + "dev": true, + "requires": { + "@babel/highlight": "7.0.0" + } + }, + "@babel/highlight": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.0.0.tgz", + "integrity": "sha512-UFMC4ZeFC48Tpvj7C8UgLvtkaUuovQX+5xNWrsIoMG8o2z+XFKjKaN9iVmS84dPwVN00W4wPmqvYoZF3EGAsfw==", + "dev": true, + "requires": { + "chalk": "2.4.1", + "esutils": "2.0.2", + "js-tokens": "4.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "1.9.1" + } + }, + "chalk": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", + "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", + "dev": true, + "requires": { + "ansi-styles": "3.2.1", + "escape-string-regexp": "1.0.5", + "supports-color": "5.5.0" + } + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "3.0.0" + } + } + } + }, + "@gulp-sourcemaps/identity-map": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@gulp-sourcemaps/identity-map/-/identity-map-1.0.2.tgz", + "integrity": "sha512-ciiioYMLdo16ShmfHBXJBOFm3xPC4AuwO4xeRpFeHz7WK9PYsWCmigagG2XyzZpubK4a3qNKoUBDhbzHfa50LQ==", + "dev": true, + "requires": { + "acorn": "5.2.1", + "css": "2.2.4", + "normalize-path": "2.1.1", + "source-map": "0.6.1", + "through2": "2.0.3" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } + } + }, + "@gulp-sourcemaps/map-sources": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@gulp-sourcemaps/map-sources/-/map-sources-1.0.0.tgz", + "integrity": "sha1-iQrnxdjId/bThIYCFazp1+yUW9o=", + "dev": true, + "requires": { + "normalize-path": "2.1.1", + "through2": "2.0.3" + } + }, + "@types/events": { + "version": "1.2.0", + "resolved": "http://registry.npmjs.org/@types/events/-/events-1.2.0.tgz", + "integrity": "sha512-KEIlhXnIutzKwRbQkGWb/I4HFqBuUykAdHgDED6xqwXJfONCjF5VoE0cXEiurh3XauygxzeDzgtXUqvLkxFzzA==" + }, + "@types/jest": { + "version": "23.3.10", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-23.3.10.tgz", + "integrity": "sha512-DC8xTuW/6TYgvEg3HEXS7cu9OijFqprVDXXiOcdOKZCU/5PJNLZU37VVvmZHdtMiGOa8wAA/We+JzbdxFzQTRQ==", + "dev": true + }, + "@types/node": { + "version": "10.12.12", + "resolved": "https://registry.npmjs.org/@types/node/-/node-10.12.12.tgz", + "integrity": "sha512-Pr+6JRiKkfsFvmU/LK68oBRCQeEg36TyAbPhc2xpez24OOZZCuoIhWGTd39VZy6nGafSbxzGouFPTFD/rR1A0A==" + }, + "@types/ws": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-6.0.1.tgz", + "integrity": "sha512-EzH8k1gyZ4xih/MaZTXwT2xOkPiIMSrhQ9b8wrlX88L0T02eYsddatQlwVFlEPyEqV0ChpdpNnE51QPH6NVT4Q==", + "requires": { + "@types/events": "1.2.0", + "@types/node": "10.12.12" + } + }, + "abab": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.0.tgz", + "integrity": "sha512-sY5AXXVZv4Y1VACTtR11UJCPHHudgY5i26Qj5TypE6DKlIApbwb5uqhXcJ5UUGbvZNRh7EeIoW+LrJumBsKp7w==", + "dev": true + }, + "acorn": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.2.1.tgz", + "integrity": "sha512-jG0u7c4Ly+3QkkW18V+NRDN+4bWHdln30NL1ZL2AvFZZmQe/BfopYCtghCKKVBUSetZ4QKcyA0pY6/4Gw8Pv8w==", + "dev": true + }, + "acorn-dynamic-import": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/acorn-dynamic-import/-/acorn-dynamic-import-2.0.2.tgz", + "integrity": "sha1-x1K9IQvvZ5UBtsbLf8hPj0cVjMQ=", + "dev": true, + "requires": { + "acorn": "4.0.13" + }, + "dependencies": { + "acorn": { + "version": "4.0.13", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-4.0.13.tgz", + "integrity": "sha1-EFSVrlNh1pe9GVyCUZLhrX8lN4c=", + "dev": true + } + } + }, + "acorn-globals": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-4.3.0.tgz", + "integrity": "sha512-hMtHj3s5RnuhvHPowpBYvJVj3rAar82JiDQHvGs1zO0l10ocX/xEdBShNHTJaboucJUsScghp74pH3s7EnHHQw==", + "dev": true, + "requires": { + "acorn": "6.0.4", + "acorn-walk": "6.1.1" + }, + "dependencies": { + "acorn": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.0.4.tgz", + "integrity": "sha512-VY4i5EKSKkofY2I+6QLTbTTN/UvEQPCo6eiwzzSaSWfpaDhOmStMCMod6wmuPciNq+XS0faCglFu2lHZpdHUtg==", + "dev": true + } + } + }, + "acorn-walk": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-6.1.1.tgz", + "integrity": "sha512-OtUw6JUTgxA2QoqqmrmQ7F2NYqiBPi/L2jqHyFtllhOUvXYQXf0Z1CYUinIfyT4bTCGmrA7gX9FvHA81uzCoVw==", + "dev": true + }, + "ajv": { + "version": "6.6.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.6.1.tgz", + "integrity": "sha512-ZoJjft5B+EJBjUyu9C9Hc0OZyPZSSlOF+plzouTrg6UlA8f+e/n8NIgBFG/9tppJtpPWfthHakK7juJdNDODww==", + "dev": true, + "requires": { + "fast-deep-equal": "2.0.1", + "fast-json-stable-stringify": "2.0.0", + "json-schema-traverse": "0.4.1", + "uri-js": "4.2.2" + } + }, + "align-text": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/align-text/-/align-text-0.1.4.tgz", + "integrity": "sha1-DNkKVhCT810KmSVsIrcGlDP60Rc=", + "dev": true, + "requires": { + "kind-of": "3.2.2", + "longest": "1.0.1", + "repeat-string": "1.6.1" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "1.1.6" + } + } + } + }, + "ansi-colors": { + "version": "1.1.0", + "resolved": "http://registry.npmjs.org/ansi-colors/-/ansi-colors-1.1.0.tgz", + "integrity": "sha512-SFKX67auSNoVR38N3L+nvsPjOE0bybKTYbkf5tRvushrAPQ9V75huw0ZxBkKVeRU9kqH3d6HA4xTckbwZ4ixmA==", + "dev": true, + "requires": { + "ansi-wrap": "0.1.0" + } + }, + "ansi-escapes": { + "version": "3.1.0", + "resolved": "http://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.1.0.tgz", + "integrity": "sha512-UgAb8H9D41AQnu/PbWlCofQVcnV4Gs2bBJi9eZPxfU/hgglFh3SMDMENRIqdr7H6XFnXdoknctFByVsCOotTVw==", + "dev": true + }, + "ansi-gray": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/ansi-gray/-/ansi-gray-0.1.1.tgz", + "integrity": "sha1-KWLPVOyXksSFEKPetSRDaGHvclE=", + "dev": true, + "requires": { + "ansi-wrap": "0.1.0" + } + }, + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true + }, + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", + "dev": true + }, + "ansi-wrap": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/ansi-wrap/-/ansi-wrap-0.1.0.tgz", + "integrity": "sha1-qCJQ3bABXponyoLoLqYDu/pF768=", + "dev": true + }, + "anymatch": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", + "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", + "dev": true, + "requires": { + "micromatch": "3.1.10", + "normalize-path": "2.1.1" + }, + "dependencies": { + "array-unique": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", + "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", + "dev": true + }, + "braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "dev": true, + "requires": { + "arr-flatten": "1.1.0", + "array-unique": "0.3.2", + "extend-shallow": "2.0.1", + "fill-range": "4.0.0", + "isobject": "3.0.1", + "repeat-element": "1.1.2", + "snapdragon": "0.8.2", + "snapdragon-node": "2.1.1", + "split-string": "3.1.0", + "to-regex": "3.0.2" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "0.1.1" + } + } + } + }, + "expand-brackets": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", + "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", + "dev": true, + "requires": { + "debug": "2.6.9", + "define-property": "0.2.5", + "extend-shallow": "2.0.1", + "posix-character-classes": "0.1.1", + "regex-not": "1.0.2", + "snapdragon": "0.8.2", + "to-regex": "3.0.2" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "0.1.6" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "0.1.1" + } + }, + "is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", + "dev": true, + "requires": { + "kind-of": "3.2.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "1.1.6" + } + } + } + }, + "is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", + "dev": true, + "requires": { + "kind-of": "3.2.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "1.1.6" + } + } + } + }, + "is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "0.1.6", + "is-data-descriptor": "0.1.4", + "kind-of": "5.1.0" + } + }, + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "dev": true + } + } + }, + "extglob": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", + "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", + "dev": true, + "requires": { + "array-unique": "0.3.2", + "define-property": "1.0.0", + "expand-brackets": "2.1.4", + "extend-shallow": "2.0.1", + "fragment-cache": "0.2.1", + "regex-not": "1.0.2", + "snapdragon": "0.8.2", + "to-regex": "3.0.2" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "requires": { + "is-descriptor": "1.0.2" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "0.1.1" + } + } + } + }, + "fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", + "dev": true, + "requires": { + "extend-shallow": "2.0.1", + "is-number": "3.0.0", + "repeat-string": "1.6.1", + "to-regex-range": "2.1.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "0.1.1" + } + } + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "requires": { + "kind-of": "6.0.2" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "requires": { + "kind-of": "6.0.2" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "1.0.0", + "is-data-descriptor": "1.0.0", + "kind-of": "6.0.2" + } + }, + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "dev": true, + "requires": { + "kind-of": "3.2.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "1.1.6" + } + } + } + }, + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true + }, + "micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "dev": true, + "requires": { + "arr-diff": "4.0.0", + "array-unique": "0.3.2", + "braces": "2.3.2", + "define-property": "2.0.2", + "extend-shallow": "3.0.2", + "extglob": "2.0.4", + "fragment-cache": "0.2.1", + "kind-of": "6.0.2", + "nanomatch": "1.2.13", + "object.pick": "1.3.0", + "regex-not": "1.0.2", + "snapdragon": "0.8.2", + "to-regex": "3.0.2" + } + } + } + }, + "append-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/append-buffer/-/append-buffer-1.0.2.tgz", + "integrity": "sha1-2CIM9GYIFSXv6lBhTz3mUU36WPE=", + "dev": true, + "requires": { + "buffer-equal": "1.0.0" + } + }, + "append-transform": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/append-transform/-/append-transform-0.4.0.tgz", + "integrity": "sha1-126/jKlNJ24keja61EpLdKthGZE=", + "dev": true, + "requires": { + "default-require-extensions": "1.0.0" + } + }, + "archy": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz", + "integrity": "sha1-+cjBN1fMHde8N5rHeyxipcKGjEA=", + "dev": true + }, + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "requires": { + "sprintf-js": "1.0.3" + } + }, + "arr-diff": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", + "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=", + "dev": true + }, + "arr-filter": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/arr-filter/-/arr-filter-1.1.2.tgz", + "integrity": "sha1-Q/3d0JHo7xGqTEXZzcGOLf8XEe4=", + "dev": true, + "requires": { + "make-iterator": "1.0.1" + } + }, + "arr-flatten": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", + "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==", + "dev": true + }, + "arr-map": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/arr-map/-/arr-map-2.0.2.tgz", + "integrity": "sha1-Onc0X/wc814qkYJWAfnljy4kysQ=", + "dev": true, + "requires": { + "make-iterator": "1.0.1" + } + }, + "arr-union": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", + "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=", + "dev": true + }, + "array-differ": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/array-differ/-/array-differ-1.0.0.tgz", + "integrity": "sha1-7/UuN1gknTO+QCuLuOVkuytdQDE=", + "dev": true + }, + "array-each": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/array-each/-/array-each-1.0.1.tgz", + "integrity": "sha1-p5SvDAWrF1KEbudTofIRoFugxE8=", + "dev": true + }, + "array-equal": { + "version": "1.0.0", + "resolved": "http://registry.npmjs.org/array-equal/-/array-equal-1.0.0.tgz", + "integrity": "sha1-jCpe8kcv2ep0KwTHenUJO6J1fJM=", + "dev": true + }, + "array-find-index": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-find-index/-/array-find-index-1.0.2.tgz", + "integrity": "sha1-3wEKoSh+Fku9pvlyOwqWoexBh6E=", + "dev": true + }, + "array-initial": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/array-initial/-/array-initial-1.1.0.tgz", + "integrity": "sha1-L6dLJnOTccOUe9enrcc74zSz15U=", + "dev": true, + "requires": { + "array-slice": "1.1.0", + "is-number": "4.0.0" + }, + "dependencies": { + "is-number": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-4.0.0.tgz", + "integrity": "sha512-rSklcAIlf1OmFdyAqbnWTLVelsQ58uvZ66S/ZyawjWqIviTWCjg2PzVGw8WUA+nNuPTqb4wgA+NszrJ+08LlgQ==", + "dev": true + } + } + }, + "array-last": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/array-last/-/array-last-1.3.0.tgz", + "integrity": "sha512-eOCut5rXlI6aCOS7Z7kCplKRKyiFQ6dHFBem4PwlwKeNFk2/XxTrhRh5T9PyaEWGy/NHTZWbY+nsZlNFJu9rYg==", + "dev": true, + "requires": { + "is-number": "4.0.0" + }, + "dependencies": { + "is-number": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-4.0.0.tgz", + "integrity": "sha512-rSklcAIlf1OmFdyAqbnWTLVelsQ58uvZ66S/ZyawjWqIviTWCjg2PzVGw8WUA+nNuPTqb4wgA+NszrJ+08LlgQ==", + "dev": true + } + } + }, + "array-slice": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/array-slice/-/array-slice-1.1.0.tgz", + "integrity": "sha512-B1qMD3RBP7O8o0H2KbrXDyB0IccejMF15+87Lvlor12ONPRHP6gTjXMNkt/d3ZuOGbAe66hFmaCfECI24Ufp6w==", + "dev": true + }, + "array-sort": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/array-sort/-/array-sort-1.0.0.tgz", + "integrity": "sha512-ihLeJkonmdiAsD7vpgN3CRcx2J2S0TiYW+IS/5zHBI7mKUq3ySvBdzzBfD236ubDBQFiiyG3SWCPc+msQ9KoYg==", + "dev": true, + "requires": { + "default-compare": "1.0.0", + "get-value": "2.0.6", + "kind-of": "5.1.0" + }, + "dependencies": { + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "dev": true + } + } + }, + "array-uniq": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", + "integrity": "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=", + "dev": true + }, + "array-unique": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.2.1.tgz", + "integrity": "sha1-odl8yvy8JiXMcPrc6zalDFiwGlM=", + "dev": true + }, + "arrify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", + "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=", + "dev": true + }, + "asn1": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", + "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", + "dev": true, + "requires": { + "safer-buffer": "2.1.2" + } + }, + "asn1.js": { + "version": "4.9.2", + "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-4.9.2.tgz", + "integrity": "sha512-b/OsSjvWEo8Pi8H0zsDd2P6Uqo2TK2pH8gNLSJtNLM2Db0v2QaAZ0pBQJXVjAn4gBuugeVDr7s63ZogpUIwWDg==", + "dev": true, + "requires": { + "bn.js": "4.11.8", + "inherits": "2.0.3", + "minimalistic-assert": "1.0.0" + } + }, + "assert": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/assert/-/assert-1.4.1.tgz", + "integrity": "sha1-mZEtWRg2tab1s0XA8H7vwI/GXZE=", + "dev": true, + "requires": { + "util": "0.10.3" + } + }, + "assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", + "dev": true + }, + "assign-symbols": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", + "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=", + "dev": true + }, + "astral-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz", + "integrity": "sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==", + "dev": true + }, + "async": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.0.tgz", + "integrity": "sha512-xAfGg1/NTLBBKlHFmnd7PlmUW9KhVQIUuSrYem9xzFUZy13ScvtyGGejaae9iAVRiRq9+Cx7DPFaAAhCpyxyPw==", + "dev": true, + "requires": { + "lodash": "4.17.11" + } + }, + "async-done": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/async-done/-/async-done-1.3.1.tgz", + "integrity": "sha512-R1BaUeJ4PMoLNJuk+0tLJgjmEqVsdN118+Z8O+alhnQDQgy0kmD5Mqi0DNEmMx2LM0Ed5yekKu+ZXYvIHceicg==", + "dev": true, + "requires": { + "end-of-stream": "1.4.0", + "once": "1.4.0", + "process-nextick-args": "1.0.7", + "stream-exhaust": "1.0.2" + } + }, + "async-each": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.1.tgz", + "integrity": "sha1-GdOGodntxufByF04iu28xW0zYC0=", + "dev": true + }, + "async-limiter": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.0.tgz", + "integrity": "sha512-jp/uFnooOiO+L211eZOoSyzpOITMXx1rBITauYykG3BRYPu8h0UcxsPNB04RR5vo4Tyz3+ay17tR6JVf9qzYWg==" + }, + "async-settle": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/async-settle/-/async-settle-1.0.0.tgz", + "integrity": "sha1-HQqRS7Aldb7IqPOnTlCA9yssDGs=", + "dev": true, + "requires": { + "async-done": "1.3.1" + } + }, + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", + "dev": true + }, + "atob": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", + "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", + "dev": true + }, + "aws-sign2": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", + "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=", + "dev": true + }, + "aws4": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.8.0.tgz", + "integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==", + "dev": true + }, + "babel-code-frame": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz", + "integrity": "sha1-Y/1D99weO7fONZR9uP42mj9Yx0s=", + "dev": true, + "requires": { + "chalk": "1.1.3", + "esutils": "2.0.2", + "js-tokens": "3.0.2" + } + }, + "babel-core": { + "version": "6.26.3", + "resolved": "https://registry.npmjs.org/babel-core/-/babel-core-6.26.3.tgz", + "integrity": "sha512-6jyFLuDmeidKmUEb3NM+/yawG0M2bDZ9Z1qbZP59cyHLz8kYGKYwpJP0UwUKKUiTRNvxfLesJnTedqczP7cTDA==", + "dev": true, + "requires": { + "babel-code-frame": "6.26.0", + "babel-generator": "6.26.1", + "babel-helpers": "6.24.1", + "babel-messages": "6.23.0", + "babel-register": "6.26.0", + "babel-runtime": "6.26.0", + "babel-template": "6.26.0", + "babel-traverse": "6.26.0", + "babel-types": "6.26.0", + "babylon": "6.18.0", + "convert-source-map": "1.5.1", + "debug": "2.6.9", + "json5": "0.5.1", + "lodash": "4.17.11", + "minimatch": "3.0.4", + "path-is-absolute": "1.0.1", + "private": "0.1.8", + "slash": "1.0.0", + "source-map": "0.5.7" + } + }, + "babel-generator": { + "version": "6.26.1", + "resolved": "https://registry.npmjs.org/babel-generator/-/babel-generator-6.26.1.tgz", + "integrity": "sha512-HyfwY6ApZj7BYTcJURpM5tznulaBvyio7/0d4zFOeMPUmfxkCjHocCuoLa2SAGzBI8AREcH3eP3758F672DppA==", + "dev": true, + "requires": { + "babel-messages": "6.23.0", + "babel-runtime": "6.26.0", + "babel-types": "6.26.0", + "detect-indent": "4.0.0", + "jsesc": "1.3.0", + "lodash": "4.17.11", + "source-map": "0.5.7", + "trim-right": "1.0.1" + } + }, + "babel-helpers": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-helpers/-/babel-helpers-6.24.1.tgz", + "integrity": "sha1-NHHenK7DiOXIUOWX5Yom3fN2ArI=", + "dev": true, + "requires": { + "babel-runtime": "6.26.0", + "babel-template": "6.26.0" + } + }, + "babel-jest": { + "version": "23.6.0", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-23.6.0.tgz", + "integrity": "sha512-lqKGG6LYXYu+DQh/slrQ8nxXQkEkhugdXsU6St7GmhVS7Ilc/22ArwqXNJrf0QaOBjZB0360qZMwXqDYQHXaew==", + "dev": true, + "requires": { + "babel-plugin-istanbul": "4.1.6", + "babel-preset-jest": "23.2.0" + } + }, + "babel-messages": { + "version": "6.23.0", + "resolved": "https://registry.npmjs.org/babel-messages/-/babel-messages-6.23.0.tgz", + "integrity": "sha1-8830cDhYA1sqKVHG7F7fbGLyYw4=", + "dev": true, + "requires": { + "babel-runtime": "6.26.0" + } + }, + "babel-plugin-istanbul": { + "version": "4.1.6", + "resolved": "http://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-4.1.6.tgz", + "integrity": "sha512-PWP9FQ1AhZhS01T/4qLSKoHGY/xvkZdVBGlKM/HuxxS3+sC66HhTNR7+MpbO/so/cz/wY94MeSWJuP1hXIPfwQ==", + "dev": true, + "requires": { + "babel-plugin-syntax-object-rest-spread": "6.13.0", + "find-up": "2.1.0", + "istanbul-lib-instrument": "1.10.2", + "test-exclude": "4.2.3" + }, + "dependencies": { + "find-up": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", + "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", + "dev": true, + "requires": { + "locate-path": "2.0.0" + } + } + } + }, + "babel-plugin-jest-hoist": { + "version": "23.2.0", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-23.2.0.tgz", + "integrity": "sha1-5h+uBaHKiAGq3uV6bWa4zvr0QWc=", + "dev": true + }, + "babel-plugin-syntax-object-rest-spread": { + "version": "6.13.0", + "resolved": "http://registry.npmjs.org/babel-plugin-syntax-object-rest-spread/-/babel-plugin-syntax-object-rest-spread-6.13.0.tgz", + "integrity": "sha1-/WU28rzhODb/o6VFjEkDpZe7O/U=", + "dev": true + }, + "babel-preset-jest": { + "version": "23.2.0", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-23.2.0.tgz", + "integrity": "sha1-jsegOhOPABoaj7HoETZSvxpV2kY=", + "dev": true, + "requires": { + "babel-plugin-jest-hoist": "23.2.0", + "babel-plugin-syntax-object-rest-spread": "6.13.0" + } + }, + "babel-register": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-register/-/babel-register-6.26.0.tgz", + "integrity": "sha1-btAhFz4vy0htestFxgCahW9kcHE=", + "dev": true, + "requires": { + "babel-core": "6.26.3", + "babel-runtime": "6.26.0", + "core-js": "2.5.7", + "home-or-tmp": "2.0.0", + "lodash": "4.17.11", + "mkdirp": "0.5.1", + "source-map-support": "0.4.18" + } + }, + "babel-runtime": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", + "dev": true, + "requires": { + "core-js": "2.5.7", + "regenerator-runtime": "0.11.1" + } + }, + "babel-template": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-template/-/babel-template-6.26.0.tgz", + "integrity": "sha1-3gPi0WOWsGn0bdn/+FIfsaDjXgI=", + "dev": true, + "requires": { + "babel-runtime": "6.26.0", + "babel-traverse": "6.26.0", + "babel-types": "6.26.0", + "babylon": "6.18.0", + "lodash": "4.17.11" + } + }, + "babel-traverse": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-traverse/-/babel-traverse-6.26.0.tgz", + "integrity": "sha1-RqnL1+3MYsjlwGTi0tjQ9ANXZu4=", + "dev": true, + "requires": { + "babel-code-frame": "6.26.0", + "babel-messages": "6.23.0", + "babel-runtime": "6.26.0", + "babel-types": "6.26.0", + "babylon": "6.18.0", + "debug": "2.6.9", + "globals": "9.18.0", + "invariant": "2.2.4", + "lodash": "4.17.11" + } + }, + "babel-types": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-types/-/babel-types-6.26.0.tgz", + "integrity": "sha1-o7Bz+Uq0nrb6Vc1lInozQ4BjJJc=", + "dev": true, + "requires": { + "babel-runtime": "6.26.0", + "esutils": "2.0.2", + "lodash": "4.17.11", + "to-fast-properties": "1.0.3" + } + }, + "babylon": { + "version": "6.18.0", + "resolved": "https://registry.npmjs.org/babylon/-/babylon-6.18.0.tgz", + "integrity": "sha512-q/UEjfGJ2Cm3oKV71DJz9d25TPnq5rhBVL2Q4fA5wcC3jcrdn7+SssEybFIxwAvvP+YCsCYNKughoF33GxgycQ==", + "dev": true + }, + "bach": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/bach/-/bach-1.2.0.tgz", + "integrity": "sha1-Szzpa/JxNPeaG0FKUcFONMO9mIA=", + "dev": true, + "requires": { + "arr-filter": "1.1.2", + "arr-flatten": "1.1.0", + "arr-map": "2.0.2", + "array-each": "1.0.1", + "array-initial": "1.1.0", + "array-last": "1.3.0", + "async-done": "1.3.1", + "async-settle": "1.0.0", + "now-and-later": "2.0.0" + } + }, + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "dev": true + }, + "base": { + "version": "0.11.2", + "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz", + "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==", + "dev": true, + "requires": { + "cache-base": "1.0.1", + "class-utils": "0.3.6", + "component-emitter": "1.2.1", + "define-property": "1.0.0", + "isobject": "3.0.1", + "mixin-deep": "1.3.1", + "pascalcase": "0.1.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "requires": { + "is-descriptor": "1.0.2" + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "requires": { + "kind-of": "6.0.2" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "requires": { + "kind-of": "6.0.2" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "1.0.0", + "is-data-descriptor": "1.0.0", + "kind-of": "6.0.2" + } + }, + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true + } + } + }, + "base64-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.2.1.tgz", + "integrity": "sha512-dwVUVIXsBZXwTuwnXI9RK8sBmgq09NDHzyR9SAph9eqk76gKK2JSQmZARC2zRC81JC2QTtxD0ARU5qTS25gIGw==", + "dev": true + }, + "bcrypt-pbkdf": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", + "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", + "dev": true, + "requires": { + "tweetnacl": "0.14.5" + } + }, + "beeper": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/beeper/-/beeper-1.1.1.tgz", + "integrity": "sha1-5tXqjF2tABMEpwsiY4RH9pyy+Ak=", + "dev": true + }, + "big.js": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/big.js/-/big.js-3.2.0.tgz", + "integrity": "sha512-+hN/Zh2D08Mx65pZ/4g5bsmNiZUuChDiQfTUQ7qJr4/kuopCr88xZsAXv6mBoZEsUI4OuGHlX59qE94K2mMW8Q==", + "dev": true + }, + "binary-extensions": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.11.0.tgz", + "integrity": "sha1-RqoXUftqL5PuXmibsQh9SxTGwgU=", + "dev": true + }, + "bn.js": { + "version": "4.11.8", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.8.tgz", + "integrity": "sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA==", + "dev": true + }, + "brace-expansion": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.8.tgz", + "integrity": "sha1-wHshHHyVLsH479Uad+8NHTmQopI=", + "dev": true, + "requires": { + "balanced-match": "1.0.0", + "concat-map": "0.0.1" + } + }, + "braces": { + "version": "1.8.5", + "resolved": "https://registry.npmjs.org/braces/-/braces-1.8.5.tgz", + "integrity": "sha1-uneWLhLf+WnWt2cR6RS3N4V79qc=", + "dev": true, + "requires": { + "expand-range": "1.8.2", + "preserve": "0.2.0", + "repeat-element": "1.1.2" + } + }, + "brorand": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", + "integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=", + "dev": true + }, + "browser-process-hrtime": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-0.1.3.tgz", + "integrity": "sha512-bRFnI4NnjO6cnyLmOV/7PVoDEMJChlcfN0z4s1YMBY989/SvlfMI1lgCnkFUs53e9gQF+w7qu7XdllSTiSl8Aw==", + "dev": true + }, + "browser-resolve": { + "version": "1.11.3", + "resolved": "https://registry.npmjs.org/browser-resolve/-/browser-resolve-1.11.3.tgz", + "integrity": "sha512-exDi1BYWB/6raKHmDTCicQfTkqwN5fioMFV4j8BsfMU4R2DK/QfZfK7kOVkmWCNANf0snkBzqGqAJBao9gZMdQ==", + "dev": true, + "requires": { + "resolve": "1.1.7" + }, + "dependencies": { + "resolve": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.1.7.tgz", + "integrity": "sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs=", + "dev": true + } + } + }, + "browserify-aes": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.1.1.tgz", + "integrity": "sha512-UGnTYAnB2a3YuYKIRy1/4FB2HdM866E0qC46JXvVTYKlBlZlnvfpSfY6OKfXZAkv70eJ2a1SqzpAo5CRhZGDFg==", + "dev": true, + "requires": { + "buffer-xor": "1.0.3", + "cipher-base": "1.0.4", + "create-hash": "1.1.3", + "evp_bytestokey": "1.0.3", + "inherits": "2.0.3", + "safe-buffer": "5.1.1" + } + }, + "browserify-cipher": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/browserify-cipher/-/browserify-cipher-1.0.0.tgz", + "integrity": "sha1-mYgkSHS/XtTijalWZtzWasj8Njo=", + "dev": true, + "requires": { + "browserify-aes": "1.1.1", + "browserify-des": "1.0.0", + "evp_bytestokey": "1.0.3" + } + }, + "browserify-des": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/browserify-des/-/browserify-des-1.0.0.tgz", + "integrity": "sha1-2qJ3cXRwki7S/hhZQRihdUOXId0=", + "dev": true, + "requires": { + "cipher-base": "1.0.4", + "des.js": "1.0.0", + "inherits": "2.0.3" + } + }, + "browserify-rsa": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.0.1.tgz", + "integrity": "sha1-IeCr+vbyApzy+vsTNWenAdQTVSQ=", + "dev": true, + "requires": { + "bn.js": "4.11.8", + "randombytes": "2.0.5" + } + }, + "browserify-sign": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.0.4.tgz", + "integrity": "sha1-qk62jl17ZYuqa/alfmMMvXqT0pg=", + "dev": true, + "requires": { + "bn.js": "4.11.8", + "browserify-rsa": "4.0.1", + "create-hash": "1.1.3", + "create-hmac": "1.1.6", + "elliptic": "6.4.0", + "inherits": "2.0.3", + "parse-asn1": "5.1.0" + } + }, + "browserify-zlib": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/browserify-zlib/-/browserify-zlib-0.2.0.tgz", + "integrity": "sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA==", + "dev": true, + "requires": { + "pako": "1.0.6" + } + }, + "bs-logger": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/bs-logger/-/bs-logger-0.2.6.tgz", + "integrity": "sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==", + "dev": true, + "requires": { + "fast-json-stable-stringify": "2.0.0" + } + }, + "bser": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/bser/-/bser-2.0.0.tgz", + "integrity": "sha1-mseNPtXZFYBP2HrLFYvHlxR6Fxk=", + "dev": true, + "requires": { + "node-int64": "0.4.0" + } + }, + "buffer": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.1.tgz", + "integrity": "sha1-bRu2AbB6TvztlwlBMgkwJ8lbwpg=", + "dev": true, + "requires": { + "base64-js": "1.2.1", + "ieee754": "1.1.8", + "isarray": "1.0.0" + } + }, + "buffer-equal": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/buffer-equal/-/buffer-equal-1.0.0.tgz", + "integrity": "sha1-WWFrSYME1Var1GaWayLu2j7KX74=", + "dev": true + }, + "buffer-from": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", + "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", + "dev": true + }, + "buffer-xor": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz", + "integrity": "sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk=", + "dev": true + }, + "builtin-modules": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz", + "integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=", + "dev": true + }, + "builtin-status-codes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz", + "integrity": "sha1-hZgoeOIbmOHGZCXgPQF0eI9Wnug=", + "dev": true + }, + "cache-base": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", + "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==", + "dev": true, + "requires": { + "collection-visit": "1.0.0", + "component-emitter": "1.2.1", + "get-value": "2.0.6", + "has-value": "1.0.0", + "isobject": "3.0.1", + "set-value": "2.0.0", + "to-object-path": "0.3.0", + "union-value": "1.0.0", + "unset-value": "1.0.0" + }, + "dependencies": { + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true + } + } + }, + "callsites": { + "version": "2.0.0", + "resolved": "http://registry.npmjs.org/callsites/-/callsites-2.0.0.tgz", + "integrity": "sha1-BuuE8A7qQT2oav/vrL/7Ngk7PFA=", + "dev": true + }, + "camelcase": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-3.0.0.tgz", + "integrity": "sha1-MvxLn82vhF/N9+c7uXysImHwqwo=", + "dev": true + }, + "camelcase-keys": { + "version": "2.1.0", + "resolved": "http://registry.npmjs.org/camelcase-keys/-/camelcase-keys-2.1.0.tgz", + "integrity": "sha1-MIvur/3ygRkFHvodkyITyRuPkuc=", + "dev": true, + "requires": { + "camelcase": "2.1.1", + "map-obj": "1.0.1" + }, + "dependencies": { + "camelcase": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-2.1.1.tgz", + "integrity": "sha1-fB0W1nmhu+WcoCys7PsBHiAfWh8=", + "dev": true + } + } + }, + "capture-exit": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/capture-exit/-/capture-exit-1.2.0.tgz", + "integrity": "sha1-HF/MSJ/QqwDU8ax64QcuMXP7q28=", + "dev": true, + "requires": { + "rsvp": "3.6.2" + } + }, + "caseless": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=", + "dev": true + }, + "center-align": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/center-align/-/center-align-0.1.3.tgz", + "integrity": "sha1-qg0yYptu6XIgBBHL1EYckHvCt60=", + "dev": true, + "requires": { + "align-text": "0.1.4", + "lazy-cache": "1.0.4" + } + }, + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "dev": true, + "requires": { + "ansi-styles": "2.2.1", + "escape-string-regexp": "1.0.5", + "has-ansi": "2.0.0", + "strip-ansi": "3.0.1", + "supports-color": "2.0.0" + } + }, + "ci-info": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-1.6.0.tgz", + "integrity": "sha512-vsGdkwSCDpWmP80ncATX7iea5DWQemg1UgCW5J8tqjU3lYw4FBYuj89J0CTVomA7BEfvSZd84GmHko+MxFQU2A==", + "dev": true + }, + "cipher-base": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz", + "integrity": "sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==", + "dev": true, + "requires": { + "inherits": "2.0.3", + "safe-buffer": "5.1.1" + } + }, + "class-utils": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", + "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==", + "dev": true, + "requires": { + "arr-union": "3.1.0", + "define-property": "0.2.5", + "isobject": "3.0.1", + "static-extend": "0.1.2" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "0.1.6" + } + }, + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true + } + } + }, + "cliui": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-2.1.0.tgz", + "integrity": "sha1-S0dXYP+AJkx2LDoXGQMukcf+oNE=", + "dev": true, + "requires": { + "center-align": "0.1.3", + "right-align": "0.1.3", + "wordwrap": "0.0.2" + } + }, + "clone": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.3.tgz", + "integrity": "sha1-KY1+IjFmD0DAA8LtMUDezz9TCF8=", + "dev": true + }, + "clone-buffer": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/clone-buffer/-/clone-buffer-1.0.0.tgz", + "integrity": "sha1-4+JbIHrE5wGvch4staFnksrD3Fg=", + "dev": true + }, + "clone-stats": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/clone-stats/-/clone-stats-0.0.1.tgz", + "integrity": "sha1-uI+UqCzzi4eR1YBG6kAprYjKmdE=", + "dev": true + }, + "cloneable-readable": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/cloneable-readable/-/cloneable-readable-1.0.0.tgz", + "integrity": "sha1-pikNQT8hemEjL5XkWP84QYz7ARc=", + "dev": true, + "requires": { + "inherits": "2.0.3", + "process-nextick-args": "1.0.7", + "through2": "2.0.3" + } + }, + "co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=", + "dev": true + }, + "code-point-at": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", + "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", + "dev": true + }, + "collection-map": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/collection-map/-/collection-map-1.0.0.tgz", + "integrity": "sha1-rqDwb40mx4DCt1SUOFVEsiVa8Yw=", + "dev": true, + "requires": { + "arr-map": "2.0.2", + "for-own": "1.0.0", + "make-iterator": "1.0.1" + }, + "dependencies": { + "for-own": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/for-own/-/for-own-1.0.0.tgz", + "integrity": "sha1-xjMy9BXO3EsE2/5wz4NklMU8tEs=", + "dev": true, + "requires": { + "for-in": "1.0.2" + } + } + } + }, + "collection-visit": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz", + "integrity": "sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA=", + "dev": true, + "requires": { + "map-visit": "1.0.0", + "object-visit": "1.0.1" + } + }, + "color-convert": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.1.tgz", + "integrity": "sha512-mjGanIiwQJskCC18rPR6OmrZ6fm2Lc7PeGFYwCmy5J34wC6F1PzdGL6xeMfmgicfYcNLGuVFA3WzXtIDCQSZxQ==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, + "color-support": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", + "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", + "dev": true + }, + "combined-stream": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.7.tgz", + "integrity": "sha512-brWl9y6vOB1xYPZcpZde3N9zDByXTosAeMDo4p1wzo6UMOX4vumB+TP1RZ76sfE6Md68Q0NJSrE/gbezd4Ul+w==", + "dev": true, + "requires": { + "delayed-stream": "1.0.0" + } + }, + "commander": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.19.0.tgz", + "integrity": "sha512-6tvAOO+D6OENvRAh524Dh9jcfKTYDQAqvqezbCW82xj5X0pSrcpxtvRKHLG0yBY6SD7PSDrJaj+0AiOcKVd1Xg==", + "dev": true + }, + "component-emitter": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz", + "integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=", + "dev": true + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "concat-stream": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", + "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", + "dev": true, + "requires": { + "buffer-from": "1.1.1", + "inherits": "2.0.3", + "readable-stream": "2.3.3", + "typedarray": "0.0.6" + } + }, + "console-browserify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.1.0.tgz", + "integrity": "sha1-8CQcRXMKn8YyOyBtvzjtx0HQuxA=", + "dev": true, + "requires": { + "date-now": "0.1.4" + } + }, + "constants-browserify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/constants-browserify/-/constants-browserify-1.0.0.tgz", + "integrity": "sha1-wguW2MYXdIqvHBYCF2DNJ/y4y3U=", + "dev": true + }, + "convert-source-map": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.5.1.tgz", + "integrity": "sha1-uCeAl7m8IpNl3lxiz1/K7YtVmeU=", + "dev": true + }, + "copy-descriptor": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", + "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=", + "dev": true + }, + "copy-props": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/copy-props/-/copy-props-2.0.4.tgz", + "integrity": "sha512-7cjuUME+p+S3HZlbllgsn2CDwS+5eCCX16qBgNC4jgSTf49qR1VKy/Zhl400m0IQXl/bPGEVqncgUUMjrr4s8A==", + "dev": true, + "requires": { + "each-props": "1.3.2", + "is-plain-object": "2.0.4" + } + }, + "core-js": { + "version": "2.5.7", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.5.7.tgz", + "integrity": "sha512-RszJCAxg/PP6uzXVXL6BsxSXx/B05oJAQ2vkJRjyjrEcNVycaqOmNb5OTxZPE3xa5gwZduqza6L9JOCenh/Ecw==", + "dev": true + }, + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", + "dev": true + }, + "create-ecdh": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.0.tgz", + "integrity": "sha1-iIxyNZbN92EvZJgjPuvXo1MBc30=", + "dev": true, + "requires": { + "bn.js": "4.11.8", + "elliptic": "6.4.0" + } + }, + "create-hash": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.1.3.tgz", + "integrity": "sha1-YGBCrIuSYnUPSDyt2rD1gZFy2P0=", + "dev": true, + "requires": { + "cipher-base": "1.0.4", + "inherits": "2.0.3", + "ripemd160": "2.0.1", + "sha.js": "2.4.9" + } + }, + "create-hmac": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.6.tgz", + "integrity": "sha1-rLniIaThe9sHbpBlfEK5PjcmzwY=", + "dev": true, + "requires": { + "cipher-base": "1.0.4", + "create-hash": "1.1.3", + "inherits": "2.0.3", + "ripemd160": "2.0.1", + "safe-buffer": "5.1.1", + "sha.js": "2.4.9" + } + }, + "cross-spawn": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", + "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=", + "dev": true, + "requires": { + "lru-cache": "4.1.1", + "shebang-command": "1.2.0", + "which": "1.3.0" + } + }, + "crypto-browserify": { + "version": "3.12.0", + "resolved": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-3.12.0.tgz", + "integrity": "sha512-fz4spIh+znjO2VjL+IdhEpRJ3YN6sMzITSBijk6FK2UvTqruSQW+/cCZTSNsMiZNvUeq0CqurF+dAbyiGOY6Wg==", + "dev": true, + "requires": { + "browserify-cipher": "1.0.0", + "browserify-sign": "4.0.4", + "create-ecdh": "4.0.0", + "create-hash": "1.1.3", + "create-hmac": "1.1.6", + "diffie-hellman": "5.0.2", + "inherits": "2.0.3", + "pbkdf2": "3.0.14", + "public-encrypt": "4.0.0", + "randombytes": "2.0.5", + "randomfill": "1.0.3" + } + }, + "css": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/css/-/css-2.2.4.tgz", + "integrity": "sha512-oUnjmWpy0niI3x/mPL8dVEI1l7MnG3+HHyRPHf+YFSbK+svOhXpmSOcDURUh2aOCgl2grzrOPt1nHLuCVFULLw==", + "dev": true, + "requires": { + "inherits": "2.0.3", + "source-map": "0.6.1", + "source-map-resolve": "0.5.2", + "urix": "0.1.0" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } + } + }, + "cssom": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.4.tgz", + "integrity": "sha512-+7prCSORpXNeR4/fUP3rL+TzqtiFfhMvTd7uEqMdgPvLPt4+uzFUeufx5RHjGTACCargg/DiEt/moMQmvnfkog==", + "dev": true + }, + "cssstyle": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-1.1.1.tgz", + "integrity": "sha512-364AI1l/M5TYcFH83JnOH/pSqgaNnKmYgKrm0didZMGKWjQB60dymwWy1rKUgL3J1ffdq9xVi2yGLHdSjjSNog==", + "dev": true, + "requires": { + "cssom": "0.3.4" + } + }, + "currently-unhandled": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/currently-unhandled/-/currently-unhandled-0.4.1.tgz", + "integrity": "sha1-mI3zP+qxke95mmE2nddsF635V+o=", + "dev": true, + "requires": { + "array-find-index": "1.0.2" + } + }, + "d": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/d/-/d-1.0.0.tgz", + "integrity": "sha1-dUu1v+VUUdpppYuU1F9MWwRi1Y8=", + "dev": true, + "requires": { + "es5-ext": "0.10.37" + } + }, + "dashdash": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", + "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", + "dev": true, + "requires": { + "assert-plus": "1.0.0" + } + }, + "data-urls": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-1.1.0.tgz", + "integrity": "sha512-YTWYI9se1P55u58gL5GkQHW4P6VJBJ5iBT+B5a7i2Tjadhv52paJG0qHX4A0OR6/t52odI64KP2YvFpkDOi3eQ==", + "dev": true, + "requires": { + "abab": "2.0.0", + "whatwg-mimetype": "2.3.0", + "whatwg-url": "7.0.0" + }, + "dependencies": { + "whatwg-url": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-7.0.0.tgz", + "integrity": "sha512-37GeVSIJ3kn1JgKyjiYNmSLP1yzbpb29jdmwBSgkD9h40/hyrR/OifpVUndji3tmwGgD8qpw7iQu3RSbCrBpsQ==", + "dev": true, + "requires": { + "lodash.sortby": "4.7.0", + "tr46": "1.0.1", + "webidl-conversions": "4.0.2" + } + } + } + }, + "date-now": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/date-now/-/date-now-0.1.4.tgz", + "integrity": "sha1-6vQ5/U1ISK105cx9vvIAZyueNFs=", + "dev": true + }, + "dateformat": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-1.0.12.tgz", + "integrity": "sha1-nxJLZ1lMk3/3BpMuSmQsyo27/uk=", + "dev": true, + "requires": { + "get-stdin": "4.0.1", + "meow": "3.7.0" + } + }, + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "debug-fabulous": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/debug-fabulous/-/debug-fabulous-1.1.0.tgz", + "integrity": "sha512-GZqvGIgKNlUnHUPQhepnUZFIMoi3dgZKQBzKDeL2g7oJF9SNAji/AAu36dusFUas0O+pae74lNeoIPHqXWDkLg==", + "dev": true, + "requires": { + "debug": "3.2.6", + "memoizee": "0.4.14", + "object-assign": "4.1.1" + }, + "dependencies": { + "debug": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "dev": true, + "requires": { + "ms": "2.1.1" + } + }, + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", + "dev": true + } + } + }, + "decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", + "dev": true + }, + "decode-uri-component": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", + "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=", + "dev": true + }, + "deep-is": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", + "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", + "dev": true + }, + "default-compare": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/default-compare/-/default-compare-1.0.0.tgz", + "integrity": "sha512-QWfXlM0EkAbqOCbD/6HjdwT19j7WCkMyiRhWilc4H9/5h/RzTF9gv5LYh1+CmDV5d1rki6KAWLtQale0xt20eQ==", + "dev": true, + "requires": { + "kind-of": "5.1.0" + }, + "dependencies": { + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "dev": true + } + } + }, + "default-require-extensions": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/default-require-extensions/-/default-require-extensions-1.0.0.tgz", + "integrity": "sha1-836hXT4T/9m0N9M+GnW1+5eHTLg=", + "dev": true, + "requires": { + "strip-bom": "2.0.0" + } + }, + "default-resolution": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/default-resolution/-/default-resolution-2.0.0.tgz", + "integrity": "sha1-vLgrqnKtebQmp2cy8aga1t8m1oQ=", + "dev": true + }, + "define-properties": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", + "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", + "dev": true, + "requires": { + "object-keys": "1.0.12" + } + }, + "define-property": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", + "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", + "dev": true, + "requires": { + "is-descriptor": "1.0.2", + "isobject": "3.0.1" + }, + "dependencies": { + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "requires": { + "kind-of": "6.0.2" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "requires": { + "kind-of": "6.0.2" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "1.0.0", + "is-data-descriptor": "1.0.0", + "kind-of": "6.0.2" + } + }, + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true + } + } + }, + "delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", + "dev": true + }, + "des.js": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/des.js/-/des.js-1.0.0.tgz", + "integrity": "sha1-wHTS4qpqipoH29YfmhXCzYPsjsw=", + "dev": true, + "requires": { + "inherits": "2.0.3", + "minimalistic-assert": "1.0.0" + } + }, + "detect-file": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/detect-file/-/detect-file-1.0.0.tgz", + "integrity": "sha1-8NZtA2cqglyxtzvbP+YjEMjlUrc=", + "dev": true + }, + "detect-indent": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-4.0.0.tgz", + "integrity": "sha1-920GQ1LN9Docts5hnE7jqUdd4gg=", + "dev": true, + "requires": { + "repeating": "2.0.1" + } + }, + "detect-newline": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-2.1.0.tgz", + "integrity": "sha1-9B8cEL5LAOh7XxPaaAdZ8sW/0+I=", + "dev": true + }, + "diff": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", + "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", + "dev": true + }, + "diffie-hellman": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.2.tgz", + "integrity": "sha1-tYNXOScM/ias9jIJn97SoH8gnl4=", + "dev": true, + "requires": { + "bn.js": "4.11.8", + "miller-rabin": "4.0.1", + "randombytes": "2.0.5" + } + }, + "domain-browser": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/domain-browser/-/domain-browser-1.1.7.tgz", + "integrity": "sha1-hnqksJP6oF8d4IwG9NeyH9+GmLw=", + "dev": true + }, + "domexception": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/domexception/-/domexception-1.0.1.tgz", + "integrity": "sha512-raigMkn7CJNNo6Ihro1fzG7wr3fHuYVytzquZKX5n0yizGsTcYgzdIUwj1X9pK0VvjeihV+XiclP+DjwbsSKug==", + "dev": true, + "requires": { + "webidl-conversions": "4.0.2" + } + }, + "duplexer2": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.0.2.tgz", + "integrity": "sha1-xhTc9n4vsUmVqRcR5aYX6KYKMds=", + "dev": true, + "requires": { + "readable-stream": "1.1.14" + }, + "dependencies": { + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", + "dev": true + }, + "readable-stream": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", + "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", + "dev": true, + "requires": { + "core-util-is": "1.0.2", + "inherits": "2.0.3", + "isarray": "0.0.1", + "string_decoder": "0.10.31" + } + }, + "string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", + "dev": true + } + } + }, + "duplexify": { + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.5.1.tgz", + "integrity": "sha512-j5goxHTwVED1Fpe5hh3q9R93Kip0Bg2KVAt4f8CEYM3UEwYcPSvWbXaUQOzdX/HtiNomipv+gU7ASQPDbV7pGQ==", + "dev": true, + "requires": { + "end-of-stream": "1.4.0", + "inherits": "2.0.3", + "readable-stream": "2.3.3", + "stream-shift": "1.0.0" + } + }, + "each-props": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/each-props/-/each-props-1.3.2.tgz", + "integrity": "sha512-vV0Hem3zAGkJAyU7JSjixeU66rwdynTAa1vofCrSA5fEln+m67Az9CcnkVD776/fsN/UjIWmBDoNRS6t6G9RfA==", + "dev": true, + "requires": { + "is-plain-object": "2.0.4", + "object.defaults": "1.1.0" + } + }, + "ecc-jsbn": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", + "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", + "dev": true, + "requires": { + "jsbn": "0.1.1", + "safer-buffer": "2.1.2" + } + }, + "elliptic": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.4.0.tgz", + "integrity": "sha1-ysmvh2LIWDYYcAPI3+GT5eLq5d8=", + "dev": true, + "requires": { + "bn.js": "4.11.8", + "brorand": "1.1.0", + "hash.js": "1.1.3", + "hmac-drbg": "1.0.1", + "inherits": "2.0.3", + "minimalistic-assert": "1.0.0", + "minimalistic-crypto-utils": "1.0.1" + } + }, + "emojis-list": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-2.1.0.tgz", + "integrity": "sha1-TapNnbAPmBmIDHn6RXrlsJof04k=", + "dev": true + }, + "end-of-stream": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.0.tgz", + "integrity": "sha1-epDYM+/abPpurA9JSduw+tOmMgY=", + "dev": true, + "requires": { + "once": "1.4.0" + } + }, + "enhanced-resolve": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-3.4.1.tgz", + "integrity": "sha1-BCHjOf1xQZs9oT0Smzl5BAIwR24=", + "dev": true, + "requires": { + "graceful-fs": "4.1.11", + "memory-fs": "0.4.1", + "object-assign": "4.1.1", + "tapable": "0.2.8" + } + }, + "errno": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.4.tgz", + "integrity": "sha1-uJbiOp5ei6M4cfyZar02NfyaHH0=", + "dev": true, + "requires": { + "prr": "0.0.0" + } + }, + "error-ex": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.1.tgz", + "integrity": "sha1-+FWobOYa3E6GIcPNoh56dhLDqNw=", + "dev": true, + "requires": { + "is-arrayish": "0.2.1" + } + }, + "es-abstract": { + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.12.0.tgz", + "integrity": "sha512-C8Fx/0jFmV5IPoMOFPA9P9G5NtqW+4cOPit3MIuvR2t7Ag2K15EJTpxnHAYTzL+aYQJIESYeXZmDBfOBE1HcpA==", + "dev": true, + "requires": { + "es-to-primitive": "1.2.0", + "function-bind": "1.1.1", + "has": "1.0.3", + "is-callable": "1.1.4", + "is-regex": "1.0.4" + } + }, + "es-to-primitive": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.0.tgz", + "integrity": "sha512-qZryBOJjV//LaxLTV6UC//WewneB3LcXOL9NP++ozKVXsIIIpm/2c13UDiD9Jp2eThsecw9m3jPqDwTyobcdbg==", + "dev": true, + "requires": { + "is-callable": "1.1.4", + "is-date-object": "1.0.1", + "is-symbol": "1.0.2" + } + }, + "es5-ext": { + "version": "0.10.37", + "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.37.tgz", + "integrity": "sha1-DudB0Ui4AGm6J9AgOTdWryV978M=", + "dev": true, + "requires": { + "es6-iterator": "2.0.3", + "es6-symbol": "3.1.1" + } + }, + "es6-iterator": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz", + "integrity": "sha1-p96IkUGgWpSwhUQDstCg+/qY87c=", + "dev": true, + "requires": { + "d": "1.0.0", + "es5-ext": "0.10.37", + "es6-symbol": "3.1.1" + } + }, + "es6-map": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/es6-map/-/es6-map-0.1.5.tgz", + "integrity": "sha1-kTbgUD3MBqMBaQ8LsU/042TpSfA=", + "dev": true, + "requires": { + "d": "1.0.0", + "es5-ext": "0.10.37", + "es6-iterator": "2.0.3", + "es6-set": "0.1.5", + "es6-symbol": "3.1.1", + "event-emitter": "0.3.5" + } + }, + "es6-set": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/es6-set/-/es6-set-0.1.5.tgz", + "integrity": "sha1-0rPsXU2ADO2BjbU40ol02wpzzLE=", + "dev": true, + "requires": { + "d": "1.0.0", + "es5-ext": "0.10.37", + "es6-iterator": "2.0.3", + "es6-symbol": "3.1.1", + "event-emitter": "0.3.5" + } + }, + "es6-symbol": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.1.tgz", + "integrity": "sha1-vwDvT9q2uhtG7Le2KbTH7VcVzHc=", + "dev": true, + "requires": { + "d": "1.0.0", + "es5-ext": "0.10.37" + } + }, + "es6-weak-map": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/es6-weak-map/-/es6-weak-map-2.0.2.tgz", + "integrity": "sha1-XjqzIlH/0VOKH45f+hNXdy+S2W8=", + "dev": true, + "requires": { + "d": "1.0.0", + "es5-ext": "0.10.37", + "es6-iterator": "2.0.3", + "es6-symbol": "3.1.1" + } + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true + }, + "escodegen": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.11.0.tgz", + "integrity": "sha512-IeMV45ReixHS53K/OmfKAIztN/igDHzTJUhZM3k1jMhIZWjk45SMwAtBsEXiJp3vSPmTcu6CXn7mDvFHRN66fw==", + "dev": true, + "requires": { + "esprima": "3.1.3", + "estraverse": "4.2.0", + "esutils": "2.0.2", + "optionator": "0.8.2", + "source-map": "0.6.1" + }, + "dependencies": { + "esprima": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-3.1.3.tgz", + "integrity": "sha1-/cpRzuYTOJXjyI1TXOSdv/YqRjM=", + "dev": true + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "optional": true + } + } + }, + "escope": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/escope/-/escope-3.6.0.tgz", + "integrity": "sha1-4Bl16BJ4GhY6ba392AOY3GTIicM=", + "dev": true, + "requires": { + "es6-map": "0.1.5", + "es6-weak-map": "2.0.2", + "esrecurse": "4.2.0", + "estraverse": "4.2.0" + } + }, + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true + }, + "esrecurse": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.0.tgz", + "integrity": "sha1-+pVo2Y04I/mkHZHpAtyrnqblsWM=", + "dev": true, + "requires": { + "estraverse": "4.2.0", + "object-assign": "4.1.1" + } + }, + "estraverse": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.2.0.tgz", + "integrity": "sha1-De4/7TH81GlhjOc0IJn8GvoL2xM=", + "dev": true + }, + "esutils": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz", + "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=", + "dev": true + }, + "event-emitter": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz", + "integrity": "sha1-34xp7vFkeSPHFXuc6DhAYQsCzDk=", + "dev": true, + "requires": { + "d": "1.0.0", + "es5-ext": "0.10.37" + } + }, + "events": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/events/-/events-1.1.1.tgz", + "integrity": "sha1-nr23Y1rQmccNzEwqH1AEKI6L2SQ=", + "dev": true + }, + "evp_bytestokey": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz", + "integrity": "sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==", + "dev": true, + "requires": { + "md5.js": "1.3.4", + "safe-buffer": "5.1.1" + } + }, + "exec-sh": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/exec-sh/-/exec-sh-0.2.2.tgz", + "integrity": "sha512-FIUCJz1RbuS0FKTdaAafAByGS0CPvU3R0MeHxgtl+djzCc//F8HakL8GzmVNZanasTbTAY/3DRFA0KpVqj/eAw==", + "dev": true, + "requires": { + "merge": "1.2.1" + } + }, + "execa": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-0.7.0.tgz", + "integrity": "sha1-lEvs00zEHuMqY6n68nrVpl/Fl3c=", + "dev": true, + "requires": { + "cross-spawn": "5.1.0", + "get-stream": "3.0.0", + "is-stream": "1.1.0", + "npm-run-path": "2.0.2", + "p-finally": "1.0.0", + "signal-exit": "3.0.2", + "strip-eof": "1.0.0" + } + }, + "exit": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", + "integrity": "sha1-BjJjj42HfMghB9MKD/8aF8uhzQw=", + "dev": true + }, + "expand-brackets": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-0.1.5.tgz", + "integrity": "sha1-3wcoTjQqgHzXM6xa9yQR5YHRF3s=", + "dev": true, + "requires": { + "is-posix-bracket": "0.1.1" + } + }, + "expand-range": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/expand-range/-/expand-range-1.8.2.tgz", + "integrity": "sha1-opnv/TNf4nIeuujiV+x5ZE/IUzc=", + "dev": true, + "requires": { + "fill-range": "2.2.4" + } + }, + "expand-tilde": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/expand-tilde/-/expand-tilde-2.0.2.tgz", + "integrity": "sha1-l+gBqgUt8CRU3kawK/YhZCzchQI=", + "dev": true, + "requires": { + "homedir-polyfill": "1.0.1" + } + }, + "expect": { + "version": "23.6.0", + "resolved": "https://registry.npmjs.org/expect/-/expect-23.6.0.tgz", + "integrity": "sha512-dgSoOHgmtn/aDGRVFWclQyPDKl2CQRq0hmIEoUAuQs/2rn2NcvCWcSCovm6BLeuB/7EZuLGu2QfnR+qRt5OM4w==", + "dev": true, + "requires": { + "ansi-styles": "3.2.1", + "jest-diff": "23.6.0", + "jest-get-type": "22.4.3", + "jest-matcher-utils": "23.6.0", + "jest-message-util": "23.4.0", + "jest-regex-util": "23.3.0" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "1.9.1" + } + } + } + }, + "extend": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.1.tgz", + "integrity": "sha1-p1Xqe8Gt/MWjHOfnYtuq3F5jZEQ=", + "dev": true + }, + "extend-shallow": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", + "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", + "dev": true, + "requires": { + "assign-symbols": "1.0.0", + "is-extendable": "1.0.1" + }, + "dependencies": { + "is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "dev": true, + "requires": { + "is-plain-object": "2.0.4" + } + } + } + }, + "extglob": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/extglob/-/extglob-0.3.2.tgz", + "integrity": "sha1-Lhj/PS9JqydlzskCPwEdqo2DSaE=", + "dev": true, + "requires": { + "is-extglob": "1.0.0" + }, + "dependencies": { + "is-extglob": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz", + "integrity": "sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA=", + "dev": true + } + } + }, + "extsprintf": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", + "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=", + "dev": true + }, + "fancy-log": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/fancy-log/-/fancy-log-1.3.0.tgz", + "integrity": "sha1-Rb4X0Cu5kX1gzP/UmVyZnmyMmUg=", + "dev": true, + "requires": { + "chalk": "1.1.3", + "time-stamp": "1.1.0" + } + }, + "fast-deep-equal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", + "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=", + "dev": true + }, + "fast-json-stable-stringify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", + "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=", + "dev": true + }, + "fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", + "dev": true + }, + "fb-watchman": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.0.tgz", + "integrity": "sha1-VOmr99+i8mzZsWNsWIwa/AXeXVg=", + "dev": true, + "requires": { + "bser": "2.0.0" + } + }, + "filename-regex": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/filename-regex/-/filename-regex-2.0.1.tgz", + "integrity": "sha1-wcS5vuPglyXdsQa3XB4wH+LxiyY=", + "dev": true + }, + "fileset": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/fileset/-/fileset-2.0.3.tgz", + "integrity": "sha1-jnVIqW08wjJ+5eZ0FocjozO7oqA=", + "dev": true, + "requires": { + "glob": "7.1.3", + "minimatch": "3.0.4" + }, + "dependencies": { + "glob": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", + "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", + "dev": true, + "requires": { + "fs.realpath": "1.0.0", + "inflight": "1.0.6", + "inherits": "2.0.3", + "minimatch": "3.0.4", + "once": "1.4.0", + "path-is-absolute": "1.0.1" + } + } + } + }, + "fill-range": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-2.2.4.tgz", + "integrity": "sha512-cnrcCbj01+j2gTG921VZPnHbjmdAf8oQV/iGeV2kZxGSyfYjjTyY79ErsK1WJWMpw6DaApEX72binqJE+/d+5Q==", + "dev": true, + "requires": { + "is-number": "2.1.0", + "isobject": "2.1.0", + "randomatic": "3.1.1", + "repeat-element": "1.1.2", + "repeat-string": "1.6.1" + }, + "dependencies": { + "randomatic": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/randomatic/-/randomatic-3.1.1.tgz", + "integrity": "sha512-TuDE5KxZ0J461RVjrJZCJc+J+zCkTb1MbH9AQUq68sMhOMcy9jLcb3BrZKgp9q9Ncltdg4QVqWrH02W2EFFVYw==", + "dev": true, + "requires": { + "is-number": "4.0.0", + "kind-of": "6.0.2", + "math-random": "1.0.1" + }, + "dependencies": { + "is-number": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-4.0.0.tgz", + "integrity": "sha512-rSklcAIlf1OmFdyAqbnWTLVelsQ58uvZ66S/ZyawjWqIviTWCjg2PzVGw8WUA+nNuPTqb4wgA+NszrJ+08LlgQ==", + "dev": true + } + } + } + } + }, + "find-up": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", + "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=", + "dev": true, + "requires": { + "path-exists": "2.1.0", + "pinkie-promise": "2.0.1" + } + }, + "findup-sync": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-2.0.0.tgz", + "integrity": "sha1-kyaxSIwi0aYIhlCoaQGy2akKLLw=", + "dev": true, + "requires": { + "detect-file": "1.0.0", + "is-glob": "3.1.0", + "micromatch": "3.1.10", + "resolve-dir": "1.0.1" + }, + "dependencies": { + "array-unique": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", + "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", + "dev": true + }, + "braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "dev": true, + "requires": { + "arr-flatten": "1.1.0", + "array-unique": "0.3.2", + "extend-shallow": "2.0.1", + "fill-range": "4.0.0", + "isobject": "3.0.1", + "repeat-element": "1.1.2", + "snapdragon": "0.8.2", + "snapdragon-node": "2.1.1", + "split-string": "3.1.0", + "to-regex": "3.0.2" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "0.1.1" + } + } + } + }, + "expand-brackets": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", + "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", + "dev": true, + "requires": { + "debug": "2.6.9", + "define-property": "0.2.5", + "extend-shallow": "2.0.1", + "posix-character-classes": "0.1.1", + "regex-not": "1.0.2", + "snapdragon": "0.8.2", + "to-regex": "3.0.2" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "0.1.6" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "0.1.1" + } + }, + "is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", + "dev": true, + "requires": { + "kind-of": "3.2.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "1.1.6" + } + } + } + }, + "is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", + "dev": true, + "requires": { + "kind-of": "3.2.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "1.1.6" + } + } + } + }, + "is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "0.1.6", + "is-data-descriptor": "0.1.4", + "kind-of": "5.1.0" + } + }, + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "dev": true + } + } + }, + "extglob": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", + "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", + "dev": true, + "requires": { + "array-unique": "0.3.2", + "define-property": "1.0.0", + "expand-brackets": "2.1.4", + "extend-shallow": "2.0.1", + "fragment-cache": "0.2.1", + "regex-not": "1.0.2", + "snapdragon": "0.8.2", + "to-regex": "3.0.2" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "requires": { + "is-descriptor": "1.0.2" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "0.1.1" + } + } + } + }, + "fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", + "dev": true, + "requires": { + "extend-shallow": "2.0.1", + "is-number": "3.0.0", + "repeat-string": "1.6.1", + "to-regex-range": "2.1.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "0.1.1" + } + } + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "requires": { + "kind-of": "6.0.2" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "requires": { + "kind-of": "6.0.2" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "1.0.0", + "is-data-descriptor": "1.0.0", + "kind-of": "6.0.2" + } + }, + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "dev": true, + "requires": { + "kind-of": "3.2.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "1.1.6" + } + } + } + }, + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true + }, + "micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "dev": true, + "requires": { + "arr-diff": "4.0.0", + "array-unique": "0.3.2", + "braces": "2.3.2", + "define-property": "2.0.2", + "extend-shallow": "3.0.2", + "extglob": "2.0.4", + "fragment-cache": "0.2.1", + "kind-of": "6.0.2", + "nanomatch": "1.2.13", + "object.pick": "1.3.0", + "regex-not": "1.0.2", + "snapdragon": "0.8.2", + "to-regex": "3.0.2" + } + } + } + }, + "fined": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fined/-/fined-1.1.0.tgz", + "integrity": "sha1-s33IRLdqL15wgeiE98CuNE8VNHY=", + "dev": true, + "requires": { + "expand-tilde": "2.0.2", + "is-plain-object": "2.0.4", + "object.defaults": "1.1.0", + "object.pick": "1.3.0", + "parse-filepath": "1.0.2" + } + }, + "first-chunk-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/first-chunk-stream/-/first-chunk-stream-1.0.0.tgz", + "integrity": "sha1-Wb+1DNkF9g18OUzT2ayqtOatk04=", + "dev": true + }, + "flagged-respawn": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/flagged-respawn/-/flagged-respawn-1.0.0.tgz", + "integrity": "sha1-Tnmumy6zi/hrO7Vr8+ClaqX8q9c=", + "dev": true + }, + "flush-write-stream": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/flush-write-stream/-/flush-write-stream-1.0.3.tgz", + "integrity": "sha512-calZMC10u0FMUqoiunI2AiGIIUtUIvifNwkHhNupZH4cbNnW1Itkoh/Nf5HFYmDrwWPjrUxpkZT0KhuCq0jmGw==", + "dev": true, + "requires": { + "inherits": "2.0.3", + "readable-stream": "2.3.3" + } + }, + "for-in": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", + "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=", + "dev": true + }, + "for-own": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/for-own/-/for-own-0.1.5.tgz", + "integrity": "sha1-UmXGgaTylNq78XyVCbZ2OqhFEM4=", + "dev": true, + "requires": { + "for-in": "1.0.2" + } + }, + "forever-agent": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", + "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=", + "dev": true + }, + "form-data": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", + "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", + "dev": true, + "requires": { + "asynckit": "0.4.0", + "combined-stream": "1.0.7", + "mime-types": "2.1.21" + } + }, + "fragment-cache": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz", + "integrity": "sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk=", + "dev": true, + "requires": { + "map-cache": "0.2.2" + } + }, + "fs-mkdirp-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-mkdirp-stream/-/fs-mkdirp-stream-1.0.0.tgz", + "integrity": "sha1-C3gV/DIBxqaeFNuYzgmMFpNSWes=", + "dev": true, + "requires": { + "graceful-fs": "4.1.11", + "through2": "2.0.3" + } + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "fsevents": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.4.tgz", + "integrity": "sha512-z8H8/diyk76B7q5wg+Ud0+CqzcAF3mBBI/bA5ne5zrRUUIvNkJY//D3BqyH571KuAC4Nr7Rw7CjWX4r0y9DvNg==", + "dev": true, + "optional": true, + "requires": { + "nan": "2.11.1", + "node-pre-gyp": "0.10.0" + }, + "dependencies": { + "abbrev": { + "version": "1.1.1", + "bundled": true, + "dev": true, + "optional": true + }, + "ansi-regex": { + "version": "2.1.1", + "bundled": true, + "dev": true + }, + "aproba": { + "version": "1.2.0", + "bundled": true, + "dev": true, + "optional": true + }, + "are-we-there-yet": { + "version": "1.1.4", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "delegates": "1.0.0", + "readable-stream": "2.3.6" + } + }, + "balanced-match": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "brace-expansion": { + "version": "1.1.11", + "bundled": true, + "dev": true, + "requires": { + "balanced-match": "1.0.0", + "concat-map": "0.0.1" + } + }, + "chownr": { + "version": "1.0.1", + "bundled": true, + "dev": true, + "optional": true + }, + "code-point-at": { + "version": "1.1.0", + "bundled": true, + "dev": true + }, + "concat-map": { + "version": "0.0.1", + "bundled": true, + "dev": true + }, + "console-control-strings": { + "version": "1.1.0", + "bundled": true, + "dev": true + }, + "core-util-is": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "debug": { + "version": "2.6.9", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "ms": "2.0.0" + } + }, + "deep-extend": { + "version": "0.5.1", + "bundled": true, + "dev": true, + "optional": true + }, + "delegates": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "detect-libc": { + "version": "1.0.3", + "bundled": true, + "dev": true, + "optional": true + }, + "fs-minipass": { + "version": "1.2.5", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "minipass": "2.2.4" + } + }, + "fs.realpath": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "gauge": { + "version": "2.7.4", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "aproba": "1.2.0", + "console-control-strings": "1.1.0", + "has-unicode": "2.0.1", + "object-assign": "4.1.1", + "signal-exit": "3.0.2", + "string-width": "1.0.2", + "strip-ansi": "3.0.1", + "wide-align": "1.1.2" + } + }, + "glob": { + "version": "7.1.2", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "fs.realpath": "1.0.0", + "inflight": "1.0.6", + "inherits": "2.0.3", + "minimatch": "3.0.4", + "once": "1.4.0", + "path-is-absolute": "1.0.1" + } + }, + "has-unicode": { + "version": "2.0.1", + "bundled": true, + "dev": true, + "optional": true + }, + "iconv-lite": { + "version": "0.4.21", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "safer-buffer": "2.1.2" + } + }, + "ignore-walk": { + "version": "3.0.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "minimatch": "3.0.4" + } + }, + "inflight": { + "version": "1.0.6", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "once": "1.4.0", + "wrappy": "1.0.2" + } + }, + "inherits": { + "version": "2.0.3", + "bundled": true, + "dev": true + }, + "ini": { + "version": "1.3.5", + "bundled": true, + "dev": true, + "optional": true + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "requires": { + "number-is-nan": "1.0.1" + } + }, + "isarray": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "minimatch": { + "version": "3.0.4", + "bundled": true, + "dev": true, + "requires": { + "brace-expansion": "1.1.11" + } + }, + "minimist": { + "version": "0.0.8", + "bundled": true, + "dev": true + }, + "minipass": { + "version": "2.2.4", + "bundled": true, + "dev": true, + "requires": { + "safe-buffer": "5.1.1", + "yallist": "3.0.2" + } + }, + "minizlib": { + "version": "1.1.0", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "minipass": "2.2.4" + } + }, + "mkdirp": { + "version": "0.5.1", + "bundled": true, + "dev": true, + "requires": { + "minimist": "0.0.8" + } + }, + "ms": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "needle": { + "version": "2.2.0", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "debug": "2.6.9", + "iconv-lite": "0.4.21", + "sax": "1.2.4" + } + }, + "node-pre-gyp": { + "version": "0.10.0", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "detect-libc": "1.0.3", + "mkdirp": "0.5.1", + "needle": "2.2.0", + "nopt": "4.0.1", + "npm-packlist": "1.1.10", + "npmlog": "4.1.2", + "rc": "1.2.7", + "rimraf": "2.6.2", + "semver": "5.5.0", + "tar": "4.4.1" + } + }, + "nopt": { + "version": "4.0.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "abbrev": "1.1.1", + "osenv": "0.1.5" + } + }, + "npm-bundled": { + "version": "1.0.3", + "bundled": true, + "dev": true, + "optional": true + }, + "npm-packlist": { + "version": "1.1.10", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "ignore-walk": "3.0.1", + "npm-bundled": "1.0.3" + } + }, + "npmlog": { + "version": "4.1.2", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "are-we-there-yet": "1.1.4", + "console-control-strings": "1.1.0", + "gauge": "2.7.4", + "set-blocking": "2.0.0" + } + }, + "number-is-nan": { + "version": "1.0.1", + "bundled": true, + "dev": true + }, + "object-assign": { + "version": "4.1.1", + "bundled": true, + "dev": true, + "optional": true + }, + "once": { + "version": "1.4.0", + "bundled": true, + "dev": true, + "requires": { + "wrappy": "1.0.2" + } + }, + "os-homedir": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "os-tmpdir": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "osenv": { + "version": "0.1.5", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "os-homedir": "1.0.2", + "os-tmpdir": "1.0.2" + } + }, + "path-is-absolute": { + "version": "1.0.1", + "bundled": true, + "dev": true, + "optional": true + }, + "process-nextick-args": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "rc": { + "version": "1.2.7", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "deep-extend": "0.5.1", + "ini": "1.3.5", + "minimist": "1.2.0", + "strip-json-comments": "2.0.1" + }, + "dependencies": { + "minimist": { + "version": "1.2.0", + "bundled": true, + "dev": true, + "optional": true + } + } + }, + "readable-stream": { + "version": "2.3.6", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "core-util-is": "1.0.2", + "inherits": "2.0.3", + "isarray": "1.0.0", + "process-nextick-args": "2.0.0", + "safe-buffer": "5.1.1", + "string_decoder": "1.1.1", + "util-deprecate": "1.0.2" + } + }, + "rimraf": { + "version": "2.6.2", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "glob": "7.1.2" + } + }, + "safe-buffer": { + "version": "5.1.1", + "bundled": true, + "dev": true + }, + "safer-buffer": { + "version": "2.1.2", + "bundled": true, + "dev": true, + "optional": true + }, + "sax": { + "version": "1.2.4", + "bundled": true, + "dev": true, + "optional": true + }, + "semver": { + "version": "5.5.0", + "bundled": true, + "dev": true, + "optional": true + }, + "set-blocking": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "signal-exit": { + "version": "3.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "string-width": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "requires": { + "code-point-at": "1.1.0", + "is-fullwidth-code-point": "1.0.0", + "strip-ansi": "3.0.1" + } + }, + "string_decoder": { + "version": "1.1.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "safe-buffer": "5.1.1" + } + }, + "strip-ansi": { + "version": "3.0.1", + "bundled": true, + "dev": true, + "requires": { + "ansi-regex": "2.1.1" + } + }, + "strip-json-comments": { + "version": "2.0.1", + "bundled": true, + "dev": true, + "optional": true + }, + "tar": { + "version": "4.4.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "chownr": "1.0.1", + "fs-minipass": "1.2.5", + "minipass": "2.2.4", + "minizlib": "1.1.0", + "mkdirp": "0.5.1", + "safe-buffer": "5.1.1", + "yallist": "3.0.2" + } + }, + "util-deprecate": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "wide-align": { + "version": "1.1.2", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "string-width": "1.0.2" + } + }, + "wrappy": { + "version": "1.0.2", + "bundled": true, + "dev": true + }, + "yallist": { + "version": "3.0.2", + "bundled": true, + "dev": true + } + } + }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, + "get-caller-file": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.2.tgz", + "integrity": "sha1-9wLmMSfn4jHBYKgMFVSstw1QR+U=", + "dev": true + }, + "get-stdin": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-4.0.1.tgz", + "integrity": "sha1-uWjGsKBDhDJJAui/Gl3zJXmkUP4=", + "dev": true + }, + "get-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", + "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=", + "dev": true + }, + "get-value": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", + "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=", + "dev": true + }, + "getpass": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", + "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", + "dev": true, + "requires": { + "assert-plus": "1.0.0" + } + }, + "glob": { + "version": "5.0.15", + "resolved": "https://registry.npmjs.org/glob/-/glob-5.0.15.tgz", + "integrity": "sha1-G8k2ueAvSmA/zCIuz3Yz0wuLk7E=", + "dev": true, + "requires": { + "inflight": "1.0.6", + "inherits": "2.0.3", + "minimatch": "3.0.4", + "once": "1.4.0", + "path-is-absolute": "1.0.1" + } + }, + "glob-base": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/glob-base/-/glob-base-0.3.0.tgz", + "integrity": "sha1-27Fk9iIbHAscz4Kuoyi0l98Oo8Q=", + "dev": true, + "requires": { + "glob-parent": "2.0.0", + "is-glob": "2.0.1" + }, + "dependencies": { + "glob-parent": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-2.0.0.tgz", + "integrity": "sha1-gTg9ctsFT8zPUzbaqQLxgvbtuyg=", + "dev": true, + "requires": { + "is-glob": "2.0.1" + } + }, + "is-extglob": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz", + "integrity": "sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA=", + "dev": true + }, + "is-glob": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz", + "integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=", + "dev": true, + "requires": { + "is-extglob": "1.0.0" + } + } + } + }, + "glob-parent": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", + "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", + "dev": true, + "requires": { + "is-glob": "3.1.0", + "path-dirname": "1.0.2" + } + }, + "glob-stream": { + "version": "5.3.5", + "resolved": "https://registry.npmjs.org/glob-stream/-/glob-stream-5.3.5.tgz", + "integrity": "sha1-pVZlqajM3EGRWofHAeMtTgFvrSI=", + "dev": true, + "requires": { + "extend": "3.0.1", + "glob": "5.0.15", + "glob-parent": "3.1.0", + "micromatch": "2.3.11", + "ordered-read-streams": "0.3.0", + "through2": "0.6.5", + "to-absolute-glob": "0.1.1", + "unique-stream": "2.2.1" + }, + "dependencies": { + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", + "dev": true + }, + "readable-stream": { + "version": "1.0.34", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", + "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", + "dev": true, + "requires": { + "core-util-is": "1.0.2", + "inherits": "2.0.3", + "isarray": "0.0.1", + "string_decoder": "0.10.31" + } + }, + "string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", + "dev": true + }, + "through2": { + "version": "0.6.5", + "resolved": "https://registry.npmjs.org/through2/-/through2-0.6.5.tgz", + "integrity": "sha1-QaucZ7KdVyCQcUEOHXp6lozTrUg=", + "dev": true, + "requires": { + "readable-stream": "1.0.34", + "xtend": "4.0.1" + } + } + } + }, + "glob-watcher": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/glob-watcher/-/glob-watcher-5.0.3.tgz", + "integrity": "sha512-8tWsULNEPHKQ2MR4zXuzSmqbdyV5PtwwCaWSGQ1WwHsJ07ilNeN1JB8ntxhckbnpSHaf9dXFUHzIWvm1I13dsg==", + "dev": true, + "requires": { + "anymatch": "2.0.0", + "async-done": "1.3.1", + "chokidar": "2.0.4", + "is-negated-glob": "1.0.0", + "just-debounce": "1.0.0", + "object.defaults": "1.1.0" + }, + "dependencies": { + "anymatch": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", + "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", + "dev": true, + "requires": { + "micromatch": "3.1.10", + "normalize-path": "2.1.1" + } + }, + "array-unique": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", + "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", + "dev": true + }, + "braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "dev": true, + "requires": { + "arr-flatten": "1.1.0", + "array-unique": "0.3.2", + "extend-shallow": "2.0.1", + "fill-range": "4.0.0", + "isobject": "3.0.1", + "repeat-element": "1.1.2", + "snapdragon": "0.8.2", + "snapdragon-node": "2.1.1", + "split-string": "3.1.0", + "to-regex": "3.0.2" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "0.1.1" + } + } + } + }, + "chokidar": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.0.4.tgz", + "integrity": "sha512-z9n7yt9rOvIJrMhvDtDictKrkFHeihkNl6uWMmZlmL6tJtX9Cs+87oK+teBx+JIgzvbX3yZHT3eF8vpbDxHJXQ==", + "dev": true, + "requires": { + "anymatch": "2.0.0", + "async-each": "1.0.1", + "braces": "2.3.2", + "fsevents": "1.2.4", + "glob-parent": "3.1.0", + "inherits": "2.0.3", + "is-binary-path": "1.0.1", + "is-glob": "4.0.0", + "lodash.debounce": "4.0.8", + "normalize-path": "2.1.1", + "path-is-absolute": "1.0.1", + "readdirp": "2.1.0", + "upath": "1.1.0" + } + }, + "expand-brackets": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", + "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", + "dev": true, + "requires": { + "debug": "2.6.9", + "define-property": "0.2.5", + "extend-shallow": "2.0.1", + "posix-character-classes": "0.1.1", + "regex-not": "1.0.2", + "snapdragon": "0.8.2", + "to-regex": "3.0.2" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "0.1.6" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "0.1.1" + } + }, + "is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", + "dev": true, + "requires": { + "kind-of": "3.2.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "1.1.6" + } + } + } + }, + "is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", + "dev": true, + "requires": { + "kind-of": "3.2.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "1.1.6" + } + } + } + }, + "is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "0.1.6", + "is-data-descriptor": "0.1.4", + "kind-of": "5.1.0" + } + }, + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "dev": true + } + } + }, + "extglob": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", + "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", + "dev": true, + "requires": { + "array-unique": "0.3.2", + "define-property": "1.0.0", + "expand-brackets": "2.1.4", + "extend-shallow": "2.0.1", + "fragment-cache": "0.2.1", + "regex-not": "1.0.2", + "snapdragon": "0.8.2", + "to-regex": "3.0.2" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "requires": { + "is-descriptor": "1.0.2" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "0.1.1" + } + } + } + }, + "fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", + "dev": true, + "requires": { + "extend-shallow": "2.0.1", + "is-number": "3.0.0", + "repeat-string": "1.6.1", + "to-regex-range": "2.1.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "0.1.1" + } + } + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "requires": { + "kind-of": "6.0.2" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "requires": { + "kind-of": "6.0.2" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "1.0.0", + "is-data-descriptor": "1.0.0", + "kind-of": "6.0.2" + } + }, + "is-glob": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.0.tgz", + "integrity": "sha1-lSHHaEXMJhCoUgPd8ICpWML/q8A=", + "dev": true, + "requires": { + "is-extglob": "2.1.1" + } + }, + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "dev": true, + "requires": { + "kind-of": "3.2.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "1.1.6" + } + } + } + }, + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true + }, + "micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "dev": true, + "requires": { + "arr-diff": "4.0.0", + "array-unique": "0.3.2", + "braces": "2.3.2", + "define-property": "2.0.2", + "extend-shallow": "3.0.2", + "extglob": "2.0.4", + "fragment-cache": "0.2.1", + "kind-of": "6.0.2", + "nanomatch": "1.2.13", + "object.pick": "1.3.0", + "regex-not": "1.0.2", + "snapdragon": "0.8.2", + "to-regex": "3.0.2" + } + } + } + }, + "global-modules": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-1.0.0.tgz", + "integrity": "sha512-sKzpEkf11GpOFuw0Zzjzmt4B4UZwjOcG757PPvrfhxcLFbq0wpsgpOqxpxtxFiCG4DtG93M6XRVbF2oGdev7bg==", + "dev": true, + "requires": { + "global-prefix": "1.0.2", + "is-windows": "1.0.2", + "resolve-dir": "1.0.1" + } + }, + "global-prefix": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-1.0.2.tgz", + "integrity": "sha1-2/dDxsFJklk8ZVVoy2btMsASLr4=", + "dev": true, + "requires": { + "expand-tilde": "2.0.2", + "homedir-polyfill": "1.0.1", + "ini": "1.3.5", + "is-windows": "1.0.2", + "which": "1.3.0" + } + }, + "globals": { + "version": "9.18.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-9.18.0.tgz", + "integrity": "sha512-S0nG3CLEQiY/ILxqtztTWH/3iRRdyBLw6KMDxnKMchrtbj2OFmehVh0WUCfW3DUrIgx/qFrJPICrq4Z4sTR9UQ==", + "dev": true + }, + "glogg": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/glogg/-/glogg-1.0.0.tgz", + "integrity": "sha1-f+DxmfV6yQbPUS/urY+Q7kooT8U=", + "dev": true, + "requires": { + "sparkles": "1.0.0" + } + }, + "graceful-fs": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz", + "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=", + "dev": true + }, + "growly": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/growly/-/growly-1.3.0.tgz", + "integrity": "sha1-8QdIy+dq+WS3yWyTxrzCivEgwIE=", + "dev": true + }, + "gulp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/gulp/-/gulp-4.0.0.tgz", + "integrity": "sha1-lXZsYB2t5Kd+0+eyttwDiBtZY2Y=", + "dev": true, + "requires": { + "glob-watcher": "5.0.3", + "gulp-cli": "2.0.1", + "undertaker": "1.2.0", + "vinyl-fs": "3.0.3" + }, + "dependencies": { + "cliui": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-3.2.0.tgz", + "integrity": "sha1-EgYBU3qRbSmUD5NNo7SNWFo5IT0=", + "dev": true, + "requires": { + "string-width": "1.0.2", + "strip-ansi": "3.0.1", + "wrap-ansi": "2.1.0" + } + }, + "clone": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", + "integrity": "sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18=", + "dev": true + }, + "clone-stats": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/clone-stats/-/clone-stats-1.0.0.tgz", + "integrity": "sha1-s3gt/4u1R04Yuba/D9/ngvh3doA=", + "dev": true + }, + "fancy-log": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/fancy-log/-/fancy-log-1.3.2.tgz", + "integrity": "sha1-9BEl49hPLn2JpD0G2VjI94vha+E=", + "dev": true, + "requires": { + "ansi-gray": "0.1.1", + "color-support": "1.1.3", + "time-stamp": "1.1.0" + } + }, + "glob": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", + "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", + "dev": true, + "requires": { + "fs.realpath": "1.0.0", + "inflight": "1.0.6", + "inherits": "2.0.3", + "minimatch": "3.0.4", + "once": "1.4.0", + "path-is-absolute": "1.0.1" + } + }, + "glob-stream": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/glob-stream/-/glob-stream-6.1.0.tgz", + "integrity": "sha1-cEXJlBOz65SIjYOrRtC0BMx73eQ=", + "dev": true, + "requires": { + "extend": "3.0.1", + "glob": "7.1.3", + "glob-parent": "3.1.0", + "is-negated-glob": "1.0.0", + "ordered-read-streams": "1.0.1", + "pumpify": "1.5.1", + "readable-stream": "2.3.3", + "remove-trailing-separator": "1.1.0", + "to-absolute-glob": "2.0.2", + "unique-stream": "2.2.1" + } + }, + "gulp-cli": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/gulp-cli/-/gulp-cli-2.0.1.tgz", + "integrity": "sha512-RxujJJdN8/O6IW2nPugl7YazhmrIEjmiVfPKrWt68r71UCaLKS71Hp0gpKT+F6qOUFtr7KqtifDKaAJPRVvMYQ==", + "dev": true, + "requires": { + "ansi-colors": "1.1.0", + "archy": "1.0.0", + "array-sort": "1.0.0", + "color-support": "1.1.3", + "concat-stream": "1.6.2", + "copy-props": "2.0.4", + "fancy-log": "1.3.2", + "gulplog": "1.0.0", + "interpret": "1.1.0", + "isobject": "3.0.1", + "liftoff": "2.5.0", + "matchdep": "2.0.0", + "mute-stdout": "1.0.1", + "pretty-hrtime": "1.0.3", + "replace-homedir": "1.0.0", + "semver-greatest-satisfied-range": "1.1.0", + "v8flags": "3.1.1", + "yargs": "7.1.0" + } + }, + "is-valid-glob": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-valid-glob/-/is-valid-glob-1.0.0.tgz", + "integrity": "sha1-Kb8+/3Ab4tTTFdusw5vDn+j2Aao=", + "dev": true + }, + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true + }, + "ordered-read-streams": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ordered-read-streams/-/ordered-read-streams-1.0.1.tgz", + "integrity": "sha1-d8DLN8QVJdZBZtmQ/61+xqDhNj4=", + "dev": true, + "requires": { + "readable-stream": "2.3.3" + } + }, + "os-locale": { + "version": "1.4.0", + "resolved": "http://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz", + "integrity": "sha1-IPnxeuKe00XoveWDsT0gCYA8FNk=", + "dev": true, + "requires": { + "lcid": "1.0.0" + } + }, + "replace-ext": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-1.0.0.tgz", + "integrity": "sha1-3mMSg3P8v3w8z6TeWkgMRaZ5WOs=", + "dev": true + }, + "string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "dev": true, + "requires": { + "code-point-at": "1.1.0", + "is-fullwidth-code-point": "1.0.0", + "strip-ansi": "3.0.1" + } + }, + "to-absolute-glob": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/to-absolute-glob/-/to-absolute-glob-2.0.2.tgz", + "integrity": "sha1-GGX0PZ50sIItufFFt4z/fQ98hJs=", + "dev": true, + "requires": { + "is-absolute": "1.0.0", + "is-negated-glob": "1.0.0" + } + }, + "vinyl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-2.2.0.tgz", + "integrity": "sha512-MBH+yP0kC/GQ5GwBqrTPTzEfiiLjta7hTtvQtbxBgTeSXsmKQRQecjibMbxIXzVT3Y9KJK+drOz1/k+vsu8Nkg==", + "dev": true, + "requires": { + "clone": "2.1.2", + "clone-buffer": "1.0.0", + "clone-stats": "1.0.0", + "cloneable-readable": "1.0.0", + "remove-trailing-separator": "1.1.0", + "replace-ext": "1.0.0" + } + }, + "vinyl-fs": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/vinyl-fs/-/vinyl-fs-3.0.3.tgz", + "integrity": "sha512-vIu34EkyNyJxmP0jscNzWBSygh7VWhqun6RmqVfXePrOwi9lhvRs//dOaGOTRUQr4tx7/zd26Tk5WeSVZitgng==", + "dev": true, + "requires": { + "fs-mkdirp-stream": "1.0.0", + "glob-stream": "6.1.0", + "graceful-fs": "4.1.11", + "is-valid-glob": "1.0.0", + "lazystream": "1.0.0", + "lead": "1.0.0", + "object.assign": "4.1.0", + "pumpify": "1.5.1", + "readable-stream": "2.3.3", + "remove-bom-buffer": "3.0.0", + "remove-bom-stream": "1.2.0", + "resolve-options": "1.1.0", + "through2": "2.0.3", + "to-through": "2.0.0", + "value-or-function": "3.0.0", + "vinyl": "2.2.0", + "vinyl-sourcemap": "1.1.0" + } + }, + "which-module": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-1.0.0.tgz", + "integrity": "sha1-u6Y8qGGUiZT/MHc2CJ47lgJsKk8=", + "dev": true + }, + "yargs": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-7.1.0.tgz", + "integrity": "sha1-a6MY6xaWFyf10oT46gA+jWFU0Mg=", + "dev": true, + "requires": { + "camelcase": "3.0.0", + "cliui": "3.2.0", + "decamelize": "1.2.0", + "get-caller-file": "1.0.2", + "os-locale": "1.4.0", + "read-pkg-up": "1.0.1", + "require-directory": "2.1.1", + "require-main-filename": "1.0.1", + "set-blocking": "2.0.0", + "string-width": "1.0.2", + "which-module": "1.0.0", + "y18n": "3.2.1", + "yargs-parser": "5.0.0" + } + }, + "yargs-parser": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-5.0.0.tgz", + "integrity": "sha1-J17PDX/+Bcd+ZOfIbkzZS/DhIoo=", + "dev": true, + "requires": { + "camelcase": "3.0.0" + } + } + } + }, + "gulp-minify": { + "version": "0.0.15", + "resolved": "https://registry.npmjs.org/gulp-minify/-/gulp-minify-0.0.15.tgz", + "integrity": "sha1-R5IFXZi3fUO9ggV5YdpHZGZ0Occ=", + "dev": true, + "requires": { + "gulp-util": "2.2.20", + "minimatch": "3.0.4", + "through2": "0.4.2", + "uglify-js": "2.8.29" + }, + "dependencies": { + "ansi-regex": { + "version": "0.2.1", + "resolved": "http://registry.npmjs.org/ansi-regex/-/ansi-regex-0.2.1.tgz", + "integrity": "sha1-DY6UaWej2BQ/k+JOKYUl/BsiNfk=", + "dev": true + }, + "ansi-styles": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-1.1.0.tgz", + "integrity": "sha1-6uy/Zs1waIJ2Cy9GkVgrj1XXp94=", + "dev": true + }, + "chalk": { + "version": "0.5.1", + "resolved": "http://registry.npmjs.org/chalk/-/chalk-0.5.1.tgz", + "integrity": "sha1-Zjs6ZItotV0EaQ1JFnqoN4WPIXQ=", + "dev": true, + "requires": { + "ansi-styles": "1.1.0", + "escape-string-regexp": "1.0.5", + "has-ansi": "0.1.0", + "strip-ansi": "0.3.0", + "supports-color": "0.2.0" + } + }, + "gulp-util": { + "version": "2.2.20", + "resolved": "https://registry.npmjs.org/gulp-util/-/gulp-util-2.2.20.tgz", + "integrity": "sha1-1xRuVyiRC9jwR6awseVJvCLb1kw=", + "dev": true, + "requires": { + "chalk": "0.5.1", + "dateformat": "1.0.12", + "lodash._reinterpolate": "2.4.1", + "lodash.template": "2.4.1", + "minimist": "0.2.0", + "multipipe": "0.1.2", + "through2": "0.5.1", + "vinyl": "0.2.3" + }, + "dependencies": { + "through2": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/through2/-/through2-0.5.1.tgz", + "integrity": "sha1-390BLrnHAOIyP9M084rGIqs3Lac=", + "dev": true, + "requires": { + "readable-stream": "1.0.34", + "xtend": "3.0.0" + } + } + } + }, + "has-ansi": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-0.1.0.tgz", + "integrity": "sha1-hPJlqujA5qiKEtcCKJS3VoiUxi4=", + "dev": true, + "requires": { + "ansi-regex": "0.2.1" + } + }, + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", + "dev": true + }, + "lodash._reinterpolate": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/lodash._reinterpolate/-/lodash._reinterpolate-2.4.1.tgz", + "integrity": "sha1-TxInqlqHEfxjL1sHofRgequLMiI=", + "dev": true + }, + "lodash.escape": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/lodash.escape/-/lodash.escape-2.4.1.tgz", + "integrity": "sha1-LOEsXghNsKV92l5dHu659dF1o7Q=", + "dev": true, + "requires": { + "lodash._escapehtmlchar": "2.4.1", + "lodash._reunescapedhtml": "2.4.1", + "lodash.keys": "2.4.1" + } + }, + "lodash.keys": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-2.4.1.tgz", + "integrity": "sha1-SN6kbfj/djKxDXBrissmWR4rNyc=", + "dev": true, + "requires": { + "lodash._isnative": "2.4.1", + "lodash._shimkeys": "2.4.1", + "lodash.isobject": "2.4.1" + } + }, + "lodash.template": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/lodash.template/-/lodash.template-2.4.1.tgz", + "integrity": "sha1-nmEQB+32KRKal0qzxIuBez4c8g0=", + "dev": true, + "requires": { + "lodash._escapestringchar": "2.4.1", + "lodash._reinterpolate": "2.4.1", + "lodash.defaults": "2.4.1", + "lodash.escape": "2.4.1", + "lodash.keys": "2.4.1", + "lodash.templatesettings": "2.4.1", + "lodash.values": "2.4.1" + } + }, + "lodash.templatesettings": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/lodash.templatesettings/-/lodash.templatesettings-2.4.1.tgz", + "integrity": "sha1-6nbHXRHrhtTb6JqDiTu4YZKaxpk=", + "dev": true, + "requires": { + "lodash._reinterpolate": "2.4.1", + "lodash.escape": "2.4.1" + } + }, + "minimist": { + "version": "0.2.0", + "resolved": "http://registry.npmjs.org/minimist/-/minimist-0.2.0.tgz", + "integrity": "sha1-Tf/lJdriuGTGbC4jxicdev3s784=", + "dev": true + }, + "object-keys": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-0.4.0.tgz", + "integrity": "sha1-KKaq50KN0sOpLz2V8hM13SBOAzY=", + "dev": true + }, + "readable-stream": { + "version": "1.0.34", + "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", + "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", + "dev": true, + "requires": { + "core-util-is": "1.0.2", + "inherits": "2.0.3", + "isarray": "0.0.1", + "string_decoder": "0.10.31" + } + }, + "string_decoder": { + "version": "0.10.31", + "resolved": "http://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", + "dev": true + }, + "strip-ansi": { + "version": "0.3.0", + "resolved": "http://registry.npmjs.org/strip-ansi/-/strip-ansi-0.3.0.tgz", + "integrity": "sha1-JfSOoiynkYfzF0pNuHWTR7sSYiA=", + "dev": true, + "requires": { + "ansi-regex": "0.2.1" + } + }, + "supports-color": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-0.2.0.tgz", + "integrity": "sha1-2S3iaU6z9nMjlz1649i1W0wiGQo=", + "dev": true + }, + "through2": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/through2/-/through2-0.4.2.tgz", + "integrity": "sha1-2/WGYDEVHsg1K7bE22SiKSqEC5s=", + "dev": true, + "requires": { + "readable-stream": "1.0.34", + "xtend": "2.1.2" + }, + "dependencies": { + "xtend": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-2.1.2.tgz", + "integrity": "sha1-bv7MKk2tjmlixJAbM3znuoe10os=", + "dev": true, + "requires": { + "object-keys": "0.4.0" + } + } + } + }, + "vinyl": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-0.2.3.tgz", + "integrity": "sha1-vKk4IJWC7FpJrVOKAPofEl5RMlI=", + "dev": true, + "requires": { + "clone-stats": "0.0.1" + } + }, + "xtend": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-3.0.0.tgz", + "integrity": "sha1-XM50B7r2Qsunvs2laBEcST9ZZlo=", + "dev": true + } + } + }, + "gulp-rename": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/gulp-rename/-/gulp-rename-1.4.0.tgz", + "integrity": "sha512-swzbIGb/arEoFK89tPY58vg3Ok1bw+d35PfUNwWqdo7KM4jkmuGA78JiDNqR+JeZFaeeHnRg9N7aihX3YPmsyg==", + "dev": true + }, + "gulp-sourcemaps": { + "version": "2.6.4", + "resolved": "https://registry.npmjs.org/gulp-sourcemaps/-/gulp-sourcemaps-2.6.4.tgz", + "integrity": "sha1-y7IAhFCxvM5s0jv5gze+dRv24wo=", + "dev": true, + "requires": { + "@gulp-sourcemaps/identity-map": "1.0.2", + "@gulp-sourcemaps/map-sources": "1.0.0", + "acorn": "5.2.1", + "convert-source-map": "1.5.1", + "css": "2.2.4", + "debug-fabulous": "1.1.0", + "detect-newline": "2.1.0", + "graceful-fs": "4.1.11", + "source-map": "0.6.1", + "strip-bom-string": "1.0.0", + "through2": "2.0.3" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } + } + }, + "gulp-tslint": { + "version": "8.1.2", + "resolved": "https://registry.npmjs.org/gulp-tslint/-/gulp-tslint-8.1.2.tgz", + "integrity": "sha1-4PQxlLRz1+drtFpY/oxg59/jvrI=", + "dev": true, + "requires": { + "gulp-util": "3.0.8", + "map-stream": "0.0.7", + "through": "2.3.8" + }, + "dependencies": { + "gulp-util": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/gulp-util/-/gulp-util-3.0.8.tgz", + "integrity": "sha1-AFTh50RQLifATBh8PsxQXdVLu08=", + "dev": true, + "requires": { + "array-differ": "1.0.0", + "array-uniq": "1.0.3", + "beeper": "1.1.1", + "chalk": "1.1.3", + "dateformat": "2.0.0", + "fancy-log": "1.3.0", + "gulplog": "1.0.0", + "has-gulplog": "0.1.0", + "lodash._reescape": "3.0.0", + "lodash._reevaluate": "3.0.0", + "lodash._reinterpolate": "3.0.0", + "lodash.template": "3.6.2", + "minimist": "1.2.0", + "multipipe": "0.1.2", + "object-assign": "3.0.0", + "replace-ext": "0.0.1", + "through2": "2.0.3", + "vinyl": "0.5.3" + }, + "dependencies": { + "array-differ": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/array-differ/-/array-differ-1.0.0.tgz", + "integrity": "sha1-7/UuN1gknTO+QCuLuOVkuytdQDE=", + "dev": true + }, + "array-uniq": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", + "integrity": "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=", + "dev": true + }, + "beeper": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/beeper/-/beeper-1.1.1.tgz", + "integrity": "sha1-5tXqjF2tABMEpwsiY4RH9pyy+Ak=", + "dev": true + }, + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "dev": true, + "requires": { + "ansi-styles": "2.2.1", + "escape-string-regexp": "1.0.5", + "has-ansi": "2.0.0", + "strip-ansi": "3.0.1", + "supports-color": "2.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", + "dev": true + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true + }, + "has-ansi": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", + "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", + "dev": true, + "requires": { + "ansi-regex": "2.1.1" + }, + "dependencies": { + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true + } + } + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "dev": true, + "requires": { + "ansi-regex": "2.1.1" + }, + "dependencies": { + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true + } + } + }, + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", + "dev": true + } + } + }, + "dateformat": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-2.0.0.tgz", + "integrity": "sha1-J0Pjq7XD/CRi5SfcpEXgTp9N7hc=", + "dev": true + }, + "fancy-log": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/fancy-log/-/fancy-log-1.3.0.tgz", + "integrity": "sha1-Rb4X0Cu5kX1gzP/UmVyZnmyMmUg=", + "dev": true, + "requires": { + "chalk": "1.1.3", + "time-stamp": "1.1.0" + }, + "dependencies": { + "time-stamp": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/time-stamp/-/time-stamp-1.1.0.tgz", + "integrity": "sha1-dkpaEa9QVhkhsTPztE5hhofg9cM=", + "dev": true + } + } + }, + "gulplog": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/gulplog/-/gulplog-1.0.0.tgz", + "integrity": "sha1-4oxNRdBey77YGDY86PnFkmIp/+U=", + "dev": true, + "requires": { + "glogg": "1.0.0" + }, + "dependencies": { + "glogg": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/glogg/-/glogg-1.0.0.tgz", + "integrity": "sha1-f+DxmfV6yQbPUS/urY+Q7kooT8U=", + "dev": true, + "requires": { + "sparkles": "1.0.0" + }, + "dependencies": { + "sparkles": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/sparkles/-/sparkles-1.0.0.tgz", + "integrity": "sha1-Gsu/tZJDbRC76PeFt8xvgoFQEsM=", + "dev": true + } + } + } + } + }, + "has-gulplog": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/has-gulplog/-/has-gulplog-0.1.0.tgz", + "integrity": "sha1-ZBTIKRNpfaUVkDl9r7EvIpZ4Ec4=", + "dev": true, + "requires": { + "sparkles": "1.0.0" + }, + "dependencies": { + "sparkles": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/sparkles/-/sparkles-1.0.0.tgz", + "integrity": "sha1-Gsu/tZJDbRC76PeFt8xvgoFQEsM=", + "dev": true + } + } + }, + "lodash._reescape": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/lodash._reescape/-/lodash._reescape-3.0.0.tgz", + "integrity": "sha1-Kx1vXf4HyKNVdT5fJ/rH8c3hYWo=", + "dev": true + }, + "lodash._reevaluate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/lodash._reevaluate/-/lodash._reevaluate-3.0.0.tgz", + "integrity": "sha1-WLx0xAZklTrgsSTYBpltrKQx4u0=", + "dev": true + }, + "lodash._reinterpolate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz", + "integrity": "sha1-DM8tiRZq8Ds2Y8eWU4t1rG4RTZ0=", + "dev": true + }, + "lodash.template": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/lodash.template/-/lodash.template-3.6.2.tgz", + "integrity": "sha1-+M3sxhaaJVvpCYrosMU9N4kx0U8=", + "dev": true, + "requires": { + "lodash._basecopy": "3.0.1", + "lodash._basetostring": "3.0.1", + "lodash._basevalues": "3.0.0", + "lodash._isiterateecall": "3.0.9", + "lodash._reinterpolate": "3.0.0", + "lodash.escape": "3.2.0", + "lodash.keys": "3.1.2", + "lodash.restparam": "3.6.1", + "lodash.templatesettings": "3.1.1" + }, + "dependencies": { + "lodash._basecopy": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/lodash._basecopy/-/lodash._basecopy-3.0.1.tgz", + "integrity": "sha1-jaDmqHbPNEwK2KVIghEd08XHyjY=", + "dev": true + }, + "lodash._basetostring": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/lodash._basetostring/-/lodash._basetostring-3.0.1.tgz", + "integrity": "sha1-0YYdh3+CSlL2aYMtyvPuFVZqB9U=", + "dev": true + }, + "lodash._basevalues": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/lodash._basevalues/-/lodash._basevalues-3.0.0.tgz", + "integrity": "sha1-W3dXYoAr3j0yl1A+JjAIIP32Ybc=", + "dev": true + }, + "lodash._isiterateecall": { + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/lodash._isiterateecall/-/lodash._isiterateecall-3.0.9.tgz", + "integrity": "sha1-UgOte6Ql+uhCRg5pbbnPPmqsBXw=", + "dev": true + }, + "lodash.escape": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/lodash.escape/-/lodash.escape-3.2.0.tgz", + "integrity": "sha1-mV7g3BjBtIzJLv+ucaEKq1tIdpg=", + "dev": true, + "requires": { + "lodash._root": "3.0.1" + }, + "dependencies": { + "lodash._root": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/lodash._root/-/lodash._root-3.0.1.tgz", + "integrity": "sha1-+6HEUkwZ7ppfgTa0YJ8BfPTe1pI=", + "dev": true + } + } + }, + "lodash.keys": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-3.1.2.tgz", + "integrity": "sha1-TbwEcrFWvlCgsoaFXRvQsMZWCYo=", + "dev": true, + "requires": { + "lodash._getnative": "3.9.1", + "lodash.isarguments": "3.1.0", + "lodash.isarray": "3.0.4" + }, + "dependencies": { + "lodash._getnative": { + "version": "3.9.1", + "resolved": "https://registry.npmjs.org/lodash._getnative/-/lodash._getnative-3.9.1.tgz", + "integrity": "sha1-VwvH3t5G1hzc3mh9ZdPuy6o6r/U=", + "dev": true + }, + "lodash.isarguments": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz", + "integrity": "sha1-L1c9hcaiQon/AGY7SRwdM4/zRYo=", + "dev": true + }, + "lodash.isarray": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/lodash.isarray/-/lodash.isarray-3.0.4.tgz", + "integrity": "sha1-eeTriMNqgSKvhvhEqpvNhRtfu1U=", + "dev": true + } + } + }, + "lodash.restparam": { + "version": "3.6.1", + "resolved": "https://registry.npmjs.org/lodash.restparam/-/lodash.restparam-3.6.1.tgz", + "integrity": "sha1-k2pOMJ7zMKdkXtQUWYbIWuWyCAU=", + "dev": true + }, + "lodash.templatesettings": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/lodash.templatesettings/-/lodash.templatesettings-3.1.1.tgz", + "integrity": "sha1-+zB4RHU7Zrnxr6VOJix0UwfbqOU=", + "dev": true, + "requires": { + "lodash._reinterpolate": "3.0.0", + "lodash.escape": "3.2.0" + } + } + } + }, + "minimist": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", + "dev": true + }, + "multipipe": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/multipipe/-/multipipe-0.1.2.tgz", + "integrity": "sha1-Ko8t33Du1WTf8tV/HhoTfZ8FB4s=", + "dev": true, + "requires": { + "duplexer2": "0.0.2" + }, + "dependencies": { + "duplexer2": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.0.2.tgz", + "integrity": "sha1-xhTc9n4vsUmVqRcR5aYX6KYKMds=", + "dev": true, + "requires": { + "readable-stream": "1.1.14" + }, + "dependencies": { + "readable-stream": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", + "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", + "dev": true, + "requires": { + "core-util-is": "1.0.2", + "inherits": "2.0.3", + "isarray": "0.0.1", + "string_decoder": "0.10.31" + }, + "dependencies": { + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", + "dev": true + }, + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", + "dev": true + }, + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", + "dev": true + }, + "string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", + "dev": true + } + } + } + } + } + } + }, + "object-assign": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-3.0.0.tgz", + "integrity": "sha1-m+3VygiXlJvKR+f/QIBi1Un1h/I=", + "dev": true + }, + "replace-ext": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-0.0.1.tgz", + "integrity": "sha1-KbvZIHinOfC8zitO5B6DeVNSKSQ=", + "dev": true + }, + "through2": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.3.tgz", + "integrity": "sha1-AARWmzfHx0ujnEPzzteNGtlBQL4=", + "dev": true, + "requires": { + "readable-stream": "2.3.3", + "xtend": "4.0.1" + }, + "dependencies": { + "readable-stream": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.3.tgz", + "integrity": "sha1-No8lEtefnUb9/HE0mueHi7weuVw=", + "dev": true, + "requires": { + "core-util-is": "1.0.2", + "inherits": "2.0.3", + "isarray": "1.0.0", + "process-nextick-args": "1.0.7", + "safe-buffer": "5.1.1", + "string_decoder": "1.0.3", + "util-deprecate": "1.0.2" + }, + "dependencies": { + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", + "dev": true + }, + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", + "dev": true + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "process-nextick-args": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz", + "integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M=", + "dev": true + }, + "safe-buffer": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", + "integrity": "sha1-iTMSr2myEj3vcfV4iQAWce6yyFM=", + "dev": true + }, + "string_decoder": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", + "integrity": "sha1-D8Z9fBQYJd6UKC3VNr7GubzoYKs=", + "dev": true, + "requires": { + "safe-buffer": "5.1.1" + } + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", + "dev": true + } + } + }, + "xtend": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz", + "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=", + "dev": true + } + } + }, + "vinyl": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-0.5.3.tgz", + "integrity": "sha1-sEVbOPxeDPMNQyUTLkYZcMIJHN4=", + "dev": true, + "requires": { + "clone": "1.0.2", + "clone-stats": "0.0.1", + "replace-ext": "0.0.1" + }, + "dependencies": { + "clone": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.2.tgz", + "integrity": "sha1-Jgt6meux7f4kdTgXX3gyQ8sZ0Uk=", + "dev": true + }, + "clone-stats": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/clone-stats/-/clone-stats-0.0.1.tgz", + "integrity": "sha1-uI+UqCzzi4eR1YBG6kAprYjKmdE=", + "dev": true + } + } + } + } + }, + "map-stream": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/map-stream/-/map-stream-0.0.7.tgz", + "integrity": "sha1-ih8HiW2CsQkmvTdEokIACfiJdKg=", + "dev": true + }, + "through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", + "dev": true + } + } + }, + "gulp-typescript": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/gulp-typescript/-/gulp-typescript-3.2.3.tgz", + "integrity": "sha512-Np2sJXgtDUwIAoMtlJ9uXsVmpu1FWXlKZw164hLuo56uJa7qo5W2KZ0yAYiYH/HUsaz5L0O2toMOcLIokpFCPg==", + "dev": true, + "requires": { + "gulp-util": "3.0.8", + "source-map": "0.5.7", + "through2": "2.0.3", + "vinyl-fs": "2.4.4" + } + }, + "gulp-uglify": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/gulp-uglify/-/gulp-uglify-3.0.1.tgz", + "integrity": "sha512-KVffbGY9d4Wv90bW/B1KZJyunLMyfHTBbilpDvmcrj5Go0/a1G3uVpt+1gRBWSw/11dqR3coJ1oWNTt1AiXuWQ==", + "dev": true, + "requires": { + "gulplog": "1.0.0", + "has-gulplog": "0.1.0", + "lodash": "4.17.11", + "make-error-cause": "1.2.2", + "safe-buffer": "5.1.2", + "through2": "2.0.3", + "uglify-js": "3.4.9", + "vinyl-sourcemaps-apply": "0.2.1" + }, + "dependencies": { + "commander": { + "version": "2.17.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.17.1.tgz", + "integrity": "sha512-wPMUt6FnH2yzG95SA6mzjQOEKUU3aLaDEmzs1ti+1E9h+CsrZghRlqEM/EJ4KscsQVG8uNN4uVreUeT8+drlgg==", + "dev": true + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "uglify-js": { + "version": "3.4.9", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.4.9.tgz", + "integrity": "sha512-8CJsbKOtEbnJsTyv6LE6m6ZKniqMiFWmm9sRbopbkGs3gMPPfd3Fh8iIA4Ykv5MgaTbqHr4BaoGLJLZNhsrW1Q==", + "dev": true, + "requires": { + "commander": "2.17.1", + "source-map": "0.6.1" + } + } + } + }, + "gulp-util": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/gulp-util/-/gulp-util-3.0.8.tgz", + "integrity": "sha1-AFTh50RQLifATBh8PsxQXdVLu08=", + "dev": true, + "requires": { + "array-differ": "1.0.0", + "array-uniq": "1.0.3", + "beeper": "1.1.1", + "chalk": "1.1.3", + "dateformat": "2.2.0", + "fancy-log": "1.3.0", + "gulplog": "1.0.0", + "has-gulplog": "0.1.0", + "lodash._reescape": "3.0.0", + "lodash._reevaluate": "3.0.0", + "lodash._reinterpolate": "3.0.0", + "lodash.template": "3.6.2", + "minimist": "1.2.0", + "multipipe": "0.1.2", + "object-assign": "3.0.0", + "replace-ext": "0.0.1", + "through2": "2.0.3", + "vinyl": "0.5.3" + }, + "dependencies": { + "dateformat": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-2.2.0.tgz", + "integrity": "sha1-QGXiATz5+5Ft39gu+1Bq1MZ2kGI=", + "dev": true + }, + "object-assign": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-3.0.0.tgz", + "integrity": "sha1-m+3VygiXlJvKR+f/QIBi1Un1h/I=", + "dev": true + } + } + }, + "gulplog": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/gulplog/-/gulplog-1.0.0.tgz", + "integrity": "sha1-4oxNRdBey77YGDY86PnFkmIp/+U=", + "dev": true, + "requires": { + "glogg": "1.0.0" + } + }, + "handlebars": { + "version": "4.0.12", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.0.12.tgz", + "integrity": "sha512-RhmTekP+FZL+XNhwS1Wf+bTTZpdLougwt5pcgA1tuz6Jcx0fpH/7z0qd71RKnZHBCxIRBHfBOnio4gViPemNzA==", + "dev": true, + "requires": { + "async": "2.6.0", + "optimist": "0.6.1", + "source-map": "0.6.1", + "uglify-js": "3.4.9" + }, + "dependencies": { + "commander": { + "version": "2.17.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.17.1.tgz", + "integrity": "sha512-wPMUt6FnH2yzG95SA6mzjQOEKUU3aLaDEmzs1ti+1E9h+CsrZghRlqEM/EJ4KscsQVG8uNN4uVreUeT8+drlgg==", + "dev": true, + "optional": true + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "uglify-js": { + "version": "3.4.9", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.4.9.tgz", + "integrity": "sha512-8CJsbKOtEbnJsTyv6LE6m6ZKniqMiFWmm9sRbopbkGs3gMPPfd3Fh8iIA4Ykv5MgaTbqHr4BaoGLJLZNhsrW1Q==", + "dev": true, + "optional": true, + "requires": { + "commander": "2.17.1", + "source-map": "0.6.1" + } + } + } + }, + "har-schema": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", + "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=", + "dev": true + }, + "har-validator": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz", + "integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==", + "dev": true, + "requires": { + "ajv": "6.6.1", + "har-schema": "2.0.0" + } + }, + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "requires": { + "function-bind": "1.1.1" + } + }, + "has-ansi": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", + "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", + "dev": true, + "requires": { + "ansi-regex": "2.1.1" + } + }, + "has-flag": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-2.0.0.tgz", + "integrity": "sha1-6CB68cx7MNRGzHC3NLXovhj4jVE=", + "dev": true + }, + "has-gulplog": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/has-gulplog/-/has-gulplog-0.1.0.tgz", + "integrity": "sha1-ZBTIKRNpfaUVkDl9r7EvIpZ4Ec4=", + "dev": true, + "requires": { + "sparkles": "1.0.0" + } + }, + "has-symbols": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.0.tgz", + "integrity": "sha1-uhqPGvKg/DllD1yFA2dwQSIGO0Q=", + "dev": true + }, + "has-value": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz", + "integrity": "sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc=", + "dev": true, + "requires": { + "get-value": "2.0.6", + "has-values": "1.0.0", + "isobject": "3.0.1" + }, + "dependencies": { + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true + } + } + }, + "has-values": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz", + "integrity": "sha1-lbC2P+whRmGab+V/51Yo1aOe/k8=", + "dev": true, + "requires": { + "is-number": "3.0.0", + "kind-of": "4.0.0" + }, + "dependencies": { + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "dev": true, + "requires": { + "kind-of": "3.2.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "1.1.6" + } + } + } + }, + "kind-of": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", + "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=", + "dev": true, + "requires": { + "is-buffer": "1.1.6" + } + } + } + }, + "hash-base": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-2.0.2.tgz", + "integrity": "sha1-ZuodhW206KVHDK32/OI65SRO8uE=", + "dev": true, + "requires": { + "inherits": "2.0.3" + } + }, + "hash.js": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.3.tgz", + "integrity": "sha512-/UETyP0W22QILqS+6HowevwhEFJ3MBJnwTf75Qob9Wz9t0DPuisL8kW8YZMK62dHAKE1c1p+gY1TtOLY+USEHA==", + "dev": true, + "requires": { + "inherits": "2.0.3", + "minimalistic-assert": "1.0.0" + } + }, + "hmac-drbg": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", + "integrity": "sha1-0nRXAQJabHdabFRXk+1QL8DGSaE=", + "dev": true, + "requires": { + "hash.js": "1.1.3", + "minimalistic-assert": "1.0.0", + "minimalistic-crypto-utils": "1.0.1" + } + }, + "home-or-tmp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/home-or-tmp/-/home-or-tmp-2.0.0.tgz", + "integrity": "sha1-42w/LSyufXRqhX440Y1fMqeILbg=", + "dev": true, + "requires": { + "os-homedir": "1.0.2", + "os-tmpdir": "1.0.2" + } + }, + "homedir-polyfill": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.1.tgz", + "integrity": "sha1-TCu8inWJmP7r9e1oWA921GdotLw=", + "dev": true, + "requires": { + "parse-passwd": "1.0.0" + } + }, + "hosted-git-info": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.5.0.tgz", + "integrity": "sha512-pNgbURSuab90KbTqvRPsseaTxOJCZBD0a7t+haSN33piP9cCM4l0CqdzAif2hUqm716UovKB2ROmiabGAKVXyg==", + "dev": true + }, + "html-encoding-sniffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-1.0.2.tgz", + "integrity": "sha512-71lZziiDnsuabfdYiUeWdCVyKuqwWi23L8YeIgV9jSSZHCtb6wB1BKWooH7L3tn4/FuZJMVWyNaIDr4RGmaSYw==", + "dev": true, + "requires": { + "whatwg-encoding": "1.0.5" + } + }, + "http-signature": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", + "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", + "dev": true, + "requires": { + "assert-plus": "1.0.0", + "jsprim": "1.4.1", + "sshpk": "1.15.2" + } + }, + "https-browserify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/https-browserify/-/https-browserify-1.0.0.tgz", + "integrity": "sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM=", + "dev": true + }, + "iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dev": true, + "requires": { + "safer-buffer": "2.1.2" + } + }, + "ieee754": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.8.tgz", + "integrity": "sha1-vjPUCsEO8ZJnAfbwii2G+/0a0+Q=", + "dev": true + }, + "import-local": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-1.0.0.tgz", + "integrity": "sha512-vAaZHieK9qjGo58agRBg+bhHX3hoTZU/Oa3GESWLz7t1U62fk63aHuDJJEteXoDeTCcPmUT+z38gkHPZkkmpmQ==", + "dev": true, + "requires": { + "pkg-dir": "2.0.0", + "resolve-cwd": "2.0.0" + } + }, + "imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", + "dev": true + }, + "indent-string": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-2.1.0.tgz", + "integrity": "sha1-ji1INIdCEhtKghi3oTfppSBJ3IA=", + "dev": true, + "requires": { + "repeating": "2.0.1" + } + }, + "indexof": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/indexof/-/indexof-0.0.1.tgz", + "integrity": "sha1-gtwzbSMrkGIXnQWrMpOmYFn9Q10=", + "dev": true + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "requires": { + "once": "1.4.0", + "wrappy": "1.0.2" + } + }, + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", + "dev": true + }, + "ini": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", + "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==", + "dev": true + }, + "interpret": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.1.0.tgz", + "integrity": "sha1-ftGxQQxqDg94z5XTuEQMY/eLhhQ=", + "dev": true + }, + "invariant": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", + "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", + "dev": true, + "requires": { + "loose-envify": "1.4.0" + } + }, + "invert-kv": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-1.0.0.tgz", + "integrity": "sha1-EEqOSqym09jNFXqO+L+rLXo//bY=", + "dev": true + }, + "is-absolute": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-absolute/-/is-absolute-1.0.0.tgz", + "integrity": "sha512-dOWoqflvcydARa360Gvv18DZ/gRuHKi2NU/wU5X1ZFzdYfH29nkiNZsF3mp4OJ3H4yo9Mx8A/uAGNzpzPN3yBA==", + "dev": true, + "requires": { + "is-relative": "1.0.0", + "is-windows": "1.0.2" + } + }, + "is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", + "dev": true, + "requires": { + "kind-of": "3.2.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "1.1.6" + } + } + } + }, + "is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", + "dev": true + }, + "is-binary-path": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz", + "integrity": "sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg=", + "dev": true, + "requires": { + "binary-extensions": "1.11.0" + } + }, + "is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", + "dev": true + }, + "is-builtin-module": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-1.0.0.tgz", + "integrity": "sha1-VAVy0096wxGfj3bDDLwbHgN6/74=", + "dev": true, + "requires": { + "builtin-modules": "1.1.1" + } + }, + "is-callable": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.4.tgz", + "integrity": "sha512-r5p9sxJjYnArLjObpjA4xu5EKI3CuKHkJXMhT7kwbpUyIFD1n5PMAsoPvWnvtZiNz7LjkYDRZhd7FlI0eMijEA==", + "dev": true + }, + "is-ci": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-1.2.1.tgz", + "integrity": "sha512-s6tfsaQaQi3JNciBH6shVqEDvhGut0SUXr31ag8Pd8BBbVVlcGfWhpPmEOoM6RJ5TFhbypvf5yyRw/VXW1IiWg==", + "dev": true, + "requires": { + "ci-info": "1.6.0" + } + }, + "is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", + "dev": true, + "requires": { + "kind-of": "3.2.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "1.1.6" + } + } + } + }, + "is-date-object": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.1.tgz", + "integrity": "sha1-mqIOtq7rv/d/vTPnTKAbM1gdOhY=", + "dev": true + }, + "is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "0.1.6", + "is-data-descriptor": "0.1.4", + "kind-of": "5.1.0" + }, + "dependencies": { + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "dev": true + } + } + }, + "is-dotfile": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/is-dotfile/-/is-dotfile-1.0.3.tgz", + "integrity": "sha1-pqLzL/0t+wT1yiXs0Pa4PPeYoeE=", + "dev": true + }, + "is-equal-shallow": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/is-equal-shallow/-/is-equal-shallow-0.1.3.tgz", + "integrity": "sha1-IjgJj8Ih3gvPpdnqxMRdY4qhxTQ=", + "dev": true, + "requires": { + "is-primitive": "2.0.0" + } + }, + "is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", + "dev": true + }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "dev": true + }, + "is-finite": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-finite/-/is-finite-1.0.2.tgz", + "integrity": "sha1-zGZ3aVYCvlUO8R6LSqYwU0K20Ko=", + "dev": true, + "requires": { + "number-is-nan": "1.0.1" + } + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "dev": true, + "requires": { + "number-is-nan": "1.0.1" + } + }, + "is-generator-fn": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-1.0.0.tgz", + "integrity": "sha1-lp1J4bszKfa7fwkIm+JleLLd1Go=", + "dev": true + }, + "is-glob": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", + "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", + "dev": true, + "requires": { + "is-extglob": "2.1.1" + } + }, + "is-negated-glob": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-negated-glob/-/is-negated-glob-1.0.0.tgz", + "integrity": "sha1-aRC8pdqMleeEtXUbl2z1oQ/uNtI=", + "dev": true + }, + "is-number": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-2.1.0.tgz", + "integrity": "sha1-Afy7s5NGOlSPL0ZszhbezknbkI8=", + "dev": true, + "requires": { + "kind-of": "3.2.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "1.1.6" + } + } + } + }, + "is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "dev": true, + "requires": { + "isobject": "3.0.1" + }, + "dependencies": { + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true + } + } + }, + "is-posix-bracket": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-posix-bracket/-/is-posix-bracket-0.1.1.tgz", + "integrity": "sha1-MzTceXdDaOkvAW5vvAqI9c1ua8Q=", + "dev": true + }, + "is-primitive": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-primitive/-/is-primitive-2.0.0.tgz", + "integrity": "sha1-IHurkWOEmcB7Kt8kCkGochADRXU=", + "dev": true + }, + "is-promise": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.1.0.tgz", + "integrity": "sha1-eaKp7OfwlugPNtKy87wWwf9L8/o=", + "dev": true + }, + "is-regex": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.4.tgz", + "integrity": "sha1-VRdIm1RwkbCTDglWVM7SXul+lJE=", + "dev": true, + "requires": { + "has": "1.0.3" + } + }, + "is-relative": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-relative/-/is-relative-1.0.0.tgz", + "integrity": "sha512-Kw/ReK0iqwKeu0MITLFuj0jbPAmEiOsIwyIXvvbfa6QfmN9pkD1M+8pdk7Rl/dTKbH34/XBFMbgD4iMJhLQbGA==", + "dev": true, + "requires": { + "is-unc-path": "1.0.0" + } + }, + "is-stream": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", + "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=", + "dev": true + }, + "is-symbol": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.2.tgz", + "integrity": "sha512-HS8bZ9ox60yCJLH9snBpIwv9pYUAkcuLhSA1oero1UB5y9aiQpRA8y2ex945AOtCZL1lJDeIk3G5LthswI46Lw==", + "dev": true, + "requires": { + "has-symbols": "1.0.0" + } + }, + "is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", + "dev": true + }, + "is-unc-path": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-unc-path/-/is-unc-path-1.0.0.tgz", + "integrity": "sha512-mrGpVd0fs7WWLfVsStvgF6iEJnbjDFZh9/emhRDcGWTduTfNHd9CHeUwH3gYIjdbwo4On6hunkztwOaAw0yllQ==", + "dev": true, + "requires": { + "unc-path-regex": "0.1.2" + } + }, + "is-utf8": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz", + "integrity": "sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=", + "dev": true + }, + "is-valid-glob": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/is-valid-glob/-/is-valid-glob-0.3.0.tgz", + "integrity": "sha1-1LVcafUYhvm2XHDWwmItN+KfSP4=", + "dev": true + }, + "is-windows": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", + "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", + "dev": true + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true + }, + "isobject": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", + "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", + "dev": true, + "requires": { + "isarray": "1.0.0" + } + }, + "isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=", + "dev": true + }, + "istanbul-api": { + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/istanbul-api/-/istanbul-api-1.3.7.tgz", + "integrity": "sha512-4/ApBnMVeEPG3EkSzcw25wDe4N66wxwn+KKn6b47vyek8Xb3NBAcg4xfuQbS7BqcZuTX4wxfD5lVagdggR3gyA==", + "dev": true, + "requires": { + "async": "2.6.0", + "fileset": "2.0.3", + "istanbul-lib-coverage": "1.2.1", + "istanbul-lib-hook": "1.2.2", + "istanbul-lib-instrument": "1.10.2", + "istanbul-lib-report": "1.1.5", + "istanbul-lib-source-maps": "1.2.6", + "istanbul-reports": "1.5.1", + "js-yaml": "3.12.0", + "mkdirp": "0.5.1", + "once": "1.4.0" + } + }, + "istanbul-lib-coverage": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-1.2.1.tgz", + "integrity": "sha512-PzITeunAgyGbtY1ibVIUiV679EFChHjoMNRibEIobvmrCRaIgwLxNucOSimtNWUhEib/oO7QY2imD75JVgCJWQ==", + "dev": true + }, + "istanbul-lib-hook": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-hook/-/istanbul-lib-hook-1.2.2.tgz", + "integrity": "sha512-/Jmq7Y1VeHnZEQ3TL10VHyb564mn6VrQXHchON9Jf/AEcmQ3ZIiyD1BVzNOKTZf/G3gE+kiGK6SmpF9y3qGPLw==", + "dev": true, + "requires": { + "append-transform": "0.4.0" + } + }, + "istanbul-lib-instrument": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-1.10.2.tgz", + "integrity": "sha512-aWHxfxDqvh/ZlxR8BBaEPVSWDPUkGD63VjGQn3jcw8jCp7sHEMKcrj4xfJn/ABzdMEHiQNyvDQhqm5o8+SQg7A==", + "dev": true, + "requires": { + "babel-generator": "6.26.1", + "babel-template": "6.26.0", + "babel-traverse": "6.26.0", + "babel-types": "6.26.0", + "babylon": "6.18.0", + "istanbul-lib-coverage": "1.2.1", + "semver": "5.6.0" + } + }, + "istanbul-lib-report": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-1.1.5.tgz", + "integrity": "sha512-UsYfRMoi6QO/doUshYNqcKJqVmFe9w51GZz8BS3WB0lYxAllQYklka2wP9+dGZeHYaWIdcXUx8JGdbqaoXRXzw==", + "dev": true, + "requires": { + "istanbul-lib-coverage": "1.2.1", + "mkdirp": "0.5.1", + "path-parse": "1.0.5", + "supports-color": "3.2.3" + }, + "dependencies": { + "has-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", + "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", + "dev": true + }, + "supports-color": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", + "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", + "dev": true, + "requires": { + "has-flag": "1.0.0" + } + } + } + }, + "istanbul-lib-source-maps": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-1.2.6.tgz", + "integrity": "sha512-TtbsY5GIHgbMsMiRw35YBHGpZ1DVFEO19vxxeiDMYaeOFOCzfnYVxvl6pOUIZR4dtPhAGpSMup8OyF8ubsaqEg==", + "dev": true, + "requires": { + "debug": "3.2.6", + "istanbul-lib-coverage": "1.2.1", + "mkdirp": "0.5.1", + "rimraf": "2.6.2", + "source-map": "0.5.7" + }, + "dependencies": { + "debug": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "dev": true, + "requires": { + "ms": "2.1.1" + } + }, + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", + "dev": true + } + } + }, + "istanbul-reports": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-1.5.1.tgz", + "integrity": "sha512-+cfoZ0UXzWjhAdzosCPP3AN8vvef8XDkWtTfgaN+7L3YTpNYITnCaEkceo5SEYy644VkHka/P1FvkWvrG/rrJw==", + "dev": true, + "requires": { + "handlebars": "4.0.12" + } + }, + "jest": { + "version": "23.6.0", + "resolved": "https://registry.npmjs.org/jest/-/jest-23.6.0.tgz", + "integrity": "sha512-lWzcd+HSiqeuxyhG+EnZds6iO3Y3ZEnMrfZq/OTGvF/C+Z4fPMCdhWTGSAiO2Oym9rbEXfwddHhh6jqrTF3+Lw==", + "dev": true, + "requires": { + "import-local": "1.0.0", + "jest-cli": "23.6.0" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true + }, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "1.9.1" + } + }, + "camelcase": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz", + "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=", + "dev": true + }, + "chalk": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", + "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", + "dev": true, + "requires": { + "ansi-styles": "3.2.1", + "escape-string-regexp": "1.0.5", + "supports-color": "5.5.0" + } + }, + "cliui": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-4.1.0.tgz", + "integrity": "sha512-4FG+RSG9DL7uEwRUZXZn3SS34DiDPfzP0VOiEwtUWlE+AR2EIg+hSyvrIgUUfhdgR/UkAeW2QHgeP+hWrXs7jQ==", + "dev": true, + "requires": { + "string-width": "2.1.1", + "strip-ansi": "4.0.0", + "wrap-ansi": "2.1.0" + } + }, + "find-up": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", + "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", + "dev": true, + "requires": { + "locate-path": "2.0.0" + } + }, + "glob": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", + "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", + "dev": true, + "requires": { + "fs.realpath": "1.0.0", + "inflight": "1.0.6", + "inherits": "2.0.3", + "minimatch": "3.0.4", + "once": "1.4.0", + "path-is-absolute": "1.0.1" + } + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "jest-cli": { + "version": "23.6.0", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-23.6.0.tgz", + "integrity": "sha512-hgeD1zRUp1E1zsiyOXjEn4LzRLWdJBV//ukAHGlx6s5mfCNJTbhbHjgxnDUXA8fsKWN/HqFFF6X5XcCwC/IvYQ==", + "dev": true, + "requires": { + "ansi-escapes": "3.1.0", + "chalk": "2.4.1", + "exit": "0.1.2", + "glob": "7.1.3", + "graceful-fs": "4.1.11", + "import-local": "1.0.0", + "is-ci": "1.2.1", + "istanbul-api": "1.3.7", + "istanbul-lib-coverage": "1.2.1", + "istanbul-lib-instrument": "1.10.2", + "istanbul-lib-source-maps": "1.2.6", + "jest-changed-files": "23.4.2", + "jest-config": "23.6.0", + "jest-environment-jsdom": "23.4.0", + "jest-get-type": "22.4.3", + "jest-haste-map": "23.6.0", + "jest-message-util": "23.4.0", + "jest-regex-util": "23.3.0", + "jest-resolve-dependencies": "23.6.0", + "jest-runner": "23.6.0", + "jest-runtime": "23.6.0", + "jest-snapshot": "23.6.0", + "jest-util": "23.4.0", + "jest-validate": "23.6.0", + "jest-watcher": "23.4.0", + "jest-worker": "23.2.0", + "micromatch": "2.3.11", + "node-notifier": "5.3.0", + "prompts": "0.1.14", + "realpath-native": "1.0.2", + "rimraf": "2.6.2", + "slash": "1.0.0", + "string-length": "2.0.0", + "strip-ansi": "4.0.0", + "which": "1.3.0", + "yargs": "11.1.0" + } + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "requires": { + "ansi-regex": "3.0.0" + } + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "3.0.0" + } + }, + "yargs": { + "version": "11.1.0", + "resolved": "http://registry.npmjs.org/yargs/-/yargs-11.1.0.tgz", + "integrity": "sha512-NwW69J42EsCSanF8kyn5upxvjp5ds+t3+udGBeTbFnERA+lF541DDpMawzo4z6W/QrzNM18D+BPMiOBibnFV5A==", + "dev": true, + "requires": { + "cliui": "4.1.0", + "decamelize": "1.2.0", + "find-up": "2.1.0", + "get-caller-file": "1.0.2", + "os-locale": "2.1.0", + "require-directory": "2.1.1", + "require-main-filename": "1.0.1", + "set-blocking": "2.0.0", + "string-width": "2.1.1", + "which-module": "2.0.0", + "y18n": "3.2.1", + "yargs-parser": "9.0.2" + } + }, + "yargs-parser": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-9.0.2.tgz", + "integrity": "sha1-nM9qQ0YP5O1Aqbto9I1DuKaMwHc=", + "dev": true, + "requires": { + "camelcase": "4.1.0" + } + } + } + }, + "jest-changed-files": { + "version": "23.4.2", + "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-23.4.2.tgz", + "integrity": "sha512-EyNhTAUWEfwnK0Is/09LxoqNDOn7mU7S3EHskG52djOFS/z+IT0jT3h3Ql61+dklcG7bJJitIWEMB4Sp1piHmA==", + "dev": true, + "requires": { + "throat": "4.1.0" + } + }, + "jest-config": { + "version": "23.6.0", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-23.6.0.tgz", + "integrity": "sha512-i8V7z9BeDXab1+VNo78WM0AtWpBRXJLnkT+lyT+Slx/cbP5sZJ0+NDuLcmBE5hXAoK0aUp7vI+MOxR+R4d8SRQ==", + "dev": true, + "requires": { + "babel-core": "6.26.3", + "babel-jest": "23.6.0", + "chalk": "2.4.1", + "glob": "7.1.3", + "jest-environment-jsdom": "23.4.0", + "jest-environment-node": "23.4.0", + "jest-get-type": "22.4.3", + "jest-jasmine2": "23.6.0", + "jest-regex-util": "23.3.0", + "jest-resolve": "23.6.0", + "jest-util": "23.4.0", + "jest-validate": "23.6.0", + "micromatch": "2.3.11", + "pretty-format": "23.6.0" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "1.9.1" + } + }, + "chalk": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", + "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", + "dev": true, + "requires": { + "ansi-styles": "3.2.1", + "escape-string-regexp": "1.0.5", + "supports-color": "5.5.0" + } + }, + "glob": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", + "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", + "dev": true, + "requires": { + "fs.realpath": "1.0.0", + "inflight": "1.0.6", + "inherits": "2.0.3", + "minimatch": "3.0.4", + "once": "1.4.0", + "path-is-absolute": "1.0.1" + } + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "3.0.0" + } + } + } + }, + "jest-diff": { + "version": "23.6.0", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-23.6.0.tgz", + "integrity": "sha512-Gz9l5Ov+X3aL5L37IT+8hoCUsof1CVYBb2QEkOupK64XyRR3h+uRpYIm97K7sY8diFxowR8pIGEdyfMKTixo3g==", + "dev": true, + "requires": { + "chalk": "2.4.1", + "diff": "3.5.0", + "jest-get-type": "22.4.3", + "pretty-format": "23.6.0" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "1.9.1" + } + }, + "chalk": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", + "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", + "dev": true, + "requires": { + "ansi-styles": "3.2.1", + "escape-string-regexp": "1.0.5", + "supports-color": "5.5.0" + } + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "3.0.0" + } + } + } + }, + "jest-docblock": { + "version": "23.2.0", + "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-23.2.0.tgz", + "integrity": "sha1-8IXh8YVI2Z/dabICB+b9VdkTg6c=", + "dev": true, + "requires": { + "detect-newline": "2.1.0" + } + }, + "jest-each": { + "version": "23.6.0", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-23.6.0.tgz", + "integrity": "sha512-x7V6M/WGJo6/kLoissORuvLIeAoyo2YqLOoCDkohgJ4XOXSqOtyvr8FbInlAWS77ojBsZrafbozWoKVRdtxFCg==", + "dev": true, + "requires": { + "chalk": "2.4.1", + "pretty-format": "23.6.0" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "1.9.1" + } + }, + "chalk": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", + "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", + "dev": true, + "requires": { + "ansi-styles": "3.2.1", + "escape-string-regexp": "1.0.5", + "supports-color": "5.5.0" + } + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "3.0.0" + } + } + } + }, + "jest-environment-jsdom": { + "version": "23.4.0", + "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-23.4.0.tgz", + "integrity": "sha1-BWp5UrP+pROsYqFAosNox52eYCM=", + "dev": true, + "requires": { + "jest-mock": "23.2.0", + "jest-util": "23.4.0", + "jsdom": "11.12.0" + } + }, + "jest-environment-node": { + "version": "23.4.0", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-23.4.0.tgz", + "integrity": "sha1-V+gO0IQd6jAxZ8zozXlSHeuv3hA=", + "dev": true, + "requires": { + "jest-mock": "23.2.0", + "jest-util": "23.4.0" + } + }, + "jest-get-type": { + "version": "22.4.3", + "resolved": "http://registry.npmjs.org/jest-get-type/-/jest-get-type-22.4.3.tgz", + "integrity": "sha512-/jsz0Y+V29w1chdXVygEKSz2nBoHoYqNShPe+QgxSNjAuP1i8+k4LbQNrfoliKej0P45sivkSCh7yiD6ubHS3w==", + "dev": true + }, + "jest-haste-map": { + "version": "23.6.0", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-23.6.0.tgz", + "integrity": "sha512-uyNhMyl6dr6HaXGHp8VF7cK6KpC6G9z9LiMNsst+rJIZ8l7wY0tk8qwjPmEghczojZ2/ZhtEdIabZ0OQRJSGGg==", + "dev": true, + "requires": { + "fb-watchman": "2.0.0", + "graceful-fs": "4.1.11", + "invariant": "2.2.4", + "jest-docblock": "23.2.0", + "jest-serializer": "23.0.1", + "jest-worker": "23.2.0", + "micromatch": "2.3.11", + "sane": "2.5.2" + } + }, + "jest-jasmine2": { + "version": "23.6.0", + "resolved": "https://registry.npmjs.org/jest-jasmine2/-/jest-jasmine2-23.6.0.tgz", + "integrity": "sha512-pe2Ytgs1nyCs8IvsEJRiRTPC0eVYd8L/dXJGU08GFuBwZ4sYH/lmFDdOL3ZmvJR8QKqV9MFuwlsAi/EWkFUbsQ==", + "dev": true, + "requires": { + "babel-traverse": "6.26.0", + "chalk": "2.4.1", + "co": "4.6.0", + "expect": "23.6.0", + "is-generator-fn": "1.0.0", + "jest-diff": "23.6.0", + "jest-each": "23.6.0", + "jest-matcher-utils": "23.6.0", + "jest-message-util": "23.4.0", + "jest-snapshot": "23.6.0", + "jest-util": "23.4.0", + "pretty-format": "23.6.0" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "1.9.1" + } + }, + "chalk": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", + "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", + "dev": true, + "requires": { + "ansi-styles": "3.2.1", + "escape-string-regexp": "1.0.5", + "supports-color": "5.5.0" + } + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "3.0.0" + } + } + } + }, + "jest-junit": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/jest-junit/-/jest-junit-5.2.0.tgz", + "integrity": "sha512-Mdg0Qpdh1Xm/FA1B/mcLlmEmlr3XzH5pZg7MvcAwZhjHijPRd1z/UwYwkwNHmCV7o4ZOWCf77nLu7ZkhHHrtJg==", + "dev": true, + "requires": { + "jest-config": "23.6.0", + "jest-validate": "23.6.0", + "mkdirp": "0.5.1", + "strip-ansi": "4.0.0", + "xml": "1.0.1" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "requires": { + "ansi-regex": "3.0.0" + } + } + } + }, + "jest-leak-detector": { + "version": "23.6.0", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-23.6.0.tgz", + "integrity": "sha512-f/8zA04rsl1Nzj10HIyEsXvYlMpMPcy0QkQilVZDFOaPbv2ur71X5u2+C4ZQJGyV/xvVXtCCZ3wQ99IgQxftCg==", + "dev": true, + "requires": { + "pretty-format": "23.6.0" + } + }, + "jest-matcher-utils": { + "version": "23.6.0", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-23.6.0.tgz", + "integrity": "sha512-rosyCHQfBcol4NsckTn01cdelzWLU9Cq7aaigDf8VwwpIRvWE/9zLgX2bON+FkEW69/0UuYslUe22SOdEf2nog==", + "dev": true, + "requires": { + "chalk": "2.4.1", + "jest-get-type": "22.4.3", + "pretty-format": "23.6.0" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "1.9.1" + } + }, + "chalk": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", + "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", + "dev": true, + "requires": { + "ansi-styles": "3.2.1", + "escape-string-regexp": "1.0.5", + "supports-color": "5.5.0" + } + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "3.0.0" + } + } + } + }, + "jest-message-util": { + "version": "23.4.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-23.4.0.tgz", + "integrity": "sha1-F2EMUJQjSVCNAaPR4L2iwHkIap8=", + "dev": true, + "requires": { + "@babel/code-frame": "7.0.0", + "chalk": "2.4.1", + "micromatch": "2.3.11", + "slash": "1.0.0", + "stack-utils": "1.0.2" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "1.9.1" + } + }, + "chalk": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", + "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", + "dev": true, + "requires": { + "ansi-styles": "3.2.1", + "escape-string-regexp": "1.0.5", + "supports-color": "5.5.0" + } + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "3.0.0" + } + } + } + }, + "jest-mock": { + "version": "23.2.0", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-23.2.0.tgz", + "integrity": "sha1-rRxg8p6HGdR8JuETgJi20YsmETQ=", + "dev": true + }, + "jest-regex-util": { + "version": "23.3.0", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-23.3.0.tgz", + "integrity": "sha1-X4ZylUfCeFxAAs6qj4Sf6MpHG8U=", + "dev": true + }, + "jest-resolve": { + "version": "23.6.0", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-23.6.0.tgz", + "integrity": "sha512-XyoRxNtO7YGpQDmtQCmZjum1MljDqUCob7XlZ6jy9gsMugHdN2hY4+Acz9Qvjz2mSsOnPSH7skBmDYCHXVZqkA==", + "dev": true, + "requires": { + "browser-resolve": "1.11.3", + "chalk": "2.4.1", + "realpath-native": "1.0.2" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "1.9.1" + } + }, + "chalk": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", + "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", + "dev": true, + "requires": { + "ansi-styles": "3.2.1", + "escape-string-regexp": "1.0.5", + "supports-color": "5.5.0" + } + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "3.0.0" + } + } + } + }, + "jest-resolve-dependencies": { + "version": "23.6.0", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-23.6.0.tgz", + "integrity": "sha512-EkQWkFWjGKwRtRyIwRwI6rtPAEyPWlUC2MpzHissYnzJeHcyCn1Hc8j7Nn1xUVrS5C6W5+ZL37XTem4D4pLZdA==", + "dev": true, + "requires": { + "jest-regex-util": "23.3.0", + "jest-snapshot": "23.6.0" + } + }, + "jest-runner": { + "version": "23.6.0", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-23.6.0.tgz", + "integrity": "sha512-kw0+uj710dzSJKU6ygri851CObtCD9cN8aNkg8jWJf4ewFyEa6kwmiH/r/M1Ec5IL/6VFa0wnAk6w+gzUtjJzA==", + "dev": true, + "requires": { + "exit": "0.1.2", + "graceful-fs": "4.1.11", + "jest-config": "23.6.0", + "jest-docblock": "23.2.0", + "jest-haste-map": "23.6.0", + "jest-jasmine2": "23.6.0", + "jest-leak-detector": "23.6.0", + "jest-message-util": "23.4.0", + "jest-runtime": "23.6.0", + "jest-util": "23.4.0", + "jest-worker": "23.2.0", + "source-map-support": "0.5.9", + "throat": "4.1.0" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "source-map-support": { + "version": "0.5.9", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.9.tgz", + "integrity": "sha512-gR6Rw4MvUlYy83vP0vxoVNzM6t8MUXqNuRsuBmBHQDu1Fh6X015FrLdgoDKcNdkwGubozq0P4N0Q37UyFVr1EA==", + "dev": true, + "requires": { + "buffer-from": "1.1.1", + "source-map": "0.6.1" + } + } + } + }, + "jest-runtime": { + "version": "23.6.0", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-23.6.0.tgz", + "integrity": "sha512-ycnLTNPT2Gv+TRhnAYAQ0B3SryEXhhRj1kA6hBPSeZaNQkJ7GbZsxOLUkwg6YmvWGdX3BB3PYKFLDQCAE1zNOw==", + "dev": true, + "requires": { + "babel-core": "6.26.3", + "babel-plugin-istanbul": "4.1.6", + "chalk": "2.4.1", + "convert-source-map": "1.5.1", + "exit": "0.1.2", + "fast-json-stable-stringify": "2.0.0", + "graceful-fs": "4.1.11", + "jest-config": "23.6.0", + "jest-haste-map": "23.6.0", + "jest-message-util": "23.4.0", + "jest-regex-util": "23.3.0", + "jest-resolve": "23.6.0", + "jest-snapshot": "23.6.0", + "jest-util": "23.4.0", + "jest-validate": "23.6.0", + "micromatch": "2.3.11", + "realpath-native": "1.0.2", + "slash": "1.0.0", + "strip-bom": "3.0.0", + "write-file-atomic": "2.3.0", + "yargs": "11.1.0" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true + }, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "1.9.1" + } + }, + "camelcase": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz", + "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=", + "dev": true + }, + "chalk": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", + "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", + "dev": true, + "requires": { + "ansi-styles": "3.2.1", + "escape-string-regexp": "1.0.5", + "supports-color": "5.5.0" + } + }, + "cliui": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-4.1.0.tgz", + "integrity": "sha512-4FG+RSG9DL7uEwRUZXZn3SS34DiDPfzP0VOiEwtUWlE+AR2EIg+hSyvrIgUUfhdgR/UkAeW2QHgeP+hWrXs7jQ==", + "dev": true, + "requires": { + "string-width": "2.1.1", + "strip-ansi": "4.0.0", + "wrap-ansi": "2.1.0" + } + }, + "find-up": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", + "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", + "dev": true, + "requires": { + "locate-path": "2.0.0" + } + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "requires": { + "ansi-regex": "3.0.0" + } + }, + "strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", + "dev": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "3.0.0" + } + }, + "yargs": { + "version": "11.1.0", + "resolved": "http://registry.npmjs.org/yargs/-/yargs-11.1.0.tgz", + "integrity": "sha512-NwW69J42EsCSanF8kyn5upxvjp5ds+t3+udGBeTbFnERA+lF541DDpMawzo4z6W/QrzNM18D+BPMiOBibnFV5A==", + "dev": true, + "requires": { + "cliui": "4.1.0", + "decamelize": "1.2.0", + "find-up": "2.1.0", + "get-caller-file": "1.0.2", + "os-locale": "2.1.0", + "require-directory": "2.1.1", + "require-main-filename": "1.0.1", + "set-blocking": "2.0.0", + "string-width": "2.1.1", + "which-module": "2.0.0", + "y18n": "3.2.1", + "yargs-parser": "9.0.2" + } + }, + "yargs-parser": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-9.0.2.tgz", + "integrity": "sha1-nM9qQ0YP5O1Aqbto9I1DuKaMwHc=", + "dev": true, + "requires": { + "camelcase": "4.1.0" + } + } + } + }, + "jest-serializer": { + "version": "23.0.1", + "resolved": "https://registry.npmjs.org/jest-serializer/-/jest-serializer-23.0.1.tgz", + "integrity": "sha1-o3dq6zEekP6D+rnlM+hRAr0WQWU=", + "dev": true + }, + "jest-snapshot": { + "version": "23.6.0", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-23.6.0.tgz", + "integrity": "sha512-tM7/Bprftun6Cvj2Awh/ikS7zV3pVwjRYU2qNYS51VZHgaAMBs5l4o/69AiDHhQrj5+LA2Lq4VIvK7zYk/bswg==", + "dev": true, + "requires": { + "babel-types": "6.26.0", + "chalk": "2.4.1", + "jest-diff": "23.6.0", + "jest-matcher-utils": "23.6.0", + "jest-message-util": "23.4.0", + "jest-resolve": "23.6.0", + "mkdirp": "0.5.1", + "natural-compare": "1.4.0", + "pretty-format": "23.6.0", + "semver": "5.6.0" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "1.9.1" + } + }, + "chalk": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", + "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", + "dev": true, + "requires": { + "ansi-styles": "3.2.1", + "escape-string-regexp": "1.0.5", + "supports-color": "5.5.0" + } + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "semver": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.6.0.tgz", + "integrity": "sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg==", + "dev": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "3.0.0" + } + } + } + }, + "jest-util": { + "version": "23.4.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-23.4.0.tgz", + "integrity": "sha1-TQY8uSe68KI4Mf9hvsLLv0l5NWE=", + "dev": true, + "requires": { + "callsites": "2.0.0", + "chalk": "2.4.1", + "graceful-fs": "4.1.11", + "is-ci": "1.2.1", + "jest-message-util": "23.4.0", + "mkdirp": "0.5.1", + "slash": "1.0.0", + "source-map": "0.6.1" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "1.9.1" + } + }, + "chalk": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", + "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", + "dev": true, + "requires": { + "ansi-styles": "3.2.1", + "escape-string-regexp": "1.0.5", + "supports-color": "5.5.0" + } + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "3.0.0" + } + } + } + }, + "jest-validate": { + "version": "23.6.0", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-23.6.0.tgz", + "integrity": "sha512-OFKapYxe72yz7agrDAWi8v2WL8GIfVqcbKRCLbRG9PAxtzF9b1SEDdTpytNDN12z2fJynoBwpMpvj2R39plI2A==", + "dev": true, + "requires": { + "chalk": "2.4.1", + "jest-get-type": "22.4.3", + "leven": "2.1.0", + "pretty-format": "23.6.0" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "1.9.1" + } + }, + "chalk": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", + "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", + "dev": true, + "requires": { + "ansi-styles": "3.2.1", + "escape-string-regexp": "1.0.5", + "supports-color": "5.5.0" + } + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "3.0.0" + } + } + } + }, + "jest-watcher": { + "version": "23.4.0", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-23.4.0.tgz", + "integrity": "sha1-0uKM50+NrWxq/JIrksq+9u0FyRw=", + "dev": true, + "requires": { + "ansi-escapes": "3.1.0", + "chalk": "2.4.1", + "string-length": "2.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "1.9.1" + } + }, + "chalk": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", + "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", + "dev": true, + "requires": { + "ansi-styles": "3.2.1", + "escape-string-regexp": "1.0.5", + "supports-color": "5.5.0" + } + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "3.0.0" + } + } + } + }, + "jest-worker": { + "version": "23.2.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-23.2.0.tgz", + "integrity": "sha1-+vcGqNo2+uYOsmlXJX+ntdjqArk=", + "dev": true, + "requires": { + "merge-stream": "1.0.1" + } + }, + "js-tokens": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz", + "integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls=", + "dev": true + }, + "js-yaml": { + "version": "3.12.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.12.0.tgz", + "integrity": "sha512-PIt2cnwmPfL4hKNwqeiuz4bKfnzHTBv6HyVgjahA6mPLwPDzjDWrplJBMjHUFxku/N3FlmrbyPclad+I+4mJ3A==", + "dev": true, + "requires": { + "argparse": "1.0.10", + "esprima": "4.0.1" + } + }, + "jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", + "dev": true + }, + "jsdom": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-11.12.0.tgz", + "integrity": "sha512-y8Px43oyiBM13Zc1z780FrfNLJCXTL40EWlty/LXUtcjykRBNgLlCjWXpfSPBl2iv+N7koQN+dvqszHZgT/Fjw==", + "dev": true, + "requires": { + "abab": "2.0.0", + "acorn": "5.7.3", + "acorn-globals": "4.3.0", + "array-equal": "1.0.0", + "cssom": "0.3.4", + "cssstyle": "1.1.1", + "data-urls": "1.1.0", + "domexception": "1.0.1", + "escodegen": "1.11.0", + "html-encoding-sniffer": "1.0.2", + "left-pad": "1.3.0", + "nwsapi": "2.0.9", + "parse5": "4.0.0", + "pn": "1.1.0", + "request": "2.88.0", + "request-promise-native": "1.0.5", + "sax": "1.2.4", + "symbol-tree": "3.2.2", + "tough-cookie": "2.5.0", + "w3c-hr-time": "1.0.1", + "webidl-conversions": "4.0.2", + "whatwg-encoding": "1.0.5", + "whatwg-mimetype": "2.3.0", + "whatwg-url": "6.5.0", + "ws": "5.2.2", + "xml-name-validator": "3.0.0" + }, + "dependencies": { + "acorn": { + "version": "5.7.3", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.3.tgz", + "integrity": "sha512-T/zvzYRfbVojPWahDsE5evJdHb3oJoQfFbsrKM7w5Zcs++Tr257tia3BmMP8XYVjp1S9RZXQMh7gao96BlqZOw==", + "dev": true + }, + "ws": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/ws/-/ws-5.2.2.tgz", + "integrity": "sha512-jaHFD6PFv6UgoIVda6qZllptQsMlDEJkTQcybzzXDYM1XO9Y8em691FGMPmM46WGyLU4z9KMgQN+qrux/nhlHA==", + "dev": true, + "requires": { + "async-limiter": "1.0.0" + } + } + } + }, + "jsesc": { + "version": "1.3.0", + "resolved": "http://registry.npmjs.org/jsesc/-/jsesc-1.3.0.tgz", + "integrity": "sha1-RsP+yMGJKxKwgz25vHYiF226s0s=", + "dev": true + }, + "json-loader": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/json-loader/-/json-loader-0.5.7.tgz", + "integrity": "sha512-QLPs8Dj7lnf3e3QYS1zkCo+4ZwqOiF9d/nZnYozTISxXWCfNs9yuky5rJw4/W34s7POaNlbZmQGaB5NiXCbP4w==", + "dev": true + }, + "json-schema": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", + "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=", + "dev": true + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "json-stable-stringify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz", + "integrity": "sha1-mnWdOcXy/1A/1TAGRu1EX4jE+a8=", + "dev": true, + "requires": { + "jsonify": "0.0.0" + } + }, + "json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=", + "dev": true + }, + "json5": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-0.5.1.tgz", + "integrity": "sha1-Hq3nrMASA0rYTiOWdn6tn6VJWCE=", + "dev": true + }, + "jsonify": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.0.tgz", + "integrity": "sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM=", + "dev": true + }, + "jsprim": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", + "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", + "dev": true, + "requires": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.2.3", + "verror": "1.10.0" + } + }, + "just-debounce": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/just-debounce/-/just-debounce-1.0.0.tgz", + "integrity": "sha1-h/zPrv/AtozRnVX2cilD+SnqNeo=", + "dev": true + }, + "kind-of": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", + "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", + "dev": true + }, + "kleur": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-2.0.2.tgz", + "integrity": "sha512-77XF9iTllATmG9lSlIv0qdQ2BQ/h9t0bJllHlbvsQ0zUWfU7Yi0S8L5JXzPZgkefIiajLmBJJ4BsMJmqcf7oxQ==", + "dev": true + }, + "last-run": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/last-run/-/last-run-1.1.1.tgz", + "integrity": "sha1-RblpQsF7HHnHchmCWbqUO+v4yls=", + "dev": true, + "requires": { + "default-resolution": "2.0.0", + "es6-weak-map": "2.0.2" + } + }, + "lazy-cache": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-1.0.4.tgz", + "integrity": "sha1-odePw6UEdMuAhF07O24dpJpEbo4=", + "dev": true + }, + "lazystream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/lazystream/-/lazystream-1.0.0.tgz", + "integrity": "sha1-9plf4PggOS9hOWvolGJAe7dxaOQ=", + "dev": true, + "requires": { + "readable-stream": "2.3.3" + } + }, + "lcid": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/lcid/-/lcid-1.0.0.tgz", + "integrity": "sha1-MIrMr6C8SDo4Z7S28rlQYlHRuDU=", + "dev": true, + "requires": { + "invert-kv": "1.0.0" + } + }, + "lead": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/lead/-/lead-1.0.0.tgz", + "integrity": "sha1-bxT5mje+Op3XhPVJVpDlkDRm7kI=", + "dev": true, + "requires": { + "flush-write-stream": "1.0.3" + } + }, + "left-pad": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/left-pad/-/left-pad-1.3.0.tgz", + "integrity": "sha512-XI5MPzVNApjAyhQzphX8BkmKsKUxD4LdyK24iZeQGinBN9yTQT3bFlCBy/aVx2HrNcqQGsdot8ghrjyrvMCoEA==", + "dev": true + }, + "leven": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/leven/-/leven-2.1.0.tgz", + "integrity": "sha1-wuep93IJTe6dNCAq6KzORoeHVYA=", + "dev": true + }, + "levn": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", + "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", + "dev": true, + "requires": { + "prelude-ls": "1.1.2", + "type-check": "0.3.2" + } + }, + "liftoff": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/liftoff/-/liftoff-2.5.0.tgz", + "integrity": "sha1-IAkpG7Mc6oYbvxCnwVooyvdcMew=", + "dev": true, + "requires": { + "extend": "3.0.1", + "findup-sync": "2.0.0", + "fined": "1.1.0", + "flagged-respawn": "1.0.0", + "is-plain-object": "2.0.4", + "object.map": "1.0.1", + "rechoir": "0.6.2", + "resolve": "1.5.0" + } + }, + "load-json-file": { + "version": "1.1.0", + "resolved": "http://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", + "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=", + "dev": true, + "requires": { + "graceful-fs": "4.1.11", + "parse-json": "2.2.0", + "pify": "2.3.0", + "pinkie-promise": "2.0.1", + "strip-bom": "2.0.0" + } + }, + "loader-runner": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-2.3.0.tgz", + "integrity": "sha1-9IKuqC1UPgeSFwDVpG7yb9rGuKI=", + "dev": true + }, + "loader-utils": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-0.2.17.tgz", + "integrity": "sha1-+G5jdNQyBabmxg6RlvF8Apm/s0g=", + "dev": true, + "requires": { + "big.js": "3.2.0", + "emojis-list": "2.1.0", + "json5": "0.5.1", + "object-assign": "4.1.1" + } + }, + "locate-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", + "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", + "dev": true, + "requires": { + "p-locate": "2.0.0", + "path-exists": "3.0.0" + }, + "dependencies": { + "path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", + "dev": true + } + } + }, + "lodash": { + "version": "4.17.11", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", + "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==", + "dev": true + }, + "lodash._basecopy": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/lodash._basecopy/-/lodash._basecopy-3.0.1.tgz", + "integrity": "sha1-jaDmqHbPNEwK2KVIghEd08XHyjY=", + "dev": true + }, + "lodash._basetostring": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/lodash._basetostring/-/lodash._basetostring-3.0.1.tgz", + "integrity": "sha1-0YYdh3+CSlL2aYMtyvPuFVZqB9U=", + "dev": true + }, + "lodash._basevalues": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/lodash._basevalues/-/lodash._basevalues-3.0.0.tgz", + "integrity": "sha1-W3dXYoAr3j0yl1A+JjAIIP32Ybc=", + "dev": true + }, + "lodash._escapehtmlchar": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/lodash._escapehtmlchar/-/lodash._escapehtmlchar-2.4.1.tgz", + "integrity": "sha1-32fDu2t+jh6DGrSL+geVuSr+iZ0=", + "dev": true, + "requires": { + "lodash._htmlescapes": "2.4.1" + } + }, + "lodash._escapestringchar": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/lodash._escapestringchar/-/lodash._escapestringchar-2.4.1.tgz", + "integrity": "sha1-7P4iYYoq3lC/7qQ5N+Ud9m8O23I=", + "dev": true + }, + "lodash._getnative": { + "version": "3.9.1", + "resolved": "https://registry.npmjs.org/lodash._getnative/-/lodash._getnative-3.9.1.tgz", + "integrity": "sha1-VwvH3t5G1hzc3mh9ZdPuy6o6r/U=", + "dev": true + }, + "lodash._htmlescapes": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/lodash._htmlescapes/-/lodash._htmlescapes-2.4.1.tgz", + "integrity": "sha1-MtFL8IRLbeb4tioFG09nwii2JMs=", + "dev": true + }, + "lodash._isiterateecall": { + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/lodash._isiterateecall/-/lodash._isiterateecall-3.0.9.tgz", + "integrity": "sha1-UgOte6Ql+uhCRg5pbbnPPmqsBXw=", + "dev": true + }, + "lodash._isnative": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/lodash._isnative/-/lodash._isnative-2.4.1.tgz", + "integrity": "sha1-PqZAS3hKe+g2x7V1gOHN95sUgyw=", + "dev": true + }, + "lodash._objecttypes": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/lodash._objecttypes/-/lodash._objecttypes-2.4.1.tgz", + "integrity": "sha1-fAt/admKH3ZSn4kLDNsbTf7BHBE=", + "dev": true + }, + "lodash._reescape": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/lodash._reescape/-/lodash._reescape-3.0.0.tgz", + "integrity": "sha1-Kx1vXf4HyKNVdT5fJ/rH8c3hYWo=", + "dev": true + }, + "lodash._reevaluate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/lodash._reevaluate/-/lodash._reevaluate-3.0.0.tgz", + "integrity": "sha1-WLx0xAZklTrgsSTYBpltrKQx4u0=", + "dev": true + }, + "lodash._reinterpolate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz", + "integrity": "sha1-DM8tiRZq8Ds2Y8eWU4t1rG4RTZ0=", + "dev": true + }, + "lodash._reunescapedhtml": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/lodash._reunescapedhtml/-/lodash._reunescapedhtml-2.4.1.tgz", + "integrity": "sha1-dHxPxAED6zu4oJduVx96JlnpO6c=", + "dev": true, + "requires": { + "lodash._htmlescapes": "2.4.1", + "lodash.keys": "2.4.1" + }, + "dependencies": { + "lodash.keys": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-2.4.1.tgz", + "integrity": "sha1-SN6kbfj/djKxDXBrissmWR4rNyc=", + "dev": true, + "requires": { + "lodash._isnative": "2.4.1", + "lodash._shimkeys": "2.4.1", + "lodash.isobject": "2.4.1" + } + } + } + }, + "lodash._root": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/lodash._root/-/lodash._root-3.0.1.tgz", + "integrity": "sha1-+6HEUkwZ7ppfgTa0YJ8BfPTe1pI=", + "dev": true + }, + "lodash._shimkeys": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/lodash._shimkeys/-/lodash._shimkeys-2.4.1.tgz", + "integrity": "sha1-bpzJZm/wgfC1psl4uD4kLmlJ0gM=", + "dev": true, + "requires": { + "lodash._objecttypes": "2.4.1" + } + }, + "lodash.clone": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.clone/-/lodash.clone-4.5.0.tgz", + "integrity": "sha1-GVhwRQ9aExkkeN9Lw9I9LeoZB7Y=", + "dev": true + }, + "lodash.debounce": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", + "integrity": "sha1-gteb/zCmfEAF/9XiUVMArZyk168=", + "dev": true + }, + "lodash.defaults": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-2.4.1.tgz", + "integrity": "sha1-p+iIXwXmiFEUS24SqPNngCa8TFQ=", + "dev": true, + "requires": { + "lodash._objecttypes": "2.4.1", + "lodash.keys": "2.4.1" + }, + "dependencies": { + "lodash.keys": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-2.4.1.tgz", + "integrity": "sha1-SN6kbfj/djKxDXBrissmWR4rNyc=", + "dev": true, + "requires": { + "lodash._isnative": "2.4.1", + "lodash._shimkeys": "2.4.1", + "lodash.isobject": "2.4.1" + } + } + } + }, + "lodash.escape": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/lodash.escape/-/lodash.escape-3.2.0.tgz", + "integrity": "sha1-mV7g3BjBtIzJLv+ucaEKq1tIdpg=", + "dev": true, + "requires": { + "lodash._root": "3.0.1" + } + }, + "lodash.isarguments": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz", + "integrity": "sha1-L1c9hcaiQon/AGY7SRwdM4/zRYo=", + "dev": true + }, + "lodash.isarray": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/lodash.isarray/-/lodash.isarray-3.0.4.tgz", + "integrity": "sha1-eeTriMNqgSKvhvhEqpvNhRtfu1U=", + "dev": true + }, + "lodash.isequal": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", + "integrity": "sha1-QVxEePK8wwEgwizhDtMib30+GOA=", + "dev": true + }, + "lodash.isobject": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/lodash.isobject/-/lodash.isobject-2.4.1.tgz", + "integrity": "sha1-Wi5H/mmVPx7mMafrof5k0tBlWPU=", + "dev": true, + "requires": { + "lodash._objecttypes": "2.4.1" + } + }, + "lodash.keys": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-3.1.2.tgz", + "integrity": "sha1-TbwEcrFWvlCgsoaFXRvQsMZWCYo=", + "dev": true, + "requires": { + "lodash._getnative": "3.9.1", + "lodash.isarguments": "3.1.0", + "lodash.isarray": "3.0.4" + } + }, + "lodash.restparam": { + "version": "3.6.1", + "resolved": "https://registry.npmjs.org/lodash.restparam/-/lodash.restparam-3.6.1.tgz", + "integrity": "sha1-k2pOMJ7zMKdkXtQUWYbIWuWyCAU=", + "dev": true + }, + "lodash.some": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.some/-/lodash.some-4.6.0.tgz", + "integrity": "sha1-G7nzFO9ri63tE7VJFpsqlF62jk0=", + "dev": true + }, + "lodash.sortby": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz", + "integrity": "sha1-7dFMgk4sycHgsKG0K7UhBRakJDg=", + "dev": true + }, + "lodash.template": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/lodash.template/-/lodash.template-3.6.2.tgz", + "integrity": "sha1-+M3sxhaaJVvpCYrosMU9N4kx0U8=", + "dev": true, + "requires": { + "lodash._basecopy": "3.0.1", + "lodash._basetostring": "3.0.1", + "lodash._basevalues": "3.0.0", + "lodash._isiterateecall": "3.0.9", + "lodash._reinterpolate": "3.0.0", + "lodash.escape": "3.2.0", + "lodash.keys": "3.1.2", + "lodash.restparam": "3.6.1", + "lodash.templatesettings": "3.1.1" + } + }, + "lodash.templatesettings": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/lodash.templatesettings/-/lodash.templatesettings-3.1.1.tgz", + "integrity": "sha1-+zB4RHU7Zrnxr6VOJix0UwfbqOU=", + "dev": true, + "requires": { + "lodash._reinterpolate": "3.0.0", + "lodash.escape": "3.2.0" + } + }, + "lodash.values": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/lodash.values/-/lodash.values-2.4.1.tgz", + "integrity": "sha1-q/UUQ2s8twUAFieXjLzzCxKA7qQ=", + "dev": true, + "requires": { + "lodash.keys": "2.4.1" + }, + "dependencies": { + "lodash.keys": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-2.4.1.tgz", + "integrity": "sha1-SN6kbfj/djKxDXBrissmWR4rNyc=", + "dev": true, + "requires": { + "lodash._isnative": "2.4.1", + "lodash._shimkeys": "2.4.1", + "lodash.isobject": "2.4.1" + } + } + } + }, + "longest": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/longest/-/longest-1.0.1.tgz", + "integrity": "sha1-MKCy2jj3N3DoKUoNIuZiXtd9AJc=", + "dev": true + }, + "loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "dev": true, + "requires": { + "js-tokens": "3.0.2" + } + }, + "loud-rejection": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/loud-rejection/-/loud-rejection-1.6.0.tgz", + "integrity": "sha1-W0b4AUft7leIcPCG0Eghz5mOVR8=", + "dev": true, + "requires": { + "currently-unhandled": "0.4.1", + "signal-exit": "3.0.2" + } + }, + "lru-cache": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.1.tgz", + "integrity": "sha512-q4spe4KTfsAS1SUHLO0wz8Qiyf1+vMIAgpRYioFYDMNqKfHQbg+AVDH3i4fvpl71/P1L0dBl+fQi+P37UYf0ew==", + "dev": true, + "requires": { + "pseudomap": "1.0.2", + "yallist": "2.1.2" + } + }, + "lru-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/lru-queue/-/lru-queue-0.1.0.tgz", + "integrity": "sha1-Jzi9nw089PhEkMVzbEhpmsYyzaM=", + "dev": true, + "requires": { + "es5-ext": "0.10.37" + } + }, + "make-error": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.5.tgz", + "integrity": "sha512-c3sIjNUow0+8swNwVpqoH4YCShKNFkMaw6oH1mNS2haDZQqkeZFlHS3dhoeEbKKmJB4vXpJucU6oH75aDYeE9g==", + "dev": true + }, + "make-error-cause": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/make-error-cause/-/make-error-cause-1.2.2.tgz", + "integrity": "sha1-3wOI/NCzeBbf8KX7gQiTl3fcvJ0=", + "dev": true, + "requires": { + "make-error": "1.3.5" + } + }, + "make-iterator": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/make-iterator/-/make-iterator-1.0.1.tgz", + "integrity": "sha512-pxiuXh0iVEq7VM7KMIhs5gxsfxCux2URptUQaXo4iZZJxBAzTPOLE2BumO5dbfVYq/hBJFBR/a1mFDmOx5AGmw==", + "dev": true, + "requires": { + "kind-of": "6.0.2" + } + }, + "makeerror": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.11.tgz", + "integrity": "sha1-4BpckQnyr3lmDk6LlYd5AYT1qWw=", + "dev": true, + "requires": { + "tmpl": "1.0.4" + } + }, + "map-cache": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", + "integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=", + "dev": true + }, + "map-obj": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz", + "integrity": "sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0=", + "dev": true + }, + "map-visit": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz", + "integrity": "sha1-7Nyo8TFE5mDxtb1B8S80edmN+48=", + "dev": true, + "requires": { + "object-visit": "1.0.1" + } + }, + "matchdep": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/matchdep/-/matchdep-2.0.0.tgz", + "integrity": "sha1-xvNINKDY28OzfCfui7yyfHd1WC4=", + "dev": true, + "requires": { + "findup-sync": "2.0.0", + "micromatch": "3.1.10", + "resolve": "1.5.0", + "stack-trace": "0.0.10" + }, + "dependencies": { + "array-unique": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", + "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", + "dev": true + }, + "braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "dev": true, + "requires": { + "arr-flatten": "1.1.0", + "array-unique": "0.3.2", + "extend-shallow": "2.0.1", + "fill-range": "4.0.0", + "isobject": "3.0.1", + "repeat-element": "1.1.2", + "snapdragon": "0.8.2", + "snapdragon-node": "2.1.1", + "split-string": "3.1.0", + "to-regex": "3.0.2" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "0.1.1" + } + } + } + }, + "expand-brackets": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", + "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", + "dev": true, + "requires": { + "debug": "2.6.9", + "define-property": "0.2.5", + "extend-shallow": "2.0.1", + "posix-character-classes": "0.1.1", + "regex-not": "1.0.2", + "snapdragon": "0.8.2", + "to-regex": "3.0.2" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "0.1.6" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "0.1.1" + } + }, + "is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", + "dev": true, + "requires": { + "kind-of": "3.2.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "1.1.6" + } + } + } + }, + "is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", + "dev": true, + "requires": { + "kind-of": "3.2.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "1.1.6" + } + } + } + }, + "is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "0.1.6", + "is-data-descriptor": "0.1.4", + "kind-of": "5.1.0" + } + }, + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "dev": true + } + } + }, + "extglob": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", + "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", + "dev": true, + "requires": { + "array-unique": "0.3.2", + "define-property": "1.0.0", + "expand-brackets": "2.1.4", + "extend-shallow": "2.0.1", + "fragment-cache": "0.2.1", + "regex-not": "1.0.2", + "snapdragon": "0.8.2", + "to-regex": "3.0.2" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "requires": { + "is-descriptor": "1.0.2" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "0.1.1" + } + } + } + }, + "fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", + "dev": true, + "requires": { + "extend-shallow": "2.0.1", + "is-number": "3.0.0", + "repeat-string": "1.6.1", + "to-regex-range": "2.1.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "0.1.1" + } + } + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "requires": { + "kind-of": "6.0.2" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "requires": { + "kind-of": "6.0.2" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "1.0.0", + "is-data-descriptor": "1.0.0", + "kind-of": "6.0.2" + } + }, + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "dev": true, + "requires": { + "kind-of": "3.2.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "1.1.6" + } + } + } + }, + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true + }, + "micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "dev": true, + "requires": { + "arr-diff": "4.0.0", + "array-unique": "0.3.2", + "braces": "2.3.2", + "define-property": "2.0.2", + "extend-shallow": "3.0.2", + "extglob": "2.0.4", + "fragment-cache": "0.2.1", + "kind-of": "6.0.2", + "nanomatch": "1.2.13", + "object.pick": "1.3.0", + "regex-not": "1.0.2", + "snapdragon": "0.8.2", + "to-regex": "3.0.2" + } + } + } + }, + "math-random": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/math-random/-/math-random-1.0.1.tgz", + "integrity": "sha1-izqsWIuKZuSXXjzepn97sylgH6w=", + "dev": true + }, + "md5.js": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.4.tgz", + "integrity": "sha1-6b296UogpawYsENA/Fdk1bCdkB0=", + "dev": true, + "requires": { + "hash-base": "3.0.4", + "inherits": "2.0.3" + }, + "dependencies": { + "hash-base": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.0.4.tgz", + "integrity": "sha1-X8hoaEfs1zSZQDMZprCj8/auSRg=", + "dev": true, + "requires": { + "inherits": "2.0.3", + "safe-buffer": "5.1.1" + } + } + } + }, + "mem": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/mem/-/mem-1.1.0.tgz", + "integrity": "sha1-Xt1StIXKHZAP5kiVUFOZoN+kX3Y=", + "dev": true, + "requires": { + "mimic-fn": "1.1.0" + } + }, + "memoizee": { + "version": "0.4.14", + "resolved": "https://registry.npmjs.org/memoizee/-/memoizee-0.4.14.tgz", + "integrity": "sha512-/SWFvWegAIYAO4NQMpcX+gcra0yEZu4OntmUdrBaWrJncxOqAziGFlHxc7yjKVK2uu3lpPW27P27wkR82wA8mg==", + "dev": true, + "requires": { + "d": "1.0.0", + "es5-ext": "0.10.46", + "es6-weak-map": "2.0.2", + "event-emitter": "0.3.5", + "is-promise": "2.1.0", + "lru-queue": "0.1.0", + "next-tick": "1.0.0", + "timers-ext": "0.1.7" + }, + "dependencies": { + "es5-ext": { + "version": "0.10.46", + "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.46.tgz", + "integrity": "sha512-24XxRvJXNFwEMpJb3nOkiRJKRoupmjYmOPVlI65Qy2SrtxwOTB+g6ODjBKOtwEHbYrhWRty9xxOWLNdClT2djw==", + "dev": true, + "requires": { + "es6-iterator": "2.0.3", + "es6-symbol": "3.1.1", + "next-tick": "1.0.0" + } + } + } + }, + "memory-fs": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.4.1.tgz", + "integrity": "sha1-OpoguEYlI+RHz7x+i7gO1me/xVI=", + "dev": true, + "requires": { + "errno": "0.1.4", + "readable-stream": "2.3.3" + } + }, + "meow": { + "version": "3.7.0", + "resolved": "http://registry.npmjs.org/meow/-/meow-3.7.0.tgz", + "integrity": "sha1-cstmi0JSKCkKu/qFaJJYcwioAfs=", + "dev": true, + "requires": { + "camelcase-keys": "2.1.0", + "decamelize": "1.2.0", + "loud-rejection": "1.6.0", + "map-obj": "1.0.1", + "minimist": "1.2.0", + "normalize-package-data": "2.4.0", + "object-assign": "4.1.1", + "read-pkg-up": "1.0.1", + "redent": "1.0.0", + "trim-newlines": "1.0.0" + } + }, + "merge": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/merge/-/merge-1.2.1.tgz", + "integrity": "sha512-VjFo4P5Whtj4vsLzsYBu5ayHhoHJ0UqNm7ibvShmbmoz7tGi0vXaoJbGdB+GmDMLUdg8DpQXEIeVDAe8MaABvQ==", + "dev": true + }, + "merge-stream": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-1.0.1.tgz", + "integrity": "sha1-QEEgLVCKNCugAXQAjfDCUbjBNeE=", + "dev": true, + "requires": { + "readable-stream": "2.3.3" + } + }, + "micromatch": { + "version": "2.3.11", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-2.3.11.tgz", + "integrity": "sha1-hmd8l9FyCzY0MdBNDRUpO9OMFWU=", + "dev": true, + "requires": { + "arr-diff": "2.0.0", + "array-unique": "0.2.1", + "braces": "1.8.5", + "expand-brackets": "0.1.5", + "extglob": "0.3.2", + "filename-regex": "2.0.1", + "is-extglob": "1.0.0", + "is-glob": "2.0.1", + "kind-of": "3.2.2", + "normalize-path": "2.1.1", + "object.omit": "2.0.1", + "parse-glob": "3.0.4", + "regex-cache": "0.4.4" + }, + "dependencies": { + "arr-diff": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-2.0.0.tgz", + "integrity": "sha1-jzuCf5Vai9ZpaX5KQlasPOrjVs8=", + "dev": true, + "requires": { + "arr-flatten": "1.1.0" + } + }, + "is-extglob": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz", + "integrity": "sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA=", + "dev": true + }, + "is-glob": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz", + "integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=", + "dev": true, + "requires": { + "is-extglob": "1.0.0" + } + }, + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "1.1.6" + } + } + } + }, + "miller-rabin": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/miller-rabin/-/miller-rabin-4.0.1.tgz", + "integrity": "sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA==", + "dev": true, + "requires": { + "bn.js": "4.11.8", + "brorand": "1.1.0" + } + }, + "mime-db": { + "version": "1.37.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.37.0.tgz", + "integrity": "sha512-R3C4db6bgQhlIhPU48fUtdVmKnflq+hRdad7IyKhtFj06VPNVdk2RhiYL3UjQIlso8L+YxAtFkobT0VK+S/ybg==", + "dev": true + }, + "mime-types": { + "version": "2.1.21", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.21.tgz", + "integrity": "sha512-3iL6DbwpyLzjR3xHSFNFeb9Nz/M8WDkX33t1GFQnFOllWk8pOrh/LSrB5OXlnlW5P9LH73X6loW/eogc+F5lJg==", + "dev": true, + "requires": { + "mime-db": "1.37.0" + } + }, + "mimic-fn": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.1.0.tgz", + "integrity": "sha1-5md4PZLonb00KBi1IwudYqZyrRg=", + "dev": true + }, + "minimalistic-assert": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.0.tgz", + "integrity": "sha1-cCvi3aazf0g2vLP121ZkG2Sh09M=", + "dev": true + }, + "minimalistic-crypto-utils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", + "integrity": "sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo=", + "dev": true + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "requires": { + "brace-expansion": "1.1.8" + } + }, + "minimist": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", + "dev": true + }, + "mixin-deep": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.1.tgz", + "integrity": "sha512-8ZItLHeEgaqEvd5lYBXfm4EZSFCX29Jb9K+lAHhDKzReKBQKj3R+7NOF6tjqYi9t4oI8VUfaWITJQm86wnXGNQ==", + "dev": true, + "requires": { + "for-in": "1.0.2", + "is-extendable": "1.0.1" + }, + "dependencies": { + "is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "dev": true, + "requires": { + "is-plain-object": "2.0.4" + } + } + } + }, + "mkdirp": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "dev": true, + "requires": { + "minimist": "0.0.8" + }, + "dependencies": { + "minimist": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", + "dev": true + } + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + }, + "multipipe": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/multipipe/-/multipipe-0.1.2.tgz", + "integrity": "sha1-Ko8t33Du1WTf8tV/HhoTfZ8FB4s=", + "dev": true, + "requires": { + "duplexer2": "0.0.2" + } + }, + "mute-stdout": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mute-stdout/-/mute-stdout-1.0.1.tgz", + "integrity": "sha512-kDcwXR4PS7caBpuRYYBUz9iVixUk3anO3f5OYFiIPwK/20vCzKCHyKoulbiDY1S53zD2bxUpxN/IJ+TnXjfvxg==", + "dev": true + }, + "nan": { + "version": "2.11.1", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.11.1.tgz", + "integrity": "sha512-iji6k87OSXa0CcrLl9z+ZiYSuR2o+c0bGuNmXdrhTQTakxytAFsC56SArGYoiHlJlFoHSnvmhpceZJaXkVuOtA==", + "dev": true, + "optional": true + }, + "nanomatch": { + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz", + "integrity": "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==", + "dev": true, + "requires": { + "arr-diff": "4.0.0", + "array-unique": "0.3.2", + "define-property": "2.0.2", + "extend-shallow": "3.0.2", + "fragment-cache": "0.2.1", + "is-windows": "1.0.2", + "kind-of": "6.0.2", + "object.pick": "1.3.0", + "regex-not": "1.0.2", + "snapdragon": "0.8.2", + "to-regex": "3.0.2" + }, + "dependencies": { + "array-unique": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", + "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", + "dev": true + } + } + }, + "natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", + "dev": true + }, + "neo-async": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.0.tgz", + "integrity": "sha512-MFh0d/Wa7vkKO3Y3LlacqAEeHK0mckVqzDieUKTT+KGxi+zIpeVsFxymkIiRpbpDziHc290Xr9A1O4Om7otoRA==", + "dev": true + }, + "next-tick": { + "version": "1.0.0", + "resolved": "http://registry.npmjs.org/next-tick/-/next-tick-1.0.0.tgz", + "integrity": "sha1-yobR/ogoFpsBICCOPchCS524NCw=", + "dev": true + }, + "node-int64": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", + "integrity": "sha1-h6kGXNs1XTGC2PlM4RGIuCXGijs=", + "dev": true + }, + "node-libs-browser": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/node-libs-browser/-/node-libs-browser-2.1.0.tgz", + "integrity": "sha512-5AzFzdoIMb89hBGMZglEegffzgRg+ZFoUmisQ8HI4j1KDdpx13J0taNp2y9xPbur6W61gepGDDotGBVQ7mfUCg==", + "dev": true, + "requires": { + "assert": "1.4.1", + "browserify-zlib": "0.2.0", + "buffer": "4.9.1", + "console-browserify": "1.1.0", + "constants-browserify": "1.0.0", + "crypto-browserify": "3.12.0", + "domain-browser": "1.1.7", + "events": "1.1.1", + "https-browserify": "1.0.0", + "os-browserify": "0.3.0", + "path-browserify": "0.0.0", + "process": "0.11.10", + "punycode": "1.4.1", + "querystring-es3": "0.2.1", + "readable-stream": "2.3.3", + "stream-browserify": "2.0.1", + "stream-http": "2.7.2", + "string_decoder": "1.0.3", + "timers-browserify": "2.0.4", + "tty-browserify": "0.0.0", + "url": "0.11.0", + "util": "0.10.3", + "vm-browserify": "0.0.4" + } + }, + "node-notifier": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/node-notifier/-/node-notifier-5.3.0.tgz", + "integrity": "sha512-AhENzCSGZnZJgBARsUjnQ7DnZbzyP+HxlVXuD0xqAnvL8q+OqtSX7lGg9e8nHzwXkMMXNdVeqq4E2M3EUAqX6Q==", + "dev": true, + "requires": { + "growly": "1.3.0", + "semver": "5.6.0", + "shellwords": "0.1.1", + "which": "1.3.0" + }, + "dependencies": { + "semver": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.6.0.tgz", + "integrity": "sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg==", + "dev": true + } + } + }, + "normalize-package-data": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.4.0.tgz", + "integrity": "sha512-9jjUFbTPfEy3R/ad/2oNbKtW9Hgovl5O1FvFWKkKblNXoN/Oou6+9+KKohPK13Yc3/TyunyWhJp6gvRNR/PPAw==", + "dev": true, + "requires": { + "hosted-git-info": "2.5.0", + "is-builtin-module": "1.0.0", + "semver": "5.6.0", + "validate-npm-package-license": "3.0.1" + } + }, + "normalize-path": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", + "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", + "dev": true, + "requires": { + "remove-trailing-separator": "1.1.0" + } + }, + "now-and-later": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/now-and-later/-/now-and-later-2.0.0.tgz", + "integrity": "sha1-vGHLtFbXnLMiB85HygUTb/Ln1u4=", + "dev": true, + "requires": { + "once": "1.4.0" + } + }, + "npm-run-path": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", + "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=", + "dev": true, + "requires": { + "path-key": "2.0.1" + } + }, + "number-is-nan": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", + "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", + "dev": true + }, + "nwsapi": { + "version": "2.0.9", + "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.0.9.tgz", + "integrity": "sha512-nlWFSCTYQcHk/6A9FFnfhKc14c3aFhfdNBXgo8Qgi9QTBu/qg3Ww+Uiz9wMzXd1T8GFxPc2QIHB6Qtf2XFryFQ==", + "dev": true + }, + "oauth-sign": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", + "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", + "dev": true + }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", + "dev": true + }, + "object-copy": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz", + "integrity": "sha1-fn2Fi3gb18mRpBupde04EnVOmYw=", + "dev": true, + "requires": { + "copy-descriptor": "0.1.1", + "define-property": "0.2.5", + "kind-of": "3.2.2" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "0.1.6" + } + }, + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "1.1.6" + } + } + } + }, + "object-keys": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.0.12.tgz", + "integrity": "sha512-FTMyFUm2wBcGHnH2eXmz7tC6IwlqQZ6mVZ+6dm6vZ4IQIHjs6FdNsQBuKGPuUUUY6NfJw2PshC08Tn6LzLDOag==", + "dev": true + }, + "object-visit": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz", + "integrity": "sha1-95xEk68MU3e1n+OdOV5BBC3QRbs=", + "dev": true, + "requires": { + "isobject": "3.0.1" + }, + "dependencies": { + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true + } + } + }, + "object.assign": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.0.tgz", + "integrity": "sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==", + "dev": true, + "requires": { + "define-properties": "1.1.3", + "function-bind": "1.1.1", + "has-symbols": "1.0.0", + "object-keys": "1.0.12" + } + }, + "object.defaults": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/object.defaults/-/object.defaults-1.1.0.tgz", + "integrity": "sha1-On+GgzS0B96gbaFtiNXNKeQ1/s8=", + "dev": true, + "requires": { + "array-each": "1.0.1", + "array-slice": "1.1.0", + "for-own": "1.0.0", + "isobject": "3.0.1" + }, + "dependencies": { + "for-own": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/for-own/-/for-own-1.0.0.tgz", + "integrity": "sha1-xjMy9BXO3EsE2/5wz4NklMU8tEs=", + "dev": true, + "requires": { + "for-in": "1.0.2" + } + }, + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true + } + } + }, + "object.getownpropertydescriptors": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.0.3.tgz", + "integrity": "sha1-h1jIRvW0B62rDyNuCYbxSwUcqhY=", + "dev": true, + "requires": { + "define-properties": "1.1.3", + "es-abstract": "1.12.0" + } + }, + "object.map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/object.map/-/object.map-1.0.1.tgz", + "integrity": "sha1-z4Plncj8wK1fQlDh94s7gb2AHTc=", + "dev": true, + "requires": { + "for-own": "1.0.0", + "make-iterator": "1.0.1" + }, + "dependencies": { + "for-own": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/for-own/-/for-own-1.0.0.tgz", + "integrity": "sha1-xjMy9BXO3EsE2/5wz4NklMU8tEs=", + "dev": true, + "requires": { + "for-in": "1.0.2" + } + } + } + }, + "object.omit": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/object.omit/-/object.omit-2.0.1.tgz", + "integrity": "sha1-Gpx0SCnznbuFjHbKNXmuKlTr0fo=", + "dev": true, + "requires": { + "for-own": "0.1.5", + "is-extendable": "0.1.1" + } + }, + "object.pick": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", + "integrity": "sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=", + "dev": true, + "requires": { + "isobject": "3.0.1" + }, + "dependencies": { + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true + } + } + }, + "object.reduce": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/object.reduce/-/object.reduce-1.0.1.tgz", + "integrity": "sha1-b+NI8qx/oPlcpiEiZZkJaCW7A60=", + "dev": true, + "requires": { + "for-own": "1.0.0", + "make-iterator": "1.0.1" + }, + "dependencies": { + "for-own": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/for-own/-/for-own-1.0.0.tgz", + "integrity": "sha1-xjMy9BXO3EsE2/5wz4NklMU8tEs=", + "dev": true, + "requires": { + "for-in": "1.0.2" + } + } + } + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "requires": { + "wrappy": "1.0.2" + } + }, + "optimist": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz", + "integrity": "sha1-2j6nRob6IaGaERwybpDrFaAZZoY=", + "dev": true, + "requires": { + "minimist": "0.0.10", + "wordwrap": "0.0.2" + }, + "dependencies": { + "minimist": { + "version": "0.0.10", + "resolved": "http://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz", + "integrity": "sha1-3j+YVD2/lggr5IrRoMfNqDYwHc8=", + "dev": true + } + } + }, + "optionator": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.2.tgz", + "integrity": "sha1-NkxeQJ0/TWMB1sC0wFu6UBgK62Q=", + "dev": true, + "requires": { + "deep-is": "0.1.3", + "fast-levenshtein": "2.0.6", + "levn": "0.3.0", + "prelude-ls": "1.1.2", + "type-check": "0.3.2", + "wordwrap": "1.0.0" + }, + "dependencies": { + "wordwrap": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=", + "dev": true + } + } + }, + "ordered-read-streams": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/ordered-read-streams/-/ordered-read-streams-0.3.0.tgz", + "integrity": "sha1-cTfmmzKYuzQiR6G77jiByA4v14s=", + "dev": true, + "requires": { + "is-stream": "1.1.0", + "readable-stream": "2.3.3" + } + }, + "os-browserify": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/os-browserify/-/os-browserify-0.3.0.tgz", + "integrity": "sha1-hUNzx/XCMVkU/Jv8a9gjj92h7Cc=", + "dev": true + }, + "os-homedir": { + "version": "1.0.2", + "resolved": "http://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", + "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=", + "dev": true + }, + "os-locale": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-2.1.0.tgz", + "integrity": "sha512-3sslG3zJbEYcaC4YVAvDorjGxc7tv6KVATnLPZONiljsUncvihe9BQoVCEs0RZ1kmf4Hk9OBqlZfJZWI4GanKA==", + "dev": true, + "requires": { + "execa": "0.7.0", + "lcid": "1.0.0", + "mem": "1.1.0" + } + }, + "os-tmpdir": { + "version": "1.0.2", + "resolved": "http://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", + "dev": true + }, + "p-finally": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", + "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=", + "dev": true + }, + "p-limit": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.1.0.tgz", + "integrity": "sha1-sH/y2aXYi+yAYDWJWiurZqJ5iLw=", + "dev": true + }, + "p-locate": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", + "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", + "dev": true, + "requires": { + "p-limit": "1.1.0" + } + }, + "pako": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.6.tgz", + "integrity": "sha512-lQe48YPsMJAig+yngZ87Lus+NF+3mtu7DVOBu6b/gHO1YpKwIj5AWjZ/TOS7i46HD/UixzWb1zeWDZfGZ3iYcg==", + "dev": true + }, + "parse-asn1": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.0.tgz", + "integrity": "sha1-N8T5t+06tlx0gXtfJICTf7+XxxI=", + "dev": true, + "requires": { + "asn1.js": "4.9.2", + "browserify-aes": "1.1.1", + "create-hash": "1.1.3", + "evp_bytestokey": "1.0.3", + "pbkdf2": "3.0.14" + } + }, + "parse-filepath": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/parse-filepath/-/parse-filepath-1.0.2.tgz", + "integrity": "sha1-pjISf1Oq89FYdvWHLz/6x2PWyJE=", + "dev": true, + "requires": { + "is-absolute": "1.0.0", + "map-cache": "0.2.2", + "path-root": "0.1.1" + } + }, + "parse-glob": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/parse-glob/-/parse-glob-3.0.4.tgz", + "integrity": "sha1-ssN2z7EfNVE7rdFz7wu246OIORw=", + "dev": true, + "requires": { + "glob-base": "0.3.0", + "is-dotfile": "1.0.3", + "is-extglob": "1.0.0", + "is-glob": "2.0.1" + }, + "dependencies": { + "is-extglob": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz", + "integrity": "sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA=", + "dev": true + }, + "is-glob": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz", + "integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=", + "dev": true, + "requires": { + "is-extglob": "1.0.0" + } + } + } + }, + "parse-json": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", + "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", + "dev": true, + "requires": { + "error-ex": "1.3.1" + } + }, + "parse-passwd": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/parse-passwd/-/parse-passwd-1.0.0.tgz", + "integrity": "sha1-bVuTSkVpk7I9N/QKOC1vFmao5cY=", + "dev": true + }, + "parse5": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-4.0.0.tgz", + "integrity": "sha512-VrZ7eOd3T1Fk4XWNXMgiGBK/z0MG48BWG2uQNU4I72fkQuKUTZpl+u9k+CxEG0twMVzSmXEEz12z5Fnw1jIQFA==", + "dev": true + }, + "pascalcase": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz", + "integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=", + "dev": true + }, + "path-browserify": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-0.0.0.tgz", + "integrity": "sha1-oLhwcpquIUAFt9UDLsLLuw+0RRo=", + "dev": true + }, + "path-dirname": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/path-dirname/-/path-dirname-1.0.2.tgz", + "integrity": "sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA=", + "dev": true + }, + "path-exists": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz", + "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=", + "dev": true, + "requires": { + "pinkie-promise": "2.0.1" + } + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true + }, + "path-key": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", + "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", + "dev": true + }, + "path-parse": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.5.tgz", + "integrity": "sha1-PBrfhx6pzWyUMbbqK9dKD/BVxME=", + "dev": true + }, + "path-root": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/path-root/-/path-root-0.1.1.tgz", + "integrity": "sha1-mkpoFMrBwM1zNgqV8yCDyOpHRbc=", + "dev": true, + "requires": { + "path-root-regex": "0.1.2" + } + }, + "path-root-regex": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/path-root-regex/-/path-root-regex-0.1.2.tgz", + "integrity": "sha1-v8zcjfWxLcUsi0PsONGNcsBLqW0=", + "dev": true + }, + "path-type": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz", + "integrity": "sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE=", + "dev": true, + "requires": { + "graceful-fs": "4.1.11", + "pify": "2.3.0", + "pinkie-promise": "2.0.1" + } + }, + "pbkdf2": { + "version": "3.0.14", + "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.0.14.tgz", + "integrity": "sha512-gjsZW9O34fm0R7PaLHRJmLLVfSoesxztjPjE9o6R+qtVJij90ltg1joIovN9GKrRW3t1PzhDDG3UMEMFfZ+1wA==", + "dev": true, + "requires": { + "create-hash": "1.1.3", + "create-hmac": "1.1.6", + "ripemd160": "2.0.1", + "safe-buffer": "5.1.1", + "sha.js": "2.4.9" + } + }, + "performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=", + "dev": true + }, + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "dev": true + }, + "pinkie": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", + "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=", + "dev": true + }, + "pinkie-promise": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", + "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", + "dev": true, + "requires": { + "pinkie": "2.0.4" + } + }, + "pkg-dir": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-2.0.0.tgz", + "integrity": "sha1-9tXREJ4Z1j7fQo4L1X4Sd3YVM0s=", + "dev": true, + "requires": { + "find-up": "2.1.0" + }, + "dependencies": { + "find-up": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", + "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", + "dev": true, + "requires": { + "locate-path": "2.0.0" + } + } + } + }, + "pn": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/pn/-/pn-1.1.0.tgz", + "integrity": "sha512-2qHaIQr2VLRFoxe2nASzsV6ef4yOOH+Fi9FBOVH6cqeSgUnoyySPZkxzLuzd+RYOQTRpROA0ztTMqxROKSb/nA==", + "dev": true + }, + "posix-character-classes": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", + "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=", + "dev": true + }, + "prelude-ls": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", + "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", + "dev": true + }, + "preserve": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/preserve/-/preserve-0.2.0.tgz", + "integrity": "sha1-gV7R9uvGWSb4ZbMQwHE7yzMVzks=", + "dev": true + }, + "pretty-format": { + "version": "23.6.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-23.6.0.tgz", + "integrity": "sha512-zf9NV1NSlDLDjycnwm6hpFATCGl/K1lt0R/GdkAK2O5LN/rwJoB+Mh93gGJjut4YbmecbfgLWVGSTCr0Ewvvbw==", + "dev": true, + "requires": { + "ansi-regex": "3.0.0", + "ansi-styles": "3.2.1" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true + }, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "1.9.1" + } + } + } + }, + "pretty-hrtime": { + "version": "1.0.3", + "resolved": "http://registry.npmjs.org/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz", + "integrity": "sha1-t+PqQkNaTJsnWdmeDyAesZWALuE=", + "dev": true + }, + "private": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/private/-/private-0.1.8.tgz", + "integrity": "sha512-VvivMrbvd2nKkiG38qjULzlc+4Vx4wm/whI9pQD35YrARNnhxeiRktSOhSukRLFNlzg6Br/cJPet5J/u19r/mg==", + "dev": true + }, + "process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha1-czIwDoQBYb2j5podHZGn1LwW8YI=", + "dev": true + }, + "process-nextick-args": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz", + "integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M=", + "dev": true + }, + "prompts": { + "version": "0.1.14", + "resolved": "https://registry.npmjs.org/prompts/-/prompts-0.1.14.tgz", + "integrity": "sha512-rxkyiE9YH6zAz/rZpywySLKkpaj0NMVyNw1qhsubdbjjSgcayjTShDreZGlFMcGSu5sab3bAKPfFk78PB90+8w==", + "dev": true, + "requires": { + "kleur": "2.0.2", + "sisteransi": "0.1.1" + } + }, + "prr": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/prr/-/prr-0.0.0.tgz", + "integrity": "sha1-GoS4WQgyVQFBGFPQCB7j+obikmo=", + "dev": true + }, + "pseudomap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", + "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=", + "dev": true + }, + "psl": { + "version": "1.1.29", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.1.29.tgz", + "integrity": "sha512-AeUmQ0oLN02flVHXWh9sSJF7mcdFq0ppid/JkErufc3hGIV/AMa8Fo9VgDo/cT2jFdOWoFvHp90qqBH54W+gjQ==", + "dev": true + }, + "public-encrypt": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/public-encrypt/-/public-encrypt-4.0.0.tgz", + "integrity": "sha1-OfaZ86RlYN1eusvKaTyvfGXBjMY=", + "dev": true, + "requires": { + "bn.js": "4.11.8", + "browserify-rsa": "4.0.1", + "create-hash": "1.1.3", + "parse-asn1": "5.1.0", + "randombytes": "2.0.5" + } + }, + "pump": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pump/-/pump-2.0.1.tgz", + "integrity": "sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==", + "dev": true, + "requires": { + "end-of-stream": "1.4.0", + "once": "1.4.0" + } + }, + "pumpify": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/pumpify/-/pumpify-1.5.1.tgz", + "integrity": "sha512-oClZI37HvuUJJxSKKrC17bZ9Cu0ZYhEAGPsPUy9KlMUmv9dKX2o77RUmq7f3XjIxbwyGwYzbzQ1L2Ks8sIradQ==", + "dev": true, + "requires": { + "duplexify": "3.6.1", + "inherits": "2.0.3", + "pump": "2.0.1" + }, + "dependencies": { + "duplexify": { + "version": "3.6.1", + "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.6.1.tgz", + "integrity": "sha512-vM58DwdnKmty+FSPzT14K9JXb90H+j5emaR4KYbr2KTIz00WHGbWOe5ghQTx233ZCLZtrGDALzKwcjEtSt35mA==", + "dev": true, + "requires": { + "end-of-stream": "1.4.0", + "inherits": "2.0.3", + "readable-stream": "2.3.3", + "stream-shift": "1.0.0" + } + } + } + }, + "punycode": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=", + "dev": true + }, + "qs": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", + "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==", + "dev": true + }, + "querystring": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", + "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=", + "dev": true + }, + "querystring-es3": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/querystring-es3/-/querystring-es3-0.2.1.tgz", + "integrity": "sha1-nsYfeQSYdXB9aUFFlv2Qek1xHnM=", + "dev": true + }, + "randombytes": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.0.5.tgz", + "integrity": "sha512-8T7Zn1AhMsQ/HI1SjcCfT/t4ii3eAqco3yOcSzS4mozsOz69lHLsoMXmF9nZgnFanYscnSlUSgs8uZyKzpE6kg==", + "dev": true, + "requires": { + "safe-buffer": "5.1.1" + } + }, + "randomfill": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/randomfill/-/randomfill-1.0.3.tgz", + "integrity": "sha512-YL6GrhrWoic0Eq8rXVbMptH7dAxCs0J+mh5Y0euNekPPYaxEmdVGim6GdoxoRzKW2yJoU8tueifS7mYxvcFDEQ==", + "dev": true, + "requires": { + "randombytes": "2.0.5", + "safe-buffer": "5.1.1" + } + }, + "read-pkg": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz", + "integrity": "sha1-9f+qXs0pyzHAR0vKfXVra7KePyg=", + "dev": true, + "requires": { + "load-json-file": "1.1.0", + "normalize-package-data": "2.4.0", + "path-type": "1.1.0" + } + }, + "read-pkg-up": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-1.0.1.tgz", + "integrity": "sha1-nWPBMnbAZZGNV/ACpX9AobZD+wI=", + "dev": true, + "requires": { + "find-up": "1.1.2", + "read-pkg": "1.1.0" + } + }, + "readable-stream": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.3.tgz", + "integrity": "sha512-m+qzzcn7KUxEmd1gMbchF+Y2eIUbieUaxkWtptyHywrX0rE8QEYqPC07Vuy4Wm32/xE16NcdBctb8S0Xe/5IeQ==", + "dev": true, + "requires": { + "core-util-is": "1.0.2", + "inherits": "2.0.3", + "isarray": "1.0.0", + "process-nextick-args": "1.0.7", + "safe-buffer": "5.1.1", + "string_decoder": "1.0.3", + "util-deprecate": "1.0.2" + } + }, + "readdirp": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.1.0.tgz", + "integrity": "sha1-TtCtBg3zBzMAxIRANz9y0cxkLXg=", + "dev": true, + "requires": { + "graceful-fs": "4.1.11", + "minimatch": "3.0.4", + "readable-stream": "2.3.3", + "set-immediate-shim": "1.0.1" + } + }, + "realpath-native": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/realpath-native/-/realpath-native-1.0.2.tgz", + "integrity": "sha512-+S3zTvVt9yTntFrBpm7TQmQ3tzpCrnA1a/y+3cUHAc9ZR6aIjG0WNLR+Rj79QpJktY+VeW/TQtFlQ1bzsehI8g==", + "dev": true, + "requires": { + "util.promisify": "1.0.0" + } + }, + "rechoir": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz", + "integrity": "sha1-hSBLVNuoLVdC4oyWdW70OvUOM4Q=", + "dev": true, + "requires": { + "resolve": "1.5.0" + } + }, + "redent": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/redent/-/redent-1.0.0.tgz", + "integrity": "sha1-z5Fqsf1fHxbfsggi3W7H9zDCr94=", + "dev": true, + "requires": { + "indent-string": "2.1.0", + "strip-indent": "1.0.1" + } + }, + "regenerator-runtime": { + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz", + "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==", + "dev": true + }, + "regex-cache": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/regex-cache/-/regex-cache-0.4.4.tgz", + "integrity": "sha512-nVIZwtCjkC9YgvWkpM55B5rBhBYRZhAaJbgcFYXXsHnbZ9UZI9nnVWYZpBlCqv9ho2eZryPnWrZGsOdPwVWXWQ==", + "dev": true, + "requires": { + "is-equal-shallow": "0.1.3" + } + }, + "regex-not": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", + "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==", + "dev": true, + "requires": { + "extend-shallow": "3.0.2", + "safe-regex": "1.1.0" + } + }, + "remove-bom-buffer": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/remove-bom-buffer/-/remove-bom-buffer-3.0.0.tgz", + "integrity": "sha512-8v2rWhaakv18qcvNeli2mZ/TMTL2nEyAKRvzo1WtnZBl15SHyEhrCu2/xKlJyUFKHiHgfXIyuY6g2dObJJycXQ==", + "dev": true, + "requires": { + "is-buffer": "1.1.6", + "is-utf8": "0.2.1" + } + }, + "remove-bom-stream": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/remove-bom-stream/-/remove-bom-stream-1.2.0.tgz", + "integrity": "sha1-BfGlk/FuQuH7kOv1nejlaVJflSM=", + "dev": true, + "requires": { + "remove-bom-buffer": "3.0.0", + "safe-buffer": "5.1.1", + "through2": "2.0.3" + } + }, + "remove-trailing-separator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", + "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=", + "dev": true + }, + "repeat-element": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.2.tgz", + "integrity": "sha1-7wiaF40Ug7quTZPrmLT55OEdmQo=", + "dev": true + }, + "repeat-string": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", + "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=", + "dev": true + }, + "repeating": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/repeating/-/repeating-2.0.1.tgz", + "integrity": "sha1-UhTFOpJtNVJwdSf7q0FdvAjQbdo=", + "dev": true, + "requires": { + "is-finite": "1.0.2" + } + }, + "replace-ext": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-0.0.1.tgz", + "integrity": "sha1-KbvZIHinOfC8zitO5B6DeVNSKSQ=", + "dev": true + }, + "replace-homedir": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/replace-homedir/-/replace-homedir-1.0.0.tgz", + "integrity": "sha1-6H9tUTuSjd6AgmDBK+f+xv9ueYw=", + "dev": true, + "requires": { + "homedir-polyfill": "1.0.1", + "is-absolute": "1.0.0", + "remove-trailing-separator": "1.1.0" + } + }, + "request": { + "version": "2.88.0", + "resolved": "https://registry.npmjs.org/request/-/request-2.88.0.tgz", + "integrity": "sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==", + "dev": true, + "requires": { + "aws-sign2": "0.7.0", + "aws4": "1.8.0", + "caseless": "0.12.0", + "combined-stream": "1.0.7", + "extend": "3.0.2", + "forever-agent": "0.6.1", + "form-data": "2.3.3", + "har-validator": "5.1.3", + "http-signature": "1.2.0", + "is-typedarray": "1.0.0", + "isstream": "0.1.2", + "json-stringify-safe": "5.0.1", + "mime-types": "2.1.21", + "oauth-sign": "0.9.0", + "performance-now": "2.1.0", + "qs": "6.5.2", + "safe-buffer": "5.1.2", + "tough-cookie": "2.4.3", + "tunnel-agent": "0.6.0", + "uuid": "3.3.2" + }, + "dependencies": { + "extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "dev": true + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "tough-cookie": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz", + "integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==", + "dev": true, + "requires": { + "psl": "1.1.29", + "punycode": "1.4.1" + } + } + } + }, + "request-promise-core": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.1.tgz", + "integrity": "sha1-Pu4AssWqgyOc+wTFcA2jb4HNCLY=", + "dev": true, + "requires": { + "lodash": "4.17.11" + } + }, + "request-promise-native": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/request-promise-native/-/request-promise-native-1.0.5.tgz", + "integrity": "sha1-UoF3D2jgyXGeUWP9P6tIIhX0/aU=", + "dev": true, + "requires": { + "request-promise-core": "1.1.1", + "stealthy-require": "1.1.1", + "tough-cookie": "2.5.0" + } + }, + "require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", + "dev": true + }, + "require-main-filename": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz", + "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=", + "dev": true + }, + "resolve": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.5.0.tgz", + "integrity": "sha512-hgoSGrc3pjzAPHNBg+KnFcK2HwlHTs/YrAGUr6qgTVUZmXv1UEXXl0bZNBKMA9fud6lRYFdPGz0xXxycPzmmiw==", + "dev": true, + "requires": { + "path-parse": "1.0.5" + } + }, + "resolve-cwd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-2.0.0.tgz", + "integrity": "sha1-AKn3OHVW4nA46uIyyqNypqWbZlo=", + "dev": true, + "requires": { + "resolve-from": "3.0.0" + } + }, + "resolve-dir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/resolve-dir/-/resolve-dir-1.0.1.tgz", + "integrity": "sha1-eaQGRMNivoLybv/nOcm7U4IEb0M=", + "dev": true, + "requires": { + "expand-tilde": "2.0.2", + "global-modules": "1.0.0" + } + }, + "resolve-from": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz", + "integrity": "sha1-six699nWiBvItuZTM17rywoYh0g=", + "dev": true + }, + "resolve-options": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/resolve-options/-/resolve-options-1.1.0.tgz", + "integrity": "sha1-MrueOcBtZzONyTeMDW1gdFZq0TE=", + "dev": true, + "requires": { + "value-or-function": "3.0.0" + } + }, + "resolve-url": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", + "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=", + "dev": true + }, + "ret": { + "version": "0.1.15", + "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", + "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==", + "dev": true + }, + "right-align": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/right-align/-/right-align-0.1.3.tgz", + "integrity": "sha1-YTObci/mo1FWiSENJOFMlhSGE+8=", + "dev": true, + "requires": { + "align-text": "0.1.4" + } + }, + "rimraf": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.2.tgz", + "integrity": "sha512-lreewLK/BlghmxtfH36YYVg1i8IAce4TI7oao75I1g245+6BctqTVQiBP3YUJ9C6DQOXJmkYR9X9fCLtCOJc5w==", + "dev": true, + "requires": { + "glob": "7.1.3" + }, + "dependencies": { + "glob": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", + "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", + "dev": true, + "requires": { + "fs.realpath": "1.0.0", + "inflight": "1.0.6", + "inherits": "2.0.3", + "minimatch": "3.0.4", + "once": "1.4.0", + "path-is-absolute": "1.0.1" + } + } + } + }, + "ripemd160": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.1.tgz", + "integrity": "sha1-D0WEKVxTo2KK9+bXmsohzlfRxuc=", + "dev": true, + "requires": { + "hash-base": "2.0.2", + "inherits": "2.0.3" + } + }, + "rsvp": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/rsvp/-/rsvp-3.6.2.tgz", + "integrity": "sha512-OfWGQTb9vnwRjwtA2QwpG2ICclHC3pgXZO5xt8H2EfgDquO0qVdSb5T88L4qJVAEugbS56pAuV4XZM58UX8ulw==", + "dev": true + }, + "safe-buffer": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", + "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==", + "dev": true + }, + "safe-regex": { + "version": "1.1.0", + "resolved": "http://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", + "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=", + "dev": true, + "requires": { + "ret": "0.1.15" + } + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true + }, + "sane": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/sane/-/sane-2.5.2.tgz", + "integrity": "sha1-tNwYYcIbQn6SlQej51HiosuKs/o=", + "dev": true, + "requires": { + "anymatch": "2.0.0", + "capture-exit": "1.2.0", + "exec-sh": "0.2.2", + "fb-watchman": "2.0.0", + "fsevents": "1.2.4", + "micromatch": "3.1.10", + "minimist": "1.2.0", + "walker": "1.0.7", + "watch": "0.18.0" + }, + "dependencies": { + "array-unique": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", + "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", + "dev": true + }, + "braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "dev": true, + "requires": { + "arr-flatten": "1.1.0", + "array-unique": "0.3.2", + "extend-shallow": "2.0.1", + "fill-range": "4.0.0", + "isobject": "3.0.1", + "repeat-element": "1.1.2", + "snapdragon": "0.8.2", + "snapdragon-node": "2.1.1", + "split-string": "3.1.0", + "to-regex": "3.0.2" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "0.1.1" + } + } + } + }, + "expand-brackets": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", + "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", + "dev": true, + "requires": { + "debug": "2.6.9", + "define-property": "0.2.5", + "extend-shallow": "2.0.1", + "posix-character-classes": "0.1.1", + "regex-not": "1.0.2", + "snapdragon": "0.8.2", + "to-regex": "3.0.2" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "0.1.6" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "0.1.1" + } + }, + "is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", + "dev": true, + "requires": { + "kind-of": "3.2.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "1.1.6" + } + } + } + }, + "is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", + "dev": true, + "requires": { + "kind-of": "3.2.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "1.1.6" + } + } + } + }, + "is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "0.1.6", + "is-data-descriptor": "0.1.4", + "kind-of": "5.1.0" + } + }, + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "dev": true + } + } + }, + "extglob": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", + "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", + "dev": true, + "requires": { + "array-unique": "0.3.2", + "define-property": "1.0.0", + "expand-brackets": "2.1.4", + "extend-shallow": "2.0.1", + "fragment-cache": "0.2.1", + "regex-not": "1.0.2", + "snapdragon": "0.8.2", + "to-regex": "3.0.2" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "requires": { + "is-descriptor": "1.0.2" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "0.1.1" + } + } + } + }, + "fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", + "dev": true, + "requires": { + "extend-shallow": "2.0.1", + "is-number": "3.0.0", + "repeat-string": "1.6.1", + "to-regex-range": "2.1.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "0.1.1" + } + } + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "requires": { + "kind-of": "6.0.2" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "requires": { + "kind-of": "6.0.2" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "1.0.0", + "is-data-descriptor": "1.0.0", + "kind-of": "6.0.2" + } + }, + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "dev": true, + "requires": { + "kind-of": "3.2.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "1.1.6" + } + } + } + }, + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true + }, + "micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "dev": true, + "requires": { + "arr-diff": "4.0.0", + "array-unique": "0.3.2", + "braces": "2.3.2", + "define-property": "2.0.2", + "extend-shallow": "3.0.2", + "extglob": "2.0.4", + "fragment-cache": "0.2.1", + "kind-of": "6.0.2", + "nanomatch": "1.2.13", + "object.pick": "1.3.0", + "regex-not": "1.0.2", + "snapdragon": "0.8.2", + "to-regex": "3.0.2" + } + } + } + }, + "sax": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", + "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==", + "dev": true + }, + "semver": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.6.0.tgz", + "integrity": "sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg==", + "dev": true + }, + "semver-greatest-satisfied-range": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/semver-greatest-satisfied-range/-/semver-greatest-satisfied-range-1.1.0.tgz", + "integrity": "sha1-E+jCZYq5aRywzXEJMkAoDTb3els=", + "dev": true, + "requires": { + "sver-compat": "1.5.0" + } + }, + "set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", + "dev": true + }, + "set-immediate-shim": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz", + "integrity": "sha1-SysbJ+uAip+NzEgaWOXlb1mfP2E=", + "dev": true + }, + "set-value": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.0.tgz", + "integrity": "sha512-hw0yxk9GT/Hr5yJEYnHNKYXkIA8mVJgd9ditYZCe16ZczcaELYYcfvaXesNACk2O8O0nTiPQcQhGUQj8JLzeeg==", + "dev": true, + "requires": { + "extend-shallow": "2.0.1", + "is-extendable": "0.1.1", + "is-plain-object": "2.0.4", + "split-string": "3.1.0" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "0.1.1" + } + } + } + }, + "setimmediate": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", + "integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=", + "dev": true + }, + "sha.js": { + "version": "2.4.9", + "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.9.tgz", + "integrity": "sha512-G8zektVqbiPHrylgew9Zg1VRB1L/DtXNUVAM6q4QLy8NE3qtHlFXTf8VLL4k1Yl6c7NMjtZUTdXV+X44nFaT6A==", + "dev": true, + "requires": { + "inherits": "2.0.3", + "safe-buffer": "5.1.1" + } + }, + "shebang-command": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", + "dev": true, + "requires": { + "shebang-regex": "1.0.0" + } + }, + "shebang-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", + "dev": true + }, + "shellwords": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/shellwords/-/shellwords-0.1.1.tgz", + "integrity": "sha512-vFwSUfQvqybiICwZY5+DAWIPLKsWO31Q91JSKl3UYv+K5c2QRPzn0qzec6QPu1Qc9eHYItiP3NdJqNVqetYAww==", + "dev": true + }, + "signal-exit": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", + "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", + "dev": true + }, + "sisteransi": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-0.1.1.tgz", + "integrity": "sha512-PmGOd02bM9YO5ifxpw36nrNMBTptEtfRl4qUYl9SndkolplkrZZOW7PGHjrZL53QvMVj9nQ+TKqUnRsw4tJa4g==", + "dev": true + }, + "slash": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-1.0.0.tgz", + "integrity": "sha1-xB8vbDn8FtHNF61LXYlhFK5HDVU=", + "dev": true + }, + "snapdragon": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", + "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==", + "dev": true, + "requires": { + "base": "0.11.2", + "debug": "2.6.9", + "define-property": "0.2.5", + "extend-shallow": "2.0.1", + "map-cache": "0.2.2", + "source-map": "0.5.7", + "source-map-resolve": "0.5.2", + "use": "3.1.1" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "0.1.6" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "0.1.1" + } + } + } + }, + "snapdragon-node": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz", + "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==", + "dev": true, + "requires": { + "define-property": "1.0.0", + "isobject": "3.0.1", + "snapdragon-util": "3.0.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "requires": { + "is-descriptor": "1.0.2" + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "requires": { + "kind-of": "6.0.2" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "requires": { + "kind-of": "6.0.2" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "1.0.0", + "is-data-descriptor": "1.0.0", + "kind-of": "6.0.2" + } + }, + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true + } + } + }, + "snapdragon-util": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz", + "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==", + "dev": true, + "requires": { + "kind-of": "3.2.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "1.1.6" + } + } + } + }, + "source-list-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.0.tgz", + "integrity": "sha512-I2UmuJSRr/T8jisiROLU3A3ltr+swpniSmNPI4Ml3ZCX6tVnDsuZzK7F2hl5jTqbZBWCEKlj5HRQiPExXLgE8A==", + "dev": true + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + }, + "source-map-loader": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/source-map-loader/-/source-map-loader-0.2.3.tgz", + "integrity": "sha512-MYbFX9DYxmTQFfy2v8FC1XZwpwHKYxg3SK8Wb7VPBKuhDjz8gi9re2819MsG4p49HDyiOSUKlmZ+nQBArW5CGw==", + "dev": true, + "requires": { + "async": "2.6.0", + "loader-utils": "0.2.17", + "source-map": "0.6.1" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } + } + }, + "source-map-resolve": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.2.tgz", + "integrity": "sha512-MjqsvNwyz1s0k81Goz/9vRBe9SZdB09Bdw+/zYyO+3CuPk6fouTaxscHkgtE8jKvf01kVfl8riHzERQ/kefaSA==", + "dev": true, + "requires": { + "atob": "2.1.2", + "decode-uri-component": "0.2.0", + "resolve-url": "0.2.1", + "source-map-url": "0.4.0", + "urix": "0.1.0" + } + }, + "source-map-support": { + "version": "0.4.18", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.4.18.tgz", + "integrity": "sha512-try0/JqxPLF9nOjvSta7tVondkP5dwgyLDjVoyMDlmjugT2lRZ1OfsrYTkCd2hkDnJTKRbO/Rl3orm8vlsUzbA==", + "dev": true, + "requires": { + "source-map": "0.5.7" + } + }, + "source-map-url": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.0.tgz", + "integrity": "sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=", + "dev": true + }, + "sparkles": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/sparkles/-/sparkles-1.0.0.tgz", + "integrity": "sha1-Gsu/tZJDbRC76PeFt8xvgoFQEsM=", + "dev": true + }, + "spdx-correct": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-1.0.2.tgz", + "integrity": "sha1-SzBz2TP/UfORLwOsVRlJikFQ20A=", + "dev": true, + "requires": { + "spdx-license-ids": "1.2.2" + } + }, + "spdx-expression-parse": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-1.0.4.tgz", + "integrity": "sha1-m98vIOH0DtRH++JzJmGR/O1RYmw=", + "dev": true + }, + "spdx-license-ids": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-1.2.2.tgz", + "integrity": "sha1-yd96NCRZSt5r0RkA1ZZpbcBrrFc=", + "dev": true + }, + "split-string": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", + "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==", + "dev": true, + "requires": { + "extend-shallow": "3.0.2" + } + }, + "sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", + "dev": true + }, + "sshpk": { + "version": "1.15.2", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.15.2.tgz", + "integrity": "sha512-Ra/OXQtuh0/enyl4ETZAfTaeksa6BXks5ZcjpSUNrjBr0DvrJKX+1fsKDPpT9TBXgHAFsa4510aNVgI8g/+SzA==", + "dev": true, + "requires": { + "asn1": "0.2.4", + "assert-plus": "1.0.0", + "bcrypt-pbkdf": "1.0.2", + "dashdash": "1.14.1", + "ecc-jsbn": "0.1.2", + "getpass": "0.1.7", + "jsbn": "0.1.1", + "safer-buffer": "2.1.2", + "tweetnacl": "0.14.5" + } + }, + "stack-trace": { + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", + "integrity": "sha1-VHxws0fo0ytOEI6hoqFZ5f3eGcA=", + "dev": true + }, + "stack-utils": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-1.0.2.tgz", + "integrity": "sha512-MTX+MeG5U994cazkjd/9KNAapsHnibjMLnfXodlkXw76JEea0UiNzrqidzo1emMwk7w5Qhc9jd4Bn9TBb1MFwA==", + "dev": true + }, + "static-extend": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", + "integrity": "sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY=", + "dev": true, + "requires": { + "define-property": "0.2.5", + "object-copy": "0.1.0" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "0.1.6" + } + } + } + }, + "stealthy-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/stealthy-require/-/stealthy-require-1.1.1.tgz", + "integrity": "sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks=", + "dev": true + }, + "stream-browserify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-2.0.1.tgz", + "integrity": "sha1-ZiZu5fm9uZQKTkUUyvtDu3Hlyds=", + "dev": true, + "requires": { + "inherits": "2.0.3", + "readable-stream": "2.3.3" + } + }, + "stream-exhaust": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/stream-exhaust/-/stream-exhaust-1.0.2.tgz", + "integrity": "sha512-b/qaq/GlBK5xaq1yrK9/zFcyRSTNxmcZwFLGSTG0mXgZl/4Z6GgiyYOXOvY7N3eEvFRAG1bkDRz5EPGSvPYQlw==", + "dev": true + }, + "stream-http": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/stream-http/-/stream-http-2.7.2.tgz", + "integrity": "sha512-c0yTD2rbQzXtSsFSVhtpvY/vS6u066PcXOX9kBB3mSO76RiUQzL340uJkGBWnlBg4/HZzqiUXtaVA7wcRcJgEw==", + "dev": true, + "requires": { + "builtin-status-codes": "3.0.0", + "inherits": "2.0.3", + "readable-stream": "2.3.3", + "to-arraybuffer": "1.0.1", + "xtend": "4.0.1" + } + }, + "stream-shift": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.0.tgz", + "integrity": "sha1-1cdSgl5TZ+eG944Y5EXqIjoVWVI=", + "dev": true + }, + "string-length": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/string-length/-/string-length-2.0.0.tgz", + "integrity": "sha1-1A27aGo6zpYMHP/KVivyxF+DY+0=", + "dev": true, + "requires": { + "astral-regex": "1.0.0", + "strip-ansi": "4.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "requires": { + "ansi-regex": "3.0.0" + } + } + } + }, + "string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "dev": true, + "requires": { + "is-fullwidth-code-point": "2.0.0", + "strip-ansi": "4.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "requires": { + "ansi-regex": "3.0.0" + } + } + } + }, + "string_decoder": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", + "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==", + "dev": true, + "requires": { + "safe-buffer": "5.1.1" + } + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "dev": true, + "requires": { + "ansi-regex": "2.1.1" + } + }, + "strip-bom": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", + "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=", + "dev": true, + "requires": { + "is-utf8": "0.2.1" + } + }, + "strip-bom-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/strip-bom-stream/-/strip-bom-stream-1.0.0.tgz", + "integrity": "sha1-5xRDmFd9Uaa+0PoZlPoF9D/ZiO4=", + "dev": true, + "requires": { + "first-chunk-stream": "1.0.0", + "strip-bom": "2.0.0" + } + }, + "strip-bom-string": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/strip-bom-string/-/strip-bom-string-1.0.0.tgz", + "integrity": "sha1-5SEekiQ2n7uB1jOi8ABE3IztrZI=", + "dev": true + }, + "strip-eof": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", + "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=", + "dev": true + }, + "strip-indent": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-1.0.1.tgz", + "integrity": "sha1-DHlipq3vp7vUrDZkYKY4VSrhoKI=", + "dev": true, + "requires": { + "get-stdin": "4.0.1" + } + }, + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", + "dev": true + }, + "sver-compat": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/sver-compat/-/sver-compat-1.5.0.tgz", + "integrity": "sha1-PPh9/rTQe0o/FIJ7wYaz/QxkXNg=", + "dev": true, + "requires": { + "es6-iterator": "2.0.3", + "es6-symbol": "3.1.1" + } + }, + "symbol-tree": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.2.tgz", + "integrity": "sha1-rifbOPZgp64uHDt9G8KQgZuFGeY=", + "dev": true + }, + "tapable": { + "version": "0.2.8", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-0.2.8.tgz", + "integrity": "sha1-mTcqXJmb8t8WCvwNdL7U9HlIzSI=", + "dev": true + }, + "test-exclude": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-4.2.3.tgz", + "integrity": "sha512-SYbXgY64PT+4GAL2ocI3HwPa4Q4TBKm0cwAVeKOt/Aoc0gSpNRjJX8w0pA1LMKZ3LBmd8pYBqApFNQLII9kavA==", + "dev": true, + "requires": { + "arrify": "1.0.1", + "micromatch": "2.3.11", + "object-assign": "4.1.1", + "read-pkg-up": "1.0.1", + "require-main-filename": "1.0.1" + } + }, + "throat": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/throat/-/throat-4.1.0.tgz", + "integrity": "sha1-iQN8vJLFarGJJua6TLsgDhVnKmo=", + "dev": true + }, + "through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", + "dev": true + }, + "through2": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.3.tgz", + "integrity": "sha1-AARWmzfHx0ujnEPzzteNGtlBQL4=", + "dev": true, + "requires": { + "readable-stream": "2.3.3", + "xtend": "4.0.1" + } + }, + "through2-filter": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/through2-filter/-/through2-filter-2.0.0.tgz", + "integrity": "sha1-YLxVoNrLdghdsfna6Zq0P4PWIuw=", + "dev": true, + "requires": { + "through2": "2.0.3", + "xtend": "4.0.1" + } + }, + "time-stamp": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/time-stamp/-/time-stamp-1.1.0.tgz", + "integrity": "sha1-dkpaEa9QVhkhsTPztE5hhofg9cM=", + "dev": true + }, + "timers-browserify": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-2.0.4.tgz", + "integrity": "sha512-uZYhyU3EX8O7HQP+J9fTVYwsq90Vr68xPEFo7yrVImIxYvHgukBEgOB/SgGoorWVTzGM/3Z+wUNnboA4M8jWrg==", + "dev": true, + "requires": { + "setimmediate": "1.0.5" + } + }, + "timers-ext": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/timers-ext/-/timers-ext-0.1.7.tgz", + "integrity": "sha512-b85NUNzTSdodShTIbky6ZF02e8STtVVfD+fu4aXXShEELpozH+bCpJLYMPZbsABN2wDH7fJpqIoXxJpzbf0NqQ==", + "dev": true, + "requires": { + "es5-ext": "0.10.46", + "next-tick": "1.0.0" + }, + "dependencies": { + "es5-ext": { + "version": "0.10.46", + "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.46.tgz", + "integrity": "sha512-24XxRvJXNFwEMpJb3nOkiRJKRoupmjYmOPVlI65Qy2SrtxwOTB+g6ODjBKOtwEHbYrhWRty9xxOWLNdClT2djw==", + "dev": true, + "requires": { + "es6-iterator": "2.0.3", + "es6-symbol": "3.1.1", + "next-tick": "1.0.0" + } + } + } + }, + "tmpl": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.4.tgz", + "integrity": "sha1-I2QN17QtAEM5ERQIIOXPRA5SHdE=", + "dev": true + }, + "to-absolute-glob": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/to-absolute-glob/-/to-absolute-glob-0.1.1.tgz", + "integrity": "sha1-HN+kcqnvUMI57maZm2YsoOs5k38=", + "dev": true, + "requires": { + "extend-shallow": "2.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "0.1.1" + } + } + } + }, + "to-arraybuffer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz", + "integrity": "sha1-fSKbH8xjfkZsoIEYCDanqr/4P0M=", + "dev": true + }, + "to-fast-properties": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-1.0.3.tgz", + "integrity": "sha1-uDVx+k2MJbguIxsG46MFXeTKGkc=", + "dev": true + }, + "to-object-path": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz", + "integrity": "sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68=", + "dev": true, + "requires": { + "kind-of": "3.2.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "1.1.6" + } + } + } + }, + "to-regex": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz", + "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==", + "dev": true, + "requires": { + "define-property": "2.0.2", + "extend-shallow": "3.0.2", + "regex-not": "1.0.2", + "safe-regex": "1.1.0" + } + }, + "to-regex-range": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", + "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", + "dev": true, + "requires": { + "is-number": "3.0.0", + "repeat-string": "1.6.1" + }, + "dependencies": { + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "dev": true, + "requires": { + "kind-of": "3.2.2" + } + }, + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "1.1.6" + } + } + } + }, + "to-through": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-through/-/to-through-2.0.0.tgz", + "integrity": "sha1-/JKtq6ByZHvAtn1rA2ZKoZUJOvY=", + "dev": true, + "requires": { + "through2": "2.0.3" + } + }, + "tough-cookie": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", + "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", + "dev": true, + "requires": { + "psl": "1.1.29", + "punycode": "2.1.1" + }, + "dependencies": { + "punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "dev": true + } + } + }, + "tr46": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-1.0.1.tgz", + "integrity": "sha1-qLE/1r/SSJUZZ0zN5VujaTtwbQk=", + "dev": true, + "requires": { + "punycode": "2.1.1" + }, + "dependencies": { + "punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "dev": true + } + } + }, + "trim-newlines": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-1.0.0.tgz", + "integrity": "sha1-WIeWa7WCpFA6QetST301ARgVphM=", + "dev": true + }, + "trim-right": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/trim-right/-/trim-right-1.0.1.tgz", + "integrity": "sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM=", + "dev": true + }, + "ts-jest": { + "version": "23.10.5", + "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-23.10.5.tgz", + "integrity": "sha512-MRCs9qnGoyKgFc8adDEntAOP64fWK1vZKnOYU1o2HxaqjdJvGqmkLCPCnVq1/If4zkUmEjKPnCiUisTrlX2p2A==", + "dev": true, + "requires": { + "bs-logger": "0.2.6", + "buffer-from": "1.1.1", + "fast-json-stable-stringify": "2.0.0", + "json5": "2.1.0", + "make-error": "1.3.5", + "mkdirp": "0.5.1", + "resolve": "1.5.0", + "semver": "5.6.0", + "yargs-parser": "10.1.0" + }, + "dependencies": { + "camelcase": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz", + "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=", + "dev": true + }, + "json5": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.1.0.tgz", + "integrity": "sha512-8Mh9h6xViijj36g7Dxi+Y4S6hNGV96vcJZr/SrlHh1LR/pEn/8j/+qIBbs44YKl69Lrfctp4QD+AdWLTMqEZAQ==", + "dev": true, + "requires": { + "minimist": "1.2.0" + } + }, + "semver": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.6.0.tgz", + "integrity": "sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg==", + "dev": true + }, + "yargs-parser": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-10.1.0.tgz", + "integrity": "sha512-VCIyR1wJoEBZUqk5PA+oOBF6ypbwh5aNB3I50guxAL/quggdfs4TtNHQrSazFA3fYZ+tEqfs0zIGlv0c/rgjbQ==", + "dev": true, + "requires": { + "camelcase": "4.1.0" + } + } + } + }, + "tslib": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.9.3.tgz", + "integrity": "sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ==", + "dev": true + }, + "tslint": { + "version": "5.11.0", + "resolved": "https://registry.npmjs.org/tslint/-/tslint-5.11.0.tgz", + "integrity": "sha1-mPMMAurjzecAYgHkwzywi0hYHu0=", + "dev": true, + "requires": { + "babel-code-frame": "6.26.0", + "builtin-modules": "1.1.1", + "chalk": "2.4.1", + "commander": "2.19.0", + "diff": "3.5.0", + "glob": "7.1.3", + "js-yaml": "3.12.0", + "minimatch": "3.0.4", + "resolve": "1.5.0", + "semver": "5.6.0", + "tslib": "1.9.3", + "tsutils": "2.29.0" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "1.9.1" + } + }, + "chalk": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", + "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", + "dev": true, + "requires": { + "ansi-styles": "3.2.1", + "escape-string-regexp": "1.0.5", + "supports-color": "5.5.0" + } + }, + "glob": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", + "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", + "dev": true, + "requires": { + "fs.realpath": "1.0.0", + "inflight": "1.0.6", + "inherits": "2.0.3", + "minimatch": "3.0.4", + "once": "1.4.0", + "path-is-absolute": "1.0.1" + } + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "3.0.0" + } + } + } + }, + "tsutils": { + "version": "2.29.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-2.29.0.tgz", + "integrity": "sha512-g5JVHCIJwzfISaXpXE1qvNalca5Jwob6FjI4AoPlqMusJ6ftFE7IkkFoMhVLRgK+4Kx3gkzb8UZK5t5yTTvEmA==", + "dev": true, + "requires": { + "tslib": "1.9.3" + } + }, + "tty-browserify": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.0.tgz", + "integrity": "sha1-oVe6QC2iTpv5V/mqadUk7tQpAaY=", + "dev": true + }, + "tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", + "dev": true, + "requires": { + "safe-buffer": "5.1.1" + } + }, + "tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", + "dev": true + }, + "type-check": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", + "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", + "dev": true, + "requires": { + "prelude-ls": "1.1.2" + } + }, + "typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=", + "dev": true + }, + "typescript": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.2.1.tgz", + "integrity": "sha512-jw7P2z/h6aPT4AENXDGjcfHTu5CSqzsbZc6YlUIebTyBAq8XaKp78x7VcSh30xwSCcsu5irZkYZUSFP1MrAMbg==", + "dev": true + }, + "uglify-js": { + "version": "2.8.29", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-2.8.29.tgz", + "integrity": "sha1-KcVzMUgFe7Th913zW3qcty5qWd0=", + "dev": true, + "requires": { + "source-map": "0.5.7", + "uglify-to-browserify": "1.0.2", + "yargs": "3.10.0" + }, + "dependencies": { + "camelcase": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-1.2.1.tgz", + "integrity": "sha1-m7UwTS4LVmmLLHWLCKPqqdqlijk=", + "dev": true + }, + "yargs": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-3.10.0.tgz", + "integrity": "sha1-9+572FfdfB0tOMDnTvvWgdFDH9E=", + "dev": true, + "requires": { + "camelcase": "1.2.1", + "cliui": "2.1.0", + "decamelize": "1.2.0", + "window-size": "0.1.0" + } + } + } + }, + "uglify-to-browserify": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/uglify-to-browserify/-/uglify-to-browserify-1.0.2.tgz", + "integrity": "sha1-bgkk1r2mta/jSeOabWMoUKD4grc=", + "dev": true, + "optional": true + }, + "uglifyjs-webpack-plugin": { + "version": "0.4.6", + "resolved": "https://registry.npmjs.org/uglifyjs-webpack-plugin/-/uglifyjs-webpack-plugin-0.4.6.tgz", + "integrity": "sha1-uVH0q7a9YX5m9j64kUmOORdj4wk=", + "dev": true, + "requires": { + "source-map": "0.5.7", + "uglify-js": "2.8.29", + "webpack-sources": "1.1.0" + } + }, + "unc-path-regex": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/unc-path-regex/-/unc-path-regex-0.1.2.tgz", + "integrity": "sha1-5z3T17DXxe2G+6xrCufYxqadUPo=", + "dev": true + }, + "undertaker": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/undertaker/-/undertaker-1.2.0.tgz", + "integrity": "sha1-M52kZGJS0ILcN45wgGcpl1DhG0k=", + "dev": true, + "requires": { + "arr-flatten": "1.1.0", + "arr-map": "2.0.2", + "bach": "1.2.0", + "collection-map": "1.0.0", + "es6-weak-map": "2.0.2", + "last-run": "1.1.1", + "object.defaults": "1.1.0", + "object.reduce": "1.0.1", + "undertaker-registry": "1.0.1" + } + }, + "undertaker-registry": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/undertaker-registry/-/undertaker-registry-1.0.1.tgz", + "integrity": "sha1-XkvaMI5KiirlhPm5pDWaSZglzFA=", + "dev": true + }, + "union-value": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.0.tgz", + "integrity": "sha1-XHHDTLW61dzr4+oM0IIHulqhrqQ=", + "dev": true, + "requires": { + "arr-union": "3.1.0", + "get-value": "2.0.6", + "is-extendable": "0.1.1", + "set-value": "0.4.3" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "0.1.1" + } + }, + "set-value": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/set-value/-/set-value-0.4.3.tgz", + "integrity": "sha1-fbCPnT0i3H945Trzw79GZuzfzPE=", + "dev": true, + "requires": { + "extend-shallow": "2.0.1", + "is-extendable": "0.1.1", + "is-plain-object": "2.0.4", + "to-object-path": "0.3.0" + } + } + } + }, + "unique-stream": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/unique-stream/-/unique-stream-2.2.1.tgz", + "integrity": "sha1-WqADz76Uxf+GbE59ZouxxNuts2k=", + "dev": true, + "requires": { + "json-stable-stringify": "1.0.1", + "through2-filter": "2.0.0" + } + }, + "unset-value": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz", + "integrity": "sha1-g3aHP30jNRef+x5vw6jtDfyKtVk=", + "dev": true, + "requires": { + "has-value": "0.3.1", + "isobject": "3.0.1" + }, + "dependencies": { + "has-value": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz", + "integrity": "sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8=", + "dev": true, + "requires": { + "get-value": "2.0.6", + "has-values": "0.1.4", + "isobject": "2.1.0" + }, + "dependencies": { + "isobject": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", + "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", + "dev": true, + "requires": { + "isarray": "1.0.0" + } + } + } + }, + "has-values": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz", + "integrity": "sha1-bWHeldkd/Km5oCCJrThL/49it3E=", + "dev": true + }, + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true + } + } + }, + "upath": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/upath/-/upath-1.1.0.tgz", + "integrity": "sha512-bzpH/oBhoS/QI/YtbkqCg6VEiPYjSZtrHQM6/QnJS6OL9pKUFLqb3aFh4Scvwm45+7iAgiMkLhSbaZxUqmrprw==", + "dev": true + }, + "uri-js": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", + "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", + "dev": true, + "requires": { + "punycode": "2.1.1" + }, + "dependencies": { + "punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "dev": true + } + } + }, + "urix": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz", + "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=", + "dev": true + }, + "url": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/url/-/url-0.11.0.tgz", + "integrity": "sha1-ODjpfPxgUh63PFJajlW/3Z4uKPE=", + "dev": true, + "requires": { + "punycode": "1.3.2", + "querystring": "0.2.0" + }, + "dependencies": { + "punycode": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", + "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=", + "dev": true + } + } + }, + "use": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", + "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==", + "dev": true + }, + "util": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/util/-/util-0.10.3.tgz", + "integrity": "sha1-evsa/lCAUkZInj23/g7TeTNqwPk=", + "dev": true, + "requires": { + "inherits": "2.0.1" + }, + "dependencies": { + "inherits": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz", + "integrity": "sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE=", + "dev": true + } + } + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", + "dev": true + }, + "util.promisify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/util.promisify/-/util.promisify-1.0.0.tgz", + "integrity": "sha512-i+6qA2MPhvoKLuxnJNpXAGhg7HphQOSUq2LKMZD0m15EiskXUkMvKdF4Uui0WYeCUGea+o2cw/ZuwehtfsrNkA==", + "dev": true, + "requires": { + "define-properties": "1.1.3", + "object.getownpropertydescriptors": "2.0.3" + } + }, + "uuid": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", + "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==", + "dev": true + }, + "v8flags": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/v8flags/-/v8flags-3.1.1.tgz", + "integrity": "sha512-iw/1ViSEaff8NJ3HLyEjawk/8hjJib3E7pvG4pddVXfUg1983s3VGsiClDjhK64MQVDGqc1Q8r18S4VKQZS9EQ==", + "dev": true, + "requires": { + "homedir-polyfill": "1.0.1" + } + }, + "vali-date": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/vali-date/-/vali-date-1.0.0.tgz", + "integrity": "sha1-G5BKWWCfsyjvB4E4Qgk09rhnCaY=", + "dev": true + }, + "validate-npm-package-license": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.1.tgz", + "integrity": "sha1-KAS6vnEq0zeUWaz74kdGqywwP7w=", + "dev": true, + "requires": { + "spdx-correct": "1.0.2", + "spdx-expression-parse": "1.0.4" + } + }, + "value-or-function": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/value-or-function/-/value-or-function-3.0.0.tgz", + "integrity": "sha1-HCQ6ULWVwb5Up1S/7OhWO5/42BM=", + "dev": true + }, + "verror": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", + "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", + "dev": true, + "requires": { + "assert-plus": "1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "1.3.0" + } + }, + "vinyl": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-0.5.3.tgz", + "integrity": "sha1-sEVbOPxeDPMNQyUTLkYZcMIJHN4=", + "dev": true, + "requires": { + "clone": "1.0.3", + "clone-stats": "0.0.1", + "replace-ext": "0.0.1" + } + }, + "vinyl-fs": { + "version": "2.4.4", + "resolved": "https://registry.npmjs.org/vinyl-fs/-/vinyl-fs-2.4.4.tgz", + "integrity": "sha1-vm/zJwy1Xf19MGNkDegfJddTIjk=", + "dev": true, + "requires": { + "duplexify": "3.5.1", + "glob-stream": "5.3.5", + "graceful-fs": "4.1.11", + "gulp-sourcemaps": "1.6.0", + "is-valid-glob": "0.3.0", + "lazystream": "1.0.0", + "lodash.isequal": "4.5.0", + "merge-stream": "1.0.1", + "mkdirp": "0.5.1", + "object-assign": "4.1.1", + "readable-stream": "2.3.3", + "strip-bom": "2.0.0", + "strip-bom-stream": "1.0.0", + "through2": "2.0.3", + "through2-filter": "2.0.0", + "vali-date": "1.0.0", + "vinyl": "1.2.0" + }, + "dependencies": { + "gulp-sourcemaps": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/gulp-sourcemaps/-/gulp-sourcemaps-1.6.0.tgz", + "integrity": "sha1-uG/zSdgBzrVuHZ59x7vLS33uYAw=", + "dev": true, + "requires": { + "convert-source-map": "1.5.1", + "graceful-fs": "4.1.11", + "strip-bom": "2.0.0", + "through2": "2.0.3", + "vinyl": "1.2.0" + } + }, + "vinyl": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-1.2.0.tgz", + "integrity": "sha1-XIgDbPVl5d8FVYv8kR+GVt8hiIQ=", + "dev": true, + "requires": { + "clone": "1.0.3", + "clone-stats": "0.0.1", + "replace-ext": "0.0.1" + } + } + } + }, + "vinyl-sourcemap": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/vinyl-sourcemap/-/vinyl-sourcemap-1.1.0.tgz", + "integrity": "sha1-kqgAWTo4cDqM2xHYswCtS+Y7PhY=", + "dev": true, + "requires": { + "append-buffer": "1.0.2", + "convert-source-map": "1.5.1", + "graceful-fs": "4.1.11", + "normalize-path": "2.1.1", + "now-and-later": "2.0.0", + "remove-bom-buffer": "3.0.0", + "vinyl": "2.2.0" + }, + "dependencies": { + "clone": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", + "integrity": "sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18=", + "dev": true + }, + "clone-stats": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/clone-stats/-/clone-stats-1.0.0.tgz", + "integrity": "sha1-s3gt/4u1R04Yuba/D9/ngvh3doA=", + "dev": true + }, + "replace-ext": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-1.0.0.tgz", + "integrity": "sha1-3mMSg3P8v3w8z6TeWkgMRaZ5WOs=", + "dev": true + }, + "vinyl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-2.2.0.tgz", + "integrity": "sha512-MBH+yP0kC/GQ5GwBqrTPTzEfiiLjta7hTtvQtbxBgTeSXsmKQRQecjibMbxIXzVT3Y9KJK+drOz1/k+vsu8Nkg==", + "dev": true, + "requires": { + "clone": "2.1.2", + "clone-buffer": "1.0.0", + "clone-stats": "1.0.0", + "cloneable-readable": "1.0.0", + "remove-trailing-separator": "1.1.0", + "replace-ext": "1.0.0" + } + } + } + }, + "vinyl-sourcemaps-apply": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/vinyl-sourcemaps-apply/-/vinyl-sourcemaps-apply-0.2.1.tgz", + "integrity": "sha1-q2VJ1h0XLCsbh75cUI0jnI74dwU=", + "dev": true, + "requires": { + "source-map": "0.5.7" + } + }, + "vm-browserify": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-0.0.4.tgz", + "integrity": "sha1-XX6kW7755Kb/ZflUOOCofDV9WnM=", + "dev": true, + "requires": { + "indexof": "0.0.1" + } + }, + "w3c-hr-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.1.tgz", + "integrity": "sha1-gqwr/2PZUOqeMYmlimViX+3xkEU=", + "dev": true, + "requires": { + "browser-process-hrtime": "0.1.3" + } + }, + "walker": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.7.tgz", + "integrity": "sha1-L3+bj9ENZ3JisYqITijRlhjgKPs=", + "dev": true, + "requires": { + "makeerror": "1.0.11" + } + }, + "watch": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/watch/-/watch-0.18.0.tgz", + "integrity": "sha1-KAlUdsbffJDJYxOJkMClQj60uYY=", + "dev": true, + "requires": { + "exec-sh": "0.2.2", + "minimist": "1.2.0" + } + }, + "watchpack": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-1.6.0.tgz", + "integrity": "sha512-i6dHe3EyLjMmDlU1/bGQpEw25XSjkJULPuAVKCbNRefQVq48yXKUpwg538F7AZTf9kyr57zj++pQFltUa5H7yA==", + "dev": true, + "requires": { + "chokidar": "2.0.4", + "graceful-fs": "4.1.11", + "neo-async": "2.6.0" + }, + "dependencies": { + "anymatch": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", + "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", + "dev": true, + "requires": { + "micromatch": "3.1.10", + "normalize-path": "2.1.1" + } + }, + "array-unique": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", + "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", + "dev": true + }, + "braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "dev": true, + "requires": { + "arr-flatten": "1.1.0", + "array-unique": "0.3.2", + "extend-shallow": "2.0.1", + "fill-range": "4.0.0", + "isobject": "3.0.1", + "repeat-element": "1.1.2", + "snapdragon": "0.8.2", + "snapdragon-node": "2.1.1", + "split-string": "3.1.0", + "to-regex": "3.0.2" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "0.1.1" + } + } + } + }, + "chokidar": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.0.4.tgz", + "integrity": "sha512-z9n7yt9rOvIJrMhvDtDictKrkFHeihkNl6uWMmZlmL6tJtX9Cs+87oK+teBx+JIgzvbX3yZHT3eF8vpbDxHJXQ==", + "dev": true, + "requires": { + "anymatch": "2.0.0", + "async-each": "1.0.1", + "braces": "2.3.2", + "fsevents": "1.2.4", + "glob-parent": "3.1.0", + "inherits": "2.0.3", + "is-binary-path": "1.0.1", + "is-glob": "4.0.0", + "lodash.debounce": "4.0.8", + "normalize-path": "2.1.1", + "path-is-absolute": "1.0.1", + "readdirp": "2.1.0", + "upath": "1.1.0" + } + }, + "expand-brackets": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", + "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", + "dev": true, + "requires": { + "debug": "2.6.9", + "define-property": "0.2.5", + "extend-shallow": "2.0.1", + "posix-character-classes": "0.1.1", + "regex-not": "1.0.2", + "snapdragon": "0.8.2", + "to-regex": "3.0.2" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "0.1.6" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "0.1.1" + } + }, + "is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", + "dev": true, + "requires": { + "kind-of": "3.2.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "1.1.6" + } + } + } + }, + "is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", + "dev": true, + "requires": { + "kind-of": "3.2.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "1.1.6" + } + } + } + }, + "is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "0.1.6", + "is-data-descriptor": "0.1.4", + "kind-of": "5.1.0" + } + }, + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "dev": true + } + } + }, + "extglob": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", + "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", + "dev": true, + "requires": { + "array-unique": "0.3.2", + "define-property": "1.0.0", + "expand-brackets": "2.1.4", + "extend-shallow": "2.0.1", + "fragment-cache": "0.2.1", + "regex-not": "1.0.2", + "snapdragon": "0.8.2", + "to-regex": "3.0.2" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "requires": { + "is-descriptor": "1.0.2" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "0.1.1" + } + } + } + }, + "fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", + "dev": true, + "requires": { + "extend-shallow": "2.0.1", + "is-number": "3.0.0", + "repeat-string": "1.6.1", + "to-regex-range": "2.1.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "0.1.1" + } + } + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "requires": { + "kind-of": "6.0.2" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "requires": { + "kind-of": "6.0.2" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "1.0.0", + "is-data-descriptor": "1.0.0", + "kind-of": "6.0.2" + } + }, + "is-glob": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.0.tgz", + "integrity": "sha1-lSHHaEXMJhCoUgPd8ICpWML/q8A=", + "dev": true, + "requires": { + "is-extglob": "2.1.1" + } + }, + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "dev": true, + "requires": { + "kind-of": "3.2.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "1.1.6" + } + } + } + }, + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true + }, + "micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "dev": true, + "requires": { + "arr-diff": "4.0.0", + "array-unique": "0.3.2", + "braces": "2.3.2", + "define-property": "2.0.2", + "extend-shallow": "3.0.2", + "extglob": "2.0.4", + "fragment-cache": "0.2.1", + "kind-of": "6.0.2", + "nanomatch": "1.2.13", + "object.pick": "1.3.0", + "regex-not": "1.0.2", + "snapdragon": "0.8.2", + "to-regex": "3.0.2" + } + } + } + }, + "webidl-conversions": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz", + "integrity": "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==", + "dev": true + }, + "webpack": { + "version": "3.12.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-3.12.0.tgz", + "integrity": "sha512-Sw7MdIIOv/nkzPzee4o0EdvCuPmxT98+vVpIvwtcwcF1Q4SDSNp92vwcKc4REe7NItH9f1S4ra9FuQ7yuYZ8bQ==", + "dev": true, + "requires": { + "acorn": "5.2.1", + "acorn-dynamic-import": "2.0.2", + "ajv": "6.5.5", + "ajv-keywords": "3.2.0", + "async": "2.6.0", + "enhanced-resolve": "3.4.1", + "escope": "3.6.0", + "interpret": "1.1.0", + "json-loader": "0.5.7", + "json5": "0.5.1", + "loader-runner": "2.3.0", + "loader-utils": "1.1.0", + "memory-fs": "0.4.1", + "mkdirp": "0.5.1", + "node-libs-browser": "2.1.0", + "source-map": "0.5.7", + "supports-color": "4.5.0", + "tapable": "0.2.8", + "uglifyjs-webpack-plugin": "0.4.6", + "watchpack": "1.6.0", + "webpack-sources": "1.1.0", + "yargs": "8.0.2" + }, + "dependencies": { + "ajv": { + "version": "6.5.5", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.5.5.tgz", + "integrity": "sha512-7q7gtRQDJSyuEHjuVgHoUa2VuemFiCMrfQc9Tc08XTAc4Zj/5U1buQJ0HU6i7fKjXU09SVgSmxa4sLvuvS8Iyg==", + "dev": true, + "requires": { + "fast-deep-equal": "2.0.1", + "fast-json-stable-stringify": "2.0.0", + "json-schema-traverse": "0.4.1", + "uri-js": "4.2.2" + } + }, + "ajv-keywords": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.2.0.tgz", + "integrity": "sha1-6GuBnGAs+IIa1jdBNpjx3sAhhHo=", + "dev": true + }, + "fast-deep-equal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", + "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=", + "dev": true + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "loader-utils": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.1.0.tgz", + "integrity": "sha1-yYrvSIvM7aL/teLeZG1qdUQp9c0=", + "dev": true, + "requires": { + "big.js": "3.2.0", + "emojis-list": "2.1.0", + "json5": "0.5.1" + } + }, + "supports-color": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-4.5.0.tgz", + "integrity": "sha1-vnoN5ITexcXN34s9WRJQRJEvY1s=", + "dev": true, + "requires": { + "has-flag": "2.0.0" + } + } + } + }, + "webpack-sources": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-1.1.0.tgz", + "integrity": "sha512-aqYp18kPphgoO5c/+NaUvEeACtZjMESmDChuD3NBciVpah3XpMEU9VAAtIaB1BsfJWWTSdv8Vv1m3T0aRk2dUw==", + "dev": true, + "requires": { + "source-list-map": "2.0.0", + "source-map": "0.6.1" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } + } + }, + "webpack-stream": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/webpack-stream/-/webpack-stream-4.0.0.tgz", + "integrity": "sha1-82c92QfW2bHqe/UfzR24W1/Z4PI=", + "dev": true, + "requires": { + "gulp-util": "3.0.8", + "lodash.clone": "4.5.0", + "lodash.some": "4.6.0", + "memory-fs": "0.4.1", + "through": "2.3.8", + "vinyl": "2.1.0", + "webpack": "3.12.0" + }, + "dependencies": { + "clone": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.1.tgz", + "integrity": "sha1-0hfR6WERjjrJpLi7oyhVU79kfNs=", + "dev": true + }, + "clone-stats": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/clone-stats/-/clone-stats-1.0.0.tgz", + "integrity": "sha1-s3gt/4u1R04Yuba/D9/ngvh3doA=", + "dev": true + }, + "replace-ext": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-1.0.0.tgz", + "integrity": "sha1-3mMSg3P8v3w8z6TeWkgMRaZ5WOs=", + "dev": true + }, + "vinyl": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-2.1.0.tgz", + "integrity": "sha1-Ah+cLPlR1rk5lDyJ617lrdT9kkw=", + "dev": true, + "requires": { + "clone": "2.1.1", + "clone-buffer": "1.0.0", + "clone-stats": "1.0.0", + "cloneable-readable": "1.0.0", + "remove-trailing-separator": "1.1.0", + "replace-ext": "1.0.0" + } + } + } + }, + "whatwg-encoding": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz", + "integrity": "sha512-b5lim54JOPN9HtzvK9HFXvBma/rnfFeqsic0hSpjtDbVxR3dJKLc+KB4V6GgiGOvl7CY/KNh8rxSo9DKQrnUEw==", + "dev": true, + "requires": { + "iconv-lite": "0.4.24" + } + }, + "whatwg-mimetype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz", + "integrity": "sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g==", + "dev": true + }, + "whatwg-url": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-6.5.0.tgz", + "integrity": "sha512-rhRZRqx/TLJQWUpQ6bmrt2UV4f0HCQ463yQuONJqC6fO2VoEb1pTYddbe59SkYq87aoM5A3bdhMZiUiVws+fzQ==", + "dev": true, + "requires": { + "lodash.sortby": "4.7.0", + "tr46": "1.0.1", + "webidl-conversions": "4.0.2" + } + }, + "which": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.0.tgz", + "integrity": "sha512-xcJpopdamTuY5duC/KnTTNBraPK54YwpenP4lzxU8H91GudWpFv38u0CKjclE1Wi2EH2EDz5LRcHcKbCIzqGyg==", + "dev": true, + "requires": { + "isexe": "2.0.0" + } + }, + "which-module": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", + "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", + "dev": true + }, + "window-size": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/window-size/-/window-size-0.1.0.tgz", + "integrity": "sha1-VDjNLqk7IC76Ohn+iIeu58lPnJ0=", + "dev": true + }, + "wordwrap": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.2.tgz", + "integrity": "sha1-t5Zpu0LstAn4PVg8rVLKF+qhZD8=", + "dev": true + }, + "wrap-ansi": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", + "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", + "dev": true, + "requires": { + "string-width": "1.0.2", + "strip-ansi": "3.0.1" + }, + "dependencies": { + "string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "dev": true, + "requires": { + "code-point-at": "1.1.0", + "is-fullwidth-code-point": "1.0.0", + "strip-ansi": "3.0.1" + } + } + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + }, + "write-file-atomic": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-2.3.0.tgz", + "integrity": "sha512-xuPeK4OdjWqtfi59ylvVL0Yn35SF3zgcAcv7rBPFHVaEapaDr4GdGgm3j7ckTwH9wHL7fGmgfAnb0+THrHb8tA==", + "dev": true, + "requires": { + "graceful-fs": "4.1.11", + "imurmurhash": "0.1.4", + "signal-exit": "3.0.2" + } + }, + "ws": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/ws/-/ws-6.1.2.tgz", + "integrity": "sha512-rfUqzvz0WxmSXtJpPMX2EeASXabOrSMk1ruMOV3JBTBjo4ac2lDjGGsbQSyxj8Odhw5fBib8ZKEjDNvgouNKYw==", + "requires": { + "async-limiter": "1.0.0" + } + }, + "xml": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/xml/-/xml-1.0.1.tgz", + "integrity": "sha1-eLpyAgApxbyHuKgaPPzXS0ovweU=", + "dev": true + }, + "xml-name-validator": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-3.0.0.tgz", + "integrity": "sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw==", + "dev": true + }, + "xtend": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz", + "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=", + "dev": true + }, + "y18n": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.1.tgz", + "integrity": "sha1-bRX7qITAhnnA136I53WegR4H+kE=", + "dev": true + }, + "yallist": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", + "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=", + "dev": true + }, + "yargs": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-8.0.2.tgz", + "integrity": "sha1-YpmpBVsc78lp/355wdkY3Osiw2A=", + "dev": true, + "requires": { + "camelcase": "4.1.0", + "cliui": "3.2.0", + "decamelize": "1.2.0", + "get-caller-file": "1.0.2", + "os-locale": "2.1.0", + "read-pkg-up": "2.0.0", + "require-directory": "2.1.1", + "require-main-filename": "1.0.1", + "set-blocking": "2.0.0", + "string-width": "2.1.1", + "which-module": "2.0.0", + "y18n": "3.2.1", + "yargs-parser": "7.0.0" + }, + "dependencies": { + "camelcase": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz", + "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=", + "dev": true + }, + "cliui": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-3.2.0.tgz", + "integrity": "sha1-EgYBU3qRbSmUD5NNo7SNWFo5IT0=", + "dev": true, + "requires": { + "string-width": "1.0.2", + "strip-ansi": "3.0.1", + "wrap-ansi": "2.1.0" + }, + "dependencies": { + "string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "dev": true, + "requires": { + "code-point-at": "1.1.0", + "is-fullwidth-code-point": "1.0.0", + "strip-ansi": "3.0.1" + } + } + } + }, + "find-up": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", + "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", + "dev": true, + "requires": { + "locate-path": "2.0.0" + } + }, + "load-json-file": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-2.0.0.tgz", + "integrity": "sha1-eUfkIUmvgNaWy/eXvKq8/h/inKg=", + "dev": true, + "requires": { + "graceful-fs": "4.1.11", + "parse-json": "2.2.0", + "pify": "2.3.0", + "strip-bom": "3.0.0" + } + }, + "path-type": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-2.0.0.tgz", + "integrity": "sha1-8BLMuEFbcJb8LaoQVMPXI4lZTHM=", + "dev": true, + "requires": { + "pify": "2.3.0" + } + }, + "read-pkg": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-2.0.0.tgz", + "integrity": "sha1-jvHAYjxqbbDcZxPEv6xGMysjaPg=", + "dev": true, + "requires": { + "load-json-file": "2.0.0", + "normalize-package-data": "2.4.0", + "path-type": "2.0.0" + } + }, + "read-pkg-up": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-2.0.0.tgz", + "integrity": "sha1-a3KoBImE4MQeeVEP1en6mbO1Sb4=", + "dev": true, + "requires": { + "find-up": "2.1.0", + "read-pkg": "2.0.0" + } + }, + "strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", + "dev": true + } + } + }, + "yargs-parser": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-7.0.0.tgz", + "integrity": "sha1-jQrELxbqVd69MyyvTEA4s+P139k=", + "dev": true, + "requires": { + "camelcase": "4.1.0" + }, + "dependencies": { + "camelcase": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz", + "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=", + "dev": true + } + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 00000000..0a7d0d80 --- /dev/null +++ b/package.json @@ -0,0 +1,75 @@ +{ + "name": "microsoft-cognitiveservices-speech-sdk", + "author": "Microsoft Corporation", + "homepage": "https://docs.microsoft.com/azure/cognitive-services/speech-service/", + "version": "1.2.0-alpha.0.1", + "license": "MIT", + "description": "Microsoft Cognitive Services Speech SDK for JavaScript", + "keywords": [ + "microsoft", + "cognitiveservices", + "speech", + "sdk", + "javascript", + "typescript", + "ts", + "js", + "browser", + "websocket", + "speechtotext" + ], + "bugs": { + "url": "https://github.com/Microsoft/cognitive-services-speech-sdk-js/issues" + }, + "repository": { + "type": "git", + "url": "https://github.com/Microsoft/cognitive-services-speech-sdk-js" + }, + "main": "distrib/lib/src/sdk/Exports.js", + "types": "distrib/lib/src/sdk/Exports.d.ts", + "files": [ + "distrib/lib/**/*", + "LICENSE", + "REDIST.txt" + ], + "devDependencies": { + "@types/jest": "^23.3.1", + "@types/node": "^10.7.0", + "gulp": "^4.0.0", + "gulp-uglify": "^3.0.1", + "gulp-rename": "^1.4.0", + "gulp-sourcemaps": "^2.6.4", + "gulp-tslint": "^8.0.0", + "gulp-typescript": "^3.2.3", + "jest": "^23.5.0", + "jest-junit": "^5.1.0", + "semver": "^5.6.0", + "source-map-loader": "^0.2.3", + "ts-jest": "^23.1.3", + "tslint": "^5.11.0", + "typescript": "^3.2.1", + "webpack-stream": "^4.0.0" + }, + "scripts": { + "build": "gulp compress", + "test": "npm run lint && npm run jest", + "jest": "jest", + "lint": "tslint -p tsconfig.json", + "civersion": "node ci/version.js" + }, + "jest": { + "testEnvironment": "node" + }, + "jest-junit": { + "suiteName": "jest tests", + "output": "./test-javascript-junit.xml", + "classNameTemplate": "{classname}-{title}", + "titleTemplate": "{classname}-{title}", + "ancestorSeparator": " � ", + "usePathForSuiteName": "true" + }, + "dependencies": { + "@types/ws": "^6.0.1", + "ws": "^6.1.2" + } +} diff --git a/src/common.browser/ConsoleLoggingListener.ts b/src/common.browser/ConsoleLoggingListener.ts new file mode 100644 index 00000000..201495a6 --- /dev/null +++ b/src/common.browser/ConsoleLoggingListener.ts @@ -0,0 +1,70 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +import { EventType, IEventListener, PlatformEvent } from "../common/Exports"; + +export class ConsoleLoggingListener implements IEventListener { + private privLogLevelFilter: EventType; + + public constructor(logLevelFilter: EventType = EventType.Warning) { + this.privLogLevelFilter = logLevelFilter; + } + + public onEvent = (event: PlatformEvent): void => { + if (event.eventType >= this.privLogLevelFilter) { + const log = this.toString(event); + + switch (event.eventType) { + case EventType.Debug: + // tslint:disable-next-line:no-console + console.debug(log); + break; + case EventType.Info: + // tslint:disable-next-line:no-console + console.info(log); + break; + case EventType.Warning: + // tslint:disable-next-line:no-console + console.warn(log); + break; + case EventType.Error: + // tslint:disable-next-line:no-console + console.error(log); + break; + default: + // tslint:disable-next-line:no-console + console.log(log); + break; + } + } + } + + private toString = (event: any): string => { + const logFragments = [ + `${event.EventTime}`, + `${event.Name}`, + ]; + + for (const prop in event) { + if (prop && event.hasOwnProperty(prop) && + prop !== "eventTime" && prop !== "eventType" && + prop !== "eventId" && prop !== "name" && + prop !== "constructor") { + const value = event[prop]; + let valueToLog = ""; + if (value !== undefined && value !== null) { + if (typeof (value) === "number" || typeof (value) === "string") { + valueToLog = value.toString(); + } else { + valueToLog = JSON.stringify(value); + } + } + + logFragments.push(`${prop}: ${valueToLog}`); + } + + } + + return logFragments.join(" | "); + } +} diff --git a/src/common.browser/Exports.ts b/src/common.browser/Exports.ts new file mode 100644 index 00000000..c3532411 --- /dev/null +++ b/src/common.browser/Exports.ts @@ -0,0 +1,14 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +export * from "./ConsoleLoggingListener"; +export * from "./IRecorder"; +export * from "./LocalStorage"; +export * from "./MicAudioSource"; +export * from "./FileAudioSource"; +export * from "./OpusRecorder"; +export * from "./PCMRecorder"; +export * from "./SessionStorage"; +export * from "./WebsocketConnection"; +export * from "./WebsocketMessageAdapter"; +export * from "./ReplayableAudioNode"; diff --git a/src/common.browser/FileAudioSource.ts b/src/common.browser/FileAudioSource.ts new file mode 100644 index 00000000..017e5831 --- /dev/null +++ b/src/common.browser/FileAudioSource.ts @@ -0,0 +1,184 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +import { AudioStreamFormat, AudioStreamFormatImpl } from "../../src/sdk/Audio/AudioStreamFormat"; +import { + AudioSourceErrorEvent, + AudioSourceEvent, + AudioSourceInitializingEvent, + AudioSourceOffEvent, + AudioSourceReadyEvent, + AudioStreamNodeAttachedEvent, + AudioStreamNodeAttachingEvent, + AudioStreamNodeDetachedEvent, + AudioStreamNodeErrorEvent, + createNoDashGuid, + Events, + EventSource, + IAudioSource, + IAudioStreamNode, + IStringDictionary, + Promise, + PromiseHelper, + Stream, + StreamReader, +} from "../common/Exports"; + +export class FileAudioSource implements IAudioSource { + + // Recommended sample rate (bytes/second). + private static readonly SAMPLE_RATE: number = 16000 * 2; // 16 kHz * 16 bits + + // We should stream audio at no faster than 2x real-time (i.e., send five chunks + // per second, with the chunk size == sample rate in bytes per second * 2 / 5). + private static readonly CHUNK_SIZE: number = FileAudioSource.SAMPLE_RATE * 2 / 5; + + private static readonly UPLOAD_INTERVAL: number = 200; // milliseconds + + // 10 seconds of audio in bytes = + // sample rate (bytes/second) * 600 (seconds) + 44 (size of the wave header). + private static readonly MAX_SIZE: number = FileAudioSource.SAMPLE_RATE * 600 + 44; + + private static readonly FILEFORMAT: AudioStreamFormatImpl = AudioStreamFormat.getWaveFormatPCM(16000, 16, 1) as AudioStreamFormatImpl; + + private privStreams: IStringDictionary> = {}; + + private privId: string; + + private privEvents: EventSource; + + private privFile: File; + + public constructor(file: File, audioSourceId?: string) { + this.privId = audioSourceId ? audioSourceId : createNoDashGuid(); + this.privEvents = new EventSource(); + this.privFile = file; + } + + public get format(): AudioStreamFormat { + return FileAudioSource.FILEFORMAT; + } + + public turnOn = (): Promise => { + if (typeof FileReader === "undefined") { + const errorMsg = "Browser does not support FileReader."; + this.onEvent(new AudioSourceErrorEvent(errorMsg, "")); // initialization error - no streamid at this point + return PromiseHelper.fromError(errorMsg); + } else if (this.privFile.name.lastIndexOf(".wav") !== this.privFile.name.length - 4) { + const errorMsg = this.privFile.name + " is not supported. Only WAVE files are allowed at the moment."; + this.onEvent(new AudioSourceErrorEvent(errorMsg, "")); + return PromiseHelper.fromError(errorMsg); + } else if (this.privFile.size > FileAudioSource.MAX_SIZE) { + const errorMsg = this.privFile.name + " exceeds the maximum allowed file size (" + FileAudioSource.MAX_SIZE + ")."; + this.onEvent(new AudioSourceErrorEvent(errorMsg, "")); + return PromiseHelper.fromError(errorMsg); + } + + this.onEvent(new AudioSourceInitializingEvent(this.privId)); // no stream id + this.onEvent(new AudioSourceReadyEvent(this.privId)); + return PromiseHelper.fromResult(true); + } + + public id = (): string => { + return this.privId; + } + + public attach = (audioNodeId: string): Promise => { + this.onEvent(new AudioStreamNodeAttachingEvent(this.privId, audioNodeId)); + + return this.upload(audioNodeId).onSuccessContinueWith( + (streamReader: StreamReader) => { + this.onEvent(new AudioStreamNodeAttachedEvent(this.privId, audioNodeId)); + return { + detach: () => { + streamReader.close(); + delete this.privStreams[audioNodeId]; + this.onEvent(new AudioStreamNodeDetachedEvent(this.privId, audioNodeId)); + this.turnOff(); + }, + id: () => { + return audioNodeId; + }, + read: () => { + return streamReader.read(); + }, + }; + }); + } + + public detach = (audioNodeId: string): void => { + if (audioNodeId && this.privStreams[audioNodeId]) { + this.privStreams[audioNodeId].close(); + delete this.privStreams[audioNodeId]; + this.onEvent(new AudioStreamNodeDetachedEvent(this.privId, audioNodeId)); + } + } + + public turnOff = (): Promise => { + for (const streamId in this.privStreams) { + if (streamId) { + const stream = this.privStreams[streamId]; + if (stream && !stream.isClosed) { + stream.close(); + } + } + } + + this.onEvent(new AudioSourceOffEvent(this.privId)); // no stream now + return PromiseHelper.fromResult(true); + } + + public get events(): EventSource { + return this.privEvents; + } + + private upload = (audioNodeId: string): Promise> => { + return this.turnOn() + .onSuccessContinueWith>((_: boolean) => { + const stream = new Stream(audioNodeId); + + this.privStreams[audioNodeId] = stream; + + const reader: FileReader = new FileReader(); + + let startOffset = 0; + let endOffset = FileAudioSource.CHUNK_SIZE; + + const processNextChunk = (event: Event): void => { + if (stream.isClosed) { + return; // output stream was closed (somebody called TurnOff). We're done here. + } + + stream.write(reader.result as ArrayBuffer); + + if (endOffset < this.privFile.size) { + startOffset = endOffset; + endOffset = Math.min(endOffset + FileAudioSource.CHUNK_SIZE, this.privFile.size); + const chunk = this.privFile.slice(startOffset, endOffset); + reader.readAsArrayBuffer(chunk); + } else { + // we've written the entire file to the output stream, can close it now. + stream.close(); + } + }; + + reader.onload = processNextChunk; + + reader.onerror = (event: ProgressEvent) => { + const errorMsg = `Error occurred while processing '${this.privFile.name}'. ${event}`; + this.onEvent(new AudioStreamNodeErrorEvent(this.privId, audioNodeId, errorMsg)); + throw new Error(errorMsg); + }; + + const chunk = this.privFile.slice(startOffset, endOffset); + reader.readAsArrayBuffer(chunk); + + return stream.getReader(); + }); + } + + private onEvent = (event: AudioSourceEvent): void => { + this.privEvents.onEvent(event); + Events.instance.onEvent(event); + } +} diff --git a/src/common.browser/IRecorder.ts b/src/common.browser/IRecorder.ts new file mode 100644 index 00000000..9fddfece --- /dev/null +++ b/src/common.browser/IRecorder.ts @@ -0,0 +1,9 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +import { Stream } from "../common/Exports"; + +export interface IRecorder { + record(context: AudioContext, mediaStream: MediaStream, outputStream: Stream): void; + releaseMediaResources(context: AudioContext): void; +} diff --git a/src/common.browser/LocalStorage.ts b/src/common.browser/LocalStorage.ts new file mode 100644 index 00000000..48c42c8a --- /dev/null +++ b/src/common.browser/LocalStorage.ts @@ -0,0 +1,44 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +import { ArgumentNullError, IKeyValueStorage } from "../common/Exports"; + +export class LocalStorage implements IKeyValueStorage { + + public get = (key: string): string => { + if (!key) { + throw new ArgumentNullError("key"); + } + + return localStorage.getItem(key); + } + + public getOrAdd = (key: string, valueToAdd: string): string => { + if (!key) { + throw new ArgumentNullError("key"); + } + + const value = localStorage.getItem(key); + if (value === null || value === undefined) { + localStorage.setItem(key, valueToAdd); + } + + return localStorage.getItem(key); + } + + public set = (key: string, value: string): void => { + if (!key) { + throw new ArgumentNullError("key"); + } + + localStorage.setItem(key, value); + } + + public remove = (key: string): void => { + if (!key) { + throw new ArgumentNullError("key"); + } + + localStorage.removeItem(key); + } +} diff --git a/src/common.browser/MicAudioSource.ts b/src/common.browser/MicAudioSource.ts new file mode 100644 index 00000000..8e6b4a85 --- /dev/null +++ b/src/common.browser/MicAudioSource.ts @@ -0,0 +1,257 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +import { AudioStreamFormat, AudioStreamFormatImpl } from "../../src/sdk/Audio/AudioStreamFormat"; +import { + AudioSourceErrorEvent, + AudioSourceEvent, + AudioSourceInitializingEvent, + AudioSourceOffEvent, + AudioSourceReadyEvent, + AudioStreamNodeAttachedEvent, + AudioStreamNodeAttachingEvent, + AudioStreamNodeDetachedEvent, + AudioStreamNodeErrorEvent, + createNoDashGuid, + Deferred, + Events, + EventSource, + IAudioSource, + IAudioStreamNode, + IStringDictionary, + Promise, + PromiseHelper, + Stream, + StreamReader, +} from "../common/Exports"; +import { IRecorder } from "./IRecorder"; + +// Extending the default definition with browser specific definitions for backward compatibility +interface INavigatorUserMedia extends NavigatorUserMedia { + webkitGetUserMedia?: (constraints: MediaStreamConstraints, successCallback: NavigatorUserMediaSuccessCallback, errorCallback: NavigatorUserMediaErrorCallback) => void; + mozGetUserMedia?: (constraints: MediaStreamConstraints, successCallback: NavigatorUserMediaSuccessCallback, errorCallback: NavigatorUserMediaErrorCallback) => void; + msGetUserMedia?: (constraints: MediaStreamConstraints, successCallback: NavigatorUserMediaSuccessCallback, errorCallback: NavigatorUserMediaErrorCallback) => void; +} + +export class MicAudioSource implements IAudioSource { + + private static readonly AUDIOFORMAT: AudioStreamFormatImpl = AudioStreamFormat.getDefaultInputFormat() as AudioStreamFormatImpl; + + private privStreams: IStringDictionary> = {}; + + private privId: string; + + private privEvents: EventSource; + + private privInitializeDeferral: Deferred; + + private privRecorder: IRecorder; + + private privMediaStream: MediaStream; + + private privContext: AudioContext; + + public constructor(recorder: IRecorder, audioSourceId?: string) { + this.privId = audioSourceId ? audioSourceId : createNoDashGuid(); + this.privEvents = new EventSource(); + this.privRecorder = recorder; + } + + public get format(): AudioStreamFormat { + return MicAudioSource.AUDIOFORMAT; + } + + public turnOn = (): Promise => { + if (this.privInitializeDeferral) { + return this.privInitializeDeferral.promise(); + } + + this.privInitializeDeferral = new Deferred(); + + this.createAudioContext(); + + const nav = window.navigator as INavigatorUserMedia; + + let getUserMedia = ( + nav.getUserMedia || + nav.webkitGetUserMedia || + nav.mozGetUserMedia || + nav.msGetUserMedia + ); + + if (!!nav.mediaDevices) { + getUserMedia = (constraints: MediaStreamConstraints, successCallback: NavigatorUserMediaSuccessCallback, errorCallback: NavigatorUserMediaErrorCallback): void => { + nav.mediaDevices + .getUserMedia(constraints) + .then(successCallback) + .catch(errorCallback); + }; + } + + if (!getUserMedia) { + const errorMsg = "Browser does not support getUserMedia."; + this.privInitializeDeferral.reject(errorMsg); + this.onEvent(new AudioSourceErrorEvent(errorMsg, "")); // mic initialized error - no streamid at this point + } else { + const next = () => { + this.onEvent(new AudioSourceInitializingEvent(this.privId)); // no stream id + getUserMedia( + { audio: true, video: false }, + (mediaStream: MediaStream) => { + this.privMediaStream = mediaStream; + this.onEvent(new AudioSourceReadyEvent(this.privId)); + this.privInitializeDeferral.resolve(true); + }, (error: MediaStreamError) => { + const errorMsg = `Error occurred during microphone initialization: ${error}`; + const tmp = this.privInitializeDeferral; + // HACK: this should be handled through onError callbacks of all promises up the stack. + // Unfortunately, the current implementation does not provide an easy way to reject promises + // without a lot of code replication. + // TODO: fix promise implementation, allow for a graceful reject chaining. + this.privInitializeDeferral = null; + tmp.reject(errorMsg); // this will bubble up through the whole chain of promises, + // with each new level adding extra "Unhandled callback error" prefix to the error message. + // The following line is not guaranteed to be executed. + this.onEvent(new AudioSourceErrorEvent(this.privId, errorMsg)); + }); + }; + + if (this.privContext.state === "suspended") { + // NOTE: On iOS, the Web Audio API requires sounds to be triggered from an explicit user action. + // https://github.com/WebAudio/web-audio-api/issues/790 + this.privContext.resume().then(next, (reason: any) => { + this.privInitializeDeferral.reject(`Failed to initialize audio context: ${reason}`); + }); + } else { + next(); + } + } + + return this.privInitializeDeferral.promise(); + } + + public id = (): string => { + return this.privId; + } + + public attach = (audioNodeId: string): Promise => { + this.onEvent(new AudioStreamNodeAttachingEvent(this.privId, audioNodeId)); + + return this.listen(audioNodeId).onSuccessContinueWith( + (streamReader: StreamReader) => { + this.onEvent(new AudioStreamNodeAttachedEvent(this.privId, audioNodeId)); + return { + detach: () => { + streamReader.close(); + delete this.privStreams[audioNodeId]; + this.onEvent(new AudioStreamNodeDetachedEvent(this.privId, audioNodeId)); + this.turnOff(); + }, + id: () => { + return audioNodeId; + }, + read: () => { + return streamReader.read(); + }, + }; + }); + } + + public detach = (audioNodeId: string): void => { + if (audioNodeId && this.privStreams[audioNodeId]) { + this.privStreams[audioNodeId].close(); + delete this.privStreams[audioNodeId]; + this.onEvent(new AudioStreamNodeDetachedEvent(this.privId, audioNodeId)); + } + } + + public turnOff = (): Promise => { + for (const streamId in this.privStreams) { + if (streamId) { + const stream = this.privStreams[streamId]; + if (stream) { + stream.close(); + } + } + } + + this.onEvent(new AudioSourceOffEvent(this.privId)); // no stream now + this.privInitializeDeferral = null; + + this.destroyAudioContext(); + + return PromiseHelper.fromResult(true); + } + + public get events(): EventSource { + return this.privEvents; + } + + private listen = (audioNodeId: string): Promise> => { + return this.turnOn() + .onSuccessContinueWith>((_: boolean) => { + const stream = new Stream(audioNodeId); + this.privStreams[audioNodeId] = stream; + + try { + this.privRecorder.record(this.privContext, this.privMediaStream, stream); + } catch (error) { + this.onEvent(new AudioStreamNodeErrorEvent(this.privId, audioNodeId, error)); + throw error; + } + + return stream.getReader(); + }); + } + + private onEvent = (event: AudioSourceEvent): void => { + this.privEvents.onEvent(event); + Events.instance.onEvent(event); + } + + private createAudioContext = (): void => { + if (!!this.privContext) { + return; + } + + // https://developer.mozilla.org/en-US/docs/Web/API/AudioContext + const AudioContext = ((window as any).AudioContext) + || ((window as any).webkitAudioContext) + || false; + + if (!AudioContext) { + throw new Error("Browser does not support Web Audio API (AudioContext is not available)."); + } + + this.privContext = new AudioContext(); + } + + private destroyAudioContext = (): void => { + if (!this.privContext) { + return; + } + + this.privRecorder.releaseMediaResources(this.privContext); + + // This pattern brought to you by a bug in the TypeScript compiler where it + // confuses the ("close" in this.privContext) with this.privContext always being null as the alternate. + // https://github.com/Microsoft/TypeScript/issues/11498 + let hasClose: boolean = false; + if ("close" in this.privContext) { + hasClose = true; + } + + if (hasClose) { + this.privContext.close(); + this.privContext = null; + } else if (null !== this.privContext && this.privContext.state === "running") { + // Suspend actually takes a callback, but analogous to the + // resume method, it'll be only fired if suspend is called + // in a direct response to a user action. The later is not always + // the case, as TurnOff is also called, when we receive an + // end-of-speech message from the service. So, doing a best effort + // fire-and-forget here. + this.privContext.suspend(); + } + } +} diff --git a/src/common.browser/OpusRecorder.ts b/src/common.browser/OpusRecorder.ts new file mode 100644 index 00000000..4b49b54e --- /dev/null +++ b/src/common.browser/OpusRecorder.ts @@ -0,0 +1,59 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +import { Stream } from "../common/Exports"; +import { IRecorder } from "./IRecorder"; + +// getting around the build error for MediaRecorder as Typescript does not have a definition for this one. +declare var MediaRecorder: any; + +export class OpusRecorder implements IRecorder { + private privMediaResources: IMediaResources; + private privMediaRecorderOptions: any; + + constructor(options?: { mimeType: string, bitsPerSecond: number }) { + this.privMediaRecorderOptions = options; + } + + public record = (context: AudioContext, mediaStream: MediaStream, outputStream: Stream): void => { + const mediaRecorder: any = new MediaRecorder(mediaStream, this.privMediaRecorderOptions); + const timeslice = 100; // this is in ms - 100 ensures that the chunk doesn't exceed the max size of chunk allowed in WS connection + mediaRecorder.ondataavailable = (dataAvailableEvent: any) => { + if (outputStream) { + const reader = new FileReader(); + reader.readAsArrayBuffer(dataAvailableEvent.data); + reader.onloadend = (event: ProgressEvent) => { + outputStream.write(reader.result as ArrayBuffer); + }; + } + }; + + this.privMediaResources = { + recorder: mediaRecorder, + stream: mediaStream, + }; + mediaRecorder.start(timeslice); + } + + public releaseMediaResources = (context: AudioContext): void => { + if (this.privMediaResources.recorder.state !== "inactive") { + this.privMediaResources.recorder.stop(); + } + this.privMediaResources.stream.getTracks().forEach((track: any) => track.stop()); + } +} + +interface IMediaResources { + stream: MediaStream; + recorder: any; +} + +/* Declaring this inline to avoid compiler warnings +declare class MediaRecorder { + constructor(mediaStream: MediaStream, options: any); + + public state: string; + + public ondataavailable(dataAvailableEvent: any): void; + public stop(): void; +}*/ diff --git a/src/common.browser/PCMRecorder.ts b/src/common.browser/PCMRecorder.ts new file mode 100644 index 00000000..3e61f38b --- /dev/null +++ b/src/common.browser/PCMRecorder.ts @@ -0,0 +1,75 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +import { RiffPcmEncoder, Stream } from "../common/Exports"; +import { IRecorder } from "./IRecorder"; + +export class PcmRecorder implements IRecorder { + private privMediaResources: IMediaResources; + + public record = (context: AudioContext, mediaStream: MediaStream, outputStream: Stream): void => { + const desiredSampleRate = 16000; + + const scriptNode = (() => { + let bufferSize = 0; + try { + return context.createScriptProcessor(bufferSize, 1, 1); + } catch (error) { + // Webkit (<= version 31) requires a valid bufferSize. + bufferSize = 2048; + let audioSampleRate = context.sampleRate; + while (bufferSize < 16384 && audioSampleRate >= (2 * desiredSampleRate)) { + bufferSize <<= 1 ; + audioSampleRate >>= 1; + } + return context.createScriptProcessor(bufferSize, 1, 1); + } + })(); + + const waveStreamEncoder = new RiffPcmEncoder(context.sampleRate, desiredSampleRate); + let needHeader: boolean = true; + const that = this; + scriptNode.onaudioprocess = (event: AudioProcessingEvent) => { + const inputFrame = event.inputBuffer.getChannelData(0); + + if (outputStream && !outputStream.isClosed) { + const waveFrame = waveStreamEncoder.encode(needHeader, inputFrame); + if (!!waveFrame) { + outputStream.write(waveFrame); + needHeader = false; + } + } + }; + + const micInput = context.createMediaStreamSource(mediaStream); + + this.privMediaResources = { + scriptProcessorNode: scriptNode, + source: micInput, + stream: mediaStream, + }; + + micInput.connect(scriptNode); + scriptNode.connect(context.destination); + } + + public releaseMediaResources = (context: AudioContext): void => { + if (this.privMediaResources) { + if (this.privMediaResources.scriptProcessorNode) { + this.privMediaResources.scriptProcessorNode.disconnect(context.destination); + this.privMediaResources.scriptProcessorNode = null; + } + if (this.privMediaResources.source) { + this.privMediaResources.source.disconnect(); + this.privMediaResources.stream.getTracks().forEach((track: any) => track.stop()); + this.privMediaResources.source = null; + } + } + } +} + +interface IMediaResources { + source: MediaStreamAudioSourceNode; + scriptProcessorNode: ScriptProcessorNode; + stream: MediaStream; +} diff --git a/src/common.browser/ReplayableAudioNode.ts b/src/common.browser/ReplayableAudioNode.ts new file mode 100644 index 00000000..87df6110 --- /dev/null +++ b/src/common.browser/ReplayableAudioNode.ts @@ -0,0 +1,131 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +import { AudioStreamFormatImpl } from "../../src/sdk/Audio/AudioStreamFormat"; +import { + IAudioStreamNode, + IStreamChunk, + Promise, + PromiseHelper, +} from "../common/Exports"; + +export class ReplayableAudioNode implements IAudioStreamNode { + private privAudioNode: IAudioStreamNode; + private privFormat: AudioStreamFormatImpl; + private privBuffers: BufferEntry[] = []; + private privReplayOffset: number = 0; + private privLastShrinkOffset: number = 0; + private privBufferStartOffset: number = 0; + private privBufferSerial: number = 0; + private privBufferedBytes: number = 0; + private privReplay: boolean = false; + + public constructor(audioSource: IAudioStreamNode, format: AudioStreamFormatImpl) { + this.privAudioNode = audioSource; + this.privFormat = format; + } + + public id = (): string => { + return this.privAudioNode.id(); + } + + // Reads and returns the next chunk of audio buffer. + // If replay of existing buffers are needed, read() will first seek and replay + // existing content, and upoin completion it will read new content from the underlying + // audio node, saving that content into the replayable buffers. + public read(): Promise> { + // if there is a replay request to honor. + if (!!this.privReplay && this.privBuffers.length !== 0) { + // Find the start point in the buffers. + // Offsets are in 100ns increments. + // So how many bytes do we need to seek to get the right offset? + const offsetToSeek: number = this.privReplayOffset - this.privBufferStartOffset; + + let bytesToSeek: number = Math.round(offsetToSeek * this.privFormat.avgBytesPerSec * 1e-7); + if (0 !== (bytesToSeek % 2)) { + bytesToSeek++; + } + + let i: number = 0; + + while (i < this.privBuffers.length && bytesToSeek >= this.privBuffers[i].buffer.byteLength) { + bytesToSeek -= this.privBuffers[i++].buffer.byteLength; + } + + const retVal: ArrayBuffer = this.privBuffers[i].buffer.slice(bytesToSeek); + + this.privReplayOffset += (retVal.byteLength / this.privFormat.avgBytesPerSec) * 1e+7; + + // If we've reached the end of the buffers, stop replaying. + if (i === this.privBuffers.length - 1) { + this.privReplay = false; + } + + return PromiseHelper.fromResult>({ + buffer: retVal, + isEnd: false, + }); + } + + return this.privAudioNode.read() + .onSuccessContinueWith((result: IStreamChunk) => { + if (result.buffer) { + + this.privBuffers.push(new BufferEntry(result.buffer, this.privBufferSerial++, this.privBufferedBytes)); + this.privBufferedBytes += result.buffer.byteLength; + } + return result; + }); + } + + public detach(): void { + this.privAudioNode.detach(); + this.privBuffers = undefined; + } + + public replay(): void { + if (0 !== this.privBuffers.length) { + this.privReplay = true; + this.privReplayOffset = this.privLastShrinkOffset; + } + } + + // Shrinks the existing audio buffers to start at the new offset, or at the + // beginnign of the buffer closest to the requested offset. + // A replay request will start from the last shrink point. + public shrinkBuffers(offset: number): void { + this.privLastShrinkOffset = offset; + + // Find the start point in the buffers. + // Offsets are in 100ns increments. + // So how many bytes do we need to seek to get the right offset? + const offsetToSeek: number = offset - this.privBufferStartOffset; + + let bytesToSeek: number = Math.round(offsetToSeek * this.privFormat.avgBytesPerSec * 1e-7); + + let i: number = 0; + + while (i < this.privBuffers.length && bytesToSeek >= this.privBuffers[i].buffer.byteLength) { + bytesToSeek -= this.privBuffers[i++].buffer.byteLength; + } + this.privBufferStartOffset = Math.round(offset - ((bytesToSeek / this.privFormat.avgBytesPerSec) * 1e+7)); + + this.privBuffers = this.privBuffers.slice(i); + } +} + +// Primary use of this class is to help debugging problems with the replay +// code. If the memory cost of alloc / dealloc gets too much, drop it and just use +// the ArrayBuffer directly. +// tslint:disable-next-line:max-classes-per-file +class BufferEntry { + public buffer: ArrayBuffer; + public serial: number; + public byteOffset: number; + + public constructor(buffer: ArrayBuffer, serial: number, byteOffset: number) { + this.buffer = buffer; + this.serial = serial; + this.byteOffset = byteOffset; + } +} diff --git a/src/common.browser/SessionStorage.ts b/src/common.browser/SessionStorage.ts new file mode 100644 index 00000000..22e5df51 --- /dev/null +++ b/src/common.browser/SessionStorage.ts @@ -0,0 +1,44 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +import { ArgumentNullError, IKeyValueStorage } from "../common/Exports"; + +export class SessionStorage implements IKeyValueStorage { + + public get = (key: string): string => { + if (!key) { + throw new ArgumentNullError("key"); + } + + return sessionStorage.getItem(key); + } + + public getOrAdd = (key: string, valueToAdd: string): string => { + if (!key) { + throw new ArgumentNullError("key"); + } + + const value = sessionStorage.getItem(key); + if (value === null || value === undefined) { + sessionStorage.setItem(key, valueToAdd); + } + + return sessionStorage.getItem(key); + } + + public set = (key: string, value: string): void => { + if (!key) { + throw new ArgumentNullError("key"); + } + + sessionStorage.setItem(key, value); + } + + public remove = (key: string): void => { + if (!key) { + throw new ArgumentNullError("key"); + } + + sessionStorage.removeItem(key); + } +} diff --git a/src/common.browser/WebsocketConnection.ts b/src/common.browser/WebsocketConnection.ts new file mode 100644 index 00000000..d5df3725 --- /dev/null +++ b/src/common.browser/WebsocketConnection.ts @@ -0,0 +1,113 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +import { + ArgumentNullError, + ConnectionEvent, + ConnectionMessage, + ConnectionOpenResponse, + ConnectionState, + createNoDashGuid, + EventSource, + IConnection, + IStringDictionary, + IWebsocketMessageFormatter, + Promise, +} from "../common/Exports"; +import { WebsocketMessageAdapter } from "./WebsocketMessageAdapter"; + +export class WebsocketConnection implements IConnection { + + private privUri: string; + private privMessageFormatter: IWebsocketMessageFormatter; + private privConnectionMessageAdapter: WebsocketMessageAdapter; + private privId: string; + private privIsDisposed: boolean = false; + + public constructor( + uri: string, + queryParameters: IStringDictionary, + headers: IStringDictionary, + messageFormatter: IWebsocketMessageFormatter, + connectionId?: string) { + + if (!uri) { + throw new ArgumentNullError("uri"); + } + + if (!messageFormatter) { + throw new ArgumentNullError("messageFormatter"); + } + + this.privMessageFormatter = messageFormatter; + + let queryParams = ""; + let i = 0; + + if (queryParameters) { + for (const paramName in queryParameters) { + if (paramName) { + queryParams += ((i === 0) && (uri.indexOf("?") === -1)) ? "?" : "&"; + const val = encodeURIComponent(queryParameters[paramName]); + queryParams += `${paramName}=${val}`; + i++; + } + } + } + + if (headers) { + for (const headerName in headers) { + if (headerName) { + queryParams += i === 0 ? "?" : "&"; + const val = encodeURIComponent(headers[headerName]); + queryParams += `${headerName}=${val}`; + i++; + } + } + } + + this.privUri = uri + queryParams; + this.privId = connectionId ? connectionId : createNoDashGuid(); + + this.privConnectionMessageAdapter = new WebsocketMessageAdapter( + this.privUri, + this.id, + this.privMessageFormatter); + } + + public dispose = (): void => { + this.privIsDisposed = true; + + if (this.privConnectionMessageAdapter) { + this.privConnectionMessageAdapter.close(); + } + } + + public isDisposed = (): boolean => { + return this.privIsDisposed; + } + + public get id(): string { + return this.privId; + } + + public state = (): ConnectionState => { + return this.privConnectionMessageAdapter.state; + } + + public open = (): Promise => { + return this.privConnectionMessageAdapter.open(); + } + + public send = (message: ConnectionMessage): Promise => { + return this.privConnectionMessageAdapter.send(message); + } + + public read = (): Promise => { + return this.privConnectionMessageAdapter.read(); + } + + public get events(): EventSource { + return this.privConnectionMessageAdapter.events; + } +} diff --git a/src/common.browser/WebsocketMessageAdapter.ts b/src/common.browser/WebsocketMessageAdapter.ts new file mode 100644 index 00000000..973f491d --- /dev/null +++ b/src/common.browser/WebsocketMessageAdapter.ts @@ -0,0 +1,271 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. +import { + ArgumentNullError, + ConnectionClosedEvent, + ConnectionEstablishedEvent, + ConnectionEvent, + ConnectionMessage, + ConnectionMessageReceivedEvent, + ConnectionMessageSentEvent, + ConnectionOpenResponse, + ConnectionStartEvent, + ConnectionState, + Deferred, + Events, + EventSource, + IWebsocketMessageFormatter, + MessageType, + Promise, + PromiseHelper, + Queue, + RawWebsocketMessage, +} from "../common/Exports"; + +import ws = require("ws"); + +interface ISendItem { + Message: ConnectionMessage; + RawWebsocketMessage: RawWebsocketMessage; + sendStatusDeferral: Deferred; +} + +export class WebsocketMessageAdapter { + private privConnectionState: ConnectionState; + private privMessageFormatter: IWebsocketMessageFormatter; + private privWebsocketClient: WebSocket | ws; + + private privSendMessageQueue: Queue; + private privReceivingMessageQueue: Queue; + private privConnectionEstablishDeferral: Deferred; + private privDisconnectDeferral: Deferred; + private privConnectionEvents: EventSource; + private privConnectionId: string; + private privUri: string; + + public static forceNpmWebSocket: boolean = false; + + public constructor( + uri: string, + connectionId: string, + messageFormatter: IWebsocketMessageFormatter) { + + if (!uri) { + throw new ArgumentNullError("uri"); + } + + if (!messageFormatter) { + throw new ArgumentNullError("messageFormatter"); + } + + this.privConnectionEvents = new EventSource(); + this.privConnectionId = connectionId; + this.privMessageFormatter = messageFormatter; + this.privConnectionState = ConnectionState.None; + this.privUri = uri; + } + + public get state(): ConnectionState { + return this.privConnectionState; + } + + public open = (): Promise => { + if (this.privConnectionState === ConnectionState.Disconnected) { + return PromiseHelper.fromError(`Cannot open a connection that is in ${this.privConnectionState} state`); + } + + if (this.privConnectionEstablishDeferral) { + return this.privConnectionEstablishDeferral.promise(); + } + + this.privConnectionEstablishDeferral = new Deferred(); + this.privConnectionState = ConnectionState.Connecting; + + try { + if (typeof WebSocket !== "undefined" && !WebsocketMessageAdapter.forceNpmWebSocket) { + this.privWebsocketClient = new WebSocket(this.privUri); + } else { + this.privWebsocketClient = new ws(this.privUri); + } + + this.privWebsocketClient.binaryType = "arraybuffer"; + this.privReceivingMessageQueue = new Queue(); + this.privDisconnectDeferral = new Deferred(); + this.privSendMessageQueue = new Queue(); + this.processSendQueue(); + } catch (error) { + this.privConnectionEstablishDeferral.resolve(new ConnectionOpenResponse(500, error)); + return this.privConnectionEstablishDeferral.promise(); + } + + this.onEvent(new ConnectionStartEvent(this.privConnectionId, this.privUri)); + + this.privWebsocketClient.onopen = (e: { target: WebSocket | ws }) => { + this.privConnectionState = ConnectionState.Connected; + this.onEvent(new ConnectionEstablishedEvent(this.privConnectionId)); + this.privConnectionEstablishDeferral.resolve(new ConnectionOpenResponse(200, "")); + }; + + this.privWebsocketClient.onerror = (e: { error: any; message: string; type: string; target: WebSocket | ws }) => { + // TODO: Understand what this is error is. Will we still get onClose ? + if (this.privConnectionState !== ConnectionState.Connecting) { + // TODO: Is this required ? + // this.onEvent(new ConnectionErrorEvent(errorMsg, connectionId)); + } + }; + + this.privWebsocketClient.onclose = (e: { wasClean: boolean; code: number; reason: string; target: WebSocket | ws }) => { + if (this.privConnectionState === ConnectionState.Connecting) { + this.privConnectionState = ConnectionState.Disconnected; + // this.onEvent(new ConnectionEstablishErrorEvent(this.connectionId, e.code, e.reason)); + this.privConnectionEstablishDeferral.resolve(new ConnectionOpenResponse(e.code, e.reason)); + } else { + this.onEvent(new ConnectionClosedEvent(this.privConnectionId, e.code, e.reason)); + } + + this.onClose(e.code, e.reason); + }; + + this.privWebsocketClient.onmessage = (e: { data: ws.Data; type: string; target: WebSocket | ws }) => { + const networkReceivedTime = new Date().toISOString(); + if (this.privConnectionState === ConnectionState.Connected) { + const deferred = new Deferred(); + // let id = ++this.idCounter; + this.privReceivingMessageQueue.enqueueFromPromise(deferred.promise()); + if (e.data instanceof ArrayBuffer) { + const rawMessage = new RawWebsocketMessage(MessageType.Binary, e.data); + this.privMessageFormatter + .toConnectionMessage(rawMessage) + .on((connectionMessage: ConnectionMessage) => { + this.onEvent(new ConnectionMessageReceivedEvent(this.privConnectionId, networkReceivedTime, connectionMessage)); + deferred.resolve(connectionMessage); + }, (error: string) => { + // TODO: Events for these ? + deferred.reject(`Invalid binary message format. Error: ${error}`); + }); + } else { + const rawMessage = new RawWebsocketMessage(MessageType.Text, e.data); + this.privMessageFormatter + .toConnectionMessage(rawMessage) + .on((connectionMessage: ConnectionMessage) => { + this.onEvent(new ConnectionMessageReceivedEvent(this.privConnectionId, networkReceivedTime, connectionMessage)); + deferred.resolve(connectionMessage); + }, (error: string) => { + // TODO: Events for these ? + deferred.reject(`Invalid text message format. Error: ${error}`); + }); + } + } + }; + + return this.privConnectionEstablishDeferral.promise(); + } + + public send = (message: ConnectionMessage): Promise => { + if (this.privConnectionState !== ConnectionState.Connected) { + return PromiseHelper.fromError(`Cannot send on connection that is in ${this.privConnectionState} state`); + } + + const messageSendStatusDeferral = new Deferred(); + const messageSendDeferral = new Deferred(); + + this.privSendMessageQueue.enqueueFromPromise(messageSendDeferral.promise()); + + this.privMessageFormatter + .fromConnectionMessage(message) + .on((rawMessage: RawWebsocketMessage) => { + messageSendDeferral.resolve({ + Message: message, + RawWebsocketMessage: rawMessage, + sendStatusDeferral: messageSendStatusDeferral, + }); + }, (error: string) => { + messageSendDeferral.reject(`Error formatting the message. ${error}`); + }); + + return messageSendStatusDeferral.promise(); + } + + public read = (): Promise => { + if (this.privConnectionState !== ConnectionState.Connected) { + return PromiseHelper.fromError(`Cannot read on connection that is in ${this.privConnectionState} state`); + } + + return this.privReceivingMessageQueue.dequeue(); + } + + public close = (reason?: string): Promise => { + if (this.privWebsocketClient) { + if (this.privConnectionState !== ConnectionState.Disconnected) { + this.privWebsocketClient.close(1000, reason ? reason : "Normal closure by client"); + } + } else { + const deferral = new Deferred(); + deferral.resolve(true); + return deferral.promise(); + } + + return this.privDisconnectDeferral.promise(); + } + + public get events(): EventSource { + return this.privConnectionEvents; + } + + private sendRawMessage = (sendItem: ISendItem): Promise => { + try { + // indicates we are draining the queue and it came with no message; + if (!sendItem) { + return PromiseHelper.fromResult(true); + } + + this.onEvent(new ConnectionMessageSentEvent(this.privConnectionId, new Date().toISOString(), sendItem.Message)); + this.privWebsocketClient.send(sendItem.RawWebsocketMessage.payload); + return PromiseHelper.fromResult(true); + } catch (e) { + return PromiseHelper.fromError(`websocket send error: ${e}`); + } + } + + private onClose = (code: number, reason: string): void => { + const closeReason = `Connection closed. ${code}: ${reason}`; + this.privConnectionState = ConnectionState.Disconnected; + this.privDisconnectDeferral.resolve(true); + this.privReceivingMessageQueue.dispose(reason); + this.privReceivingMessageQueue.drainAndDispose((pendingReceiveItem: ConnectionMessage) => { + // TODO: Events for these ? + // Logger.instance.onEvent(new LoggingEvent(LogType.Warning, null, `Failed to process received message. Reason: ${closeReason}, Message: ${JSON.stringify(pendingReceiveItem)}`)); + }, closeReason); + + this.privSendMessageQueue.drainAndDispose((pendingSendItem: ISendItem) => { + pendingSendItem.sendStatusDeferral.reject(closeReason); + }, closeReason); + } + + private processSendQueue = (): void => { + this.privSendMessageQueue + .dequeue() + .on((sendItem: ISendItem) => { + // indicates we are draining the queue and it came with no message; + if (!sendItem) { + return; + } + + this.sendRawMessage(sendItem) + .on((result: boolean) => { + sendItem.sendStatusDeferral.resolve(result); + this.processSendQueue(); + }, (sendError: string) => { + sendItem.sendStatusDeferral.reject(sendError); + this.processSendQueue(); + }); + }, (error: string) => { + // do nothing + }); + } + + private onEvent = (event: ConnectionEvent): void => { + this.privConnectionEvents.onEvent(event); + Events.instance.onEvent(event); + } +} diff --git a/src/common.speech/AddedLmIntent.ts b/src/common.speech/AddedLmIntent.ts new file mode 100644 index 00000000..9cfa9dd4 --- /dev/null +++ b/src/common.speech/AddedLmIntent.ts @@ -0,0 +1,24 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +import { LanguageUnderstandingModelImpl } from "../sdk/LanguageUnderstandingModel"; + +/** + * @class AddedLmIntent + */ +// tslint:disable-next-line:max-classes-per-file +export class AddedLmIntent { + public modelImpl: LanguageUnderstandingModelImpl; + public intentName: string; + + /** + * Creates and initializes an instance of this class. + * @constructor + * @param modelImpl - The model. + * @param intentName - The intent name. + */ + public constructor(modelImpl: LanguageUnderstandingModelImpl, intentName: string) { + this.modelImpl = modelImpl; + this.intentName = intentName; + } +} diff --git a/src/common.speech/CognitiveSubscriptionKeyAuthentication.ts b/src/common.speech/CognitiveSubscriptionKeyAuthentication.ts new file mode 100644 index 00000000..ed107842 --- /dev/null +++ b/src/common.speech/CognitiveSubscriptionKeyAuthentication.ts @@ -0,0 +1,49 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +import { ArgumentNullError, Promise, PromiseHelper } from "../common/Exports"; +import { AuthInfo, IAuthentication } from "./IAuthentication"; + +const AuthHeader: string = "Ocp-Apim-Subscription-Key"; + +/** + * @class + */ +export class CognitiveSubscriptionKeyAuthentication implements IAuthentication { + private privAuthInfo: AuthInfo; + + /** + * Creates and initializes an instance of the CognitiveSubscriptionKeyAuthentication class. + * @constructor + * @param {string} subscriptionKey - The subscription key + */ + constructor(subscriptionKey: string) { + if (!subscriptionKey) { + throw new ArgumentNullError("subscriptionKey"); + } + + this.privAuthInfo = new AuthInfo(AuthHeader, subscriptionKey); + } + + /** + * Fetches the subscription key. + * @member + * @function + * @public + * @param {string} authFetchEventId - The id to fetch. + */ + public fetch = (authFetchEventId: string): Promise => { + return PromiseHelper.fromResult(this.privAuthInfo); + } + + /** + * Fetches the subscription key. + * @member + * @function + * @public + * @param {string} authFetchEventId - The id to fetch. + */ + public fetchOnExpiry = (authFetchEventId: string): Promise => { + return PromiseHelper.fromResult(this.privAuthInfo); + } +} diff --git a/src/common.speech/CognitiveTokenAuthentication.ts b/src/common.speech/CognitiveTokenAuthentication.ts new file mode 100644 index 00000000..e70893e5 --- /dev/null +++ b/src/common.speech/CognitiveTokenAuthentication.ts @@ -0,0 +1,33 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +import { ArgumentNullError, Promise } from "../common/Exports"; +import { AuthInfo, IAuthentication } from "./IAuthentication"; + +const AuthHeader: string = "Authorization"; + +export class CognitiveTokenAuthentication implements IAuthentication { + private privFetchCallback: (authFetchEventId: string) => Promise; + private privFetchOnExpiryCallback: (authFetchEventId: string) => Promise; + + constructor(fetchCallback: (authFetchEventId: string) => Promise, fetchOnExpiryCallback: (authFetchEventId: string) => Promise) { + if (!fetchCallback) { + throw new ArgumentNullError("fetchCallback"); + } + + if (!fetchOnExpiryCallback) { + throw new ArgumentNullError("fetchOnExpiryCallback"); + } + + this.privFetchCallback = fetchCallback; + this.privFetchOnExpiryCallback = fetchOnExpiryCallback; + } + + public fetch = (authFetchEventId: string): Promise => { + return this.privFetchCallback(authFetchEventId).onSuccessContinueWith((token: string) => new AuthInfo(AuthHeader, token)); + } + + public fetchOnExpiry = (authFetchEventId: string): Promise => { + return this.privFetchOnExpiryCallback(authFetchEventId).onSuccessContinueWith((token: string) => new AuthInfo(AuthHeader, token)); + } +} diff --git a/src/common.speech/EnumTranslation.ts b/src/common.speech/EnumTranslation.ts new file mode 100644 index 00000000..34b6b2c1 --- /dev/null +++ b/src/common.speech/EnumTranslation.ts @@ -0,0 +1,46 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +import { CancellationReason, ResultReason } from "../sdk/Exports"; +import { RecognitionStatus } from "./Exports"; + +export class EnumTranslation { + public static implTranslateRecognitionResult(recognitionStatus: RecognitionStatus): ResultReason { + let reason = ResultReason.Canceled; + switch (recognitionStatus) { + case RecognitionStatus.Success: + reason = ResultReason.RecognizedSpeech; + break; + case RecognitionStatus.NoMatch: + case RecognitionStatus.InitialSilenceTimeout: + case RecognitionStatus.BabbleTimeout: + case RecognitionStatus.EndOfDictation: + reason = ResultReason.NoMatch; + break; + case RecognitionStatus.Error: + default: + reason = ResultReason.Canceled; + break; + } + + return reason; + } + + public static implTranslateCancelResult(recognitionStatus: RecognitionStatus): CancellationReason { + let reason = CancellationReason.EndOfStream; + switch (recognitionStatus) { + case RecognitionStatus.Success: + case RecognitionStatus.EndOfDictation: + case RecognitionStatus.NoMatch: + reason = CancellationReason.EndOfStream; + break; + case RecognitionStatus.InitialSilenceTimeout: + case RecognitionStatus.BabbleTimeout: + case RecognitionStatus.Error: + default: + reason = CancellationReason.Error; + break; + } + return reason; + } +} diff --git a/src/common.speech/Exports.ts b/src/common.speech/Exports.ts new file mode 100644 index 00000000..ef83047f --- /dev/null +++ b/src/common.speech/Exports.ts @@ -0,0 +1,35 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +// Make sure not to export internal modules. +// +export * from "./CognitiveSubscriptionKeyAuthentication"; +export * from "./CognitiveTokenAuthentication"; +export * from "./IAuthentication"; +export * from "./IConnectionFactory"; +export * from "./IntentConnectionFactory"; +export * from "./RecognitionEvents"; +export * from "./ServiceRecognizerBase"; +export * from "./RecognizerConfig"; +export * from "./SpeechServiceInterfaces"; +export * from "./WebsocketMessageFormatter"; +export * from "./SpeechConnectionFactory"; +export * from "./TranslationConnectionFactory"; +export * from "./EnumTranslation"; +export * from "./ServiceMessages/Enums"; +export * from "./ServiceMessages/TranslationSynthesisEnd"; +export * from "./ServiceMessages/TranslationHypothesis"; +export * from "./ServiceMessages/TranslationPhrase"; +export * from "./TranslationServiceRecognizer"; +export * from "./ServiceMessages/SpeechDetected"; +export * from "./ServiceMessages/SpeechHypothesis"; +export * from "./SpeechServiceRecognizer"; +export * from "./ServiceMessages/DetailedSpeechPhrase"; +export * from "./ServiceMessages/SimpleSpeechPhrase"; +export * from "./AddedLmIntent"; +export * from "./IntentServiceRecognizer"; +export * from "./ServiceMessages/IntentResponse"; +export * from "./RequestSession"; + +export const OutputFormatPropertyName: string = "OutputFormat"; +export const CancellationErrorCodePropertyName: string = "CancellationErrorCode"; diff --git a/src/common.speech/IAuthentication.ts b/src/common.speech/IAuthentication.ts new file mode 100644 index 00000000..fee14578 --- /dev/null +++ b/src/common.speech/IAuthentication.ts @@ -0,0 +1,27 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +import { Promise } from "../common/Exports"; + +export interface IAuthentication { + fetch(authFetchEventId: string): Promise; + fetchOnExpiry(authFetchEventId: string): Promise; +} + +export class AuthInfo { + private privHeaderName: string; + private privToken: string; + + public constructor(headerName: string, token: string) { + this.privHeaderName = headerName; + this.privToken = token; + } + + public get headerName(): string { + return this.privHeaderName; + } + + public get token(): string { + return this.privToken; + } +} diff --git a/src/common.speech/IConnectionFactory.ts b/src/common.speech/IConnectionFactory.ts new file mode 100644 index 00000000..479cd785 --- /dev/null +++ b/src/common.speech/IConnectionFactory.ts @@ -0,0 +1,13 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +import { IConnection } from "../common/Exports"; +import { AuthInfo } from "./IAuthentication"; +import { RecognizerConfig } from "./RecognizerConfig"; + +export interface IConnectionFactory { + create( + config: RecognizerConfig, + authInfo: AuthInfo, + connectionId?: string): IConnection; +} diff --git a/src/common.speech/IntentConnectionFactory.ts b/src/common.speech/IntentConnectionFactory.ts new file mode 100644 index 00000000..c0a626be --- /dev/null +++ b/src/common.speech/IntentConnectionFactory.ts @@ -0,0 +1,105 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +import { WebsocketConnection } from "../common.browser/Exports"; +import { IConnection, IStringDictionary, Storage } from "../common/Exports"; +import { PropertyId } from "../sdk/Exports"; +import { AuthInfo, IConnectionFactory, RecognizerConfig, WebsocketMessageFormatter } from "./Exports"; + +const TestHooksParamName: string = "testhooks"; +const ConnectionIdHeader: string = "X-ConnectionId"; + +export class IntentConnectionFactory implements IConnectionFactory { + + public create = ( + config: RecognizerConfig, + authInfo: AuthInfo, + connectionId?: string): IConnection => { + + let endpoint: string = config.parameters.getProperty(PropertyId.SpeechServiceConnection_Endpoint); + if (!endpoint) { + const region: string = config.parameters.getProperty(PropertyId.SpeechServiceConnection_IntentRegion); + + endpoint = this.host() + Storage.local.getOrAdd("TranslationRelativeUri", "/speech/" + this.getSpeechRegionFromIntentRegion(region) + "/recognition/interactive/cognitiveservices/v1"); + } + + const queryParams: IStringDictionary = { + format: "simple", + language: config.parameters.getProperty(PropertyId.SpeechServiceConnection_RecoLanguage), + }; + + if (this.isDebugModeEnabled) { + queryParams[TestHooksParamName] = "1"; + } + + const headers: IStringDictionary = {}; + headers[authInfo.headerName] = authInfo.token; + headers[ConnectionIdHeader] = connectionId; + + return new WebsocketConnection(endpoint, queryParams, headers, new WebsocketMessageFormatter(), connectionId); + } + + private host(): string { + return Storage.local.getOrAdd("Host", "wss://speech.platform.bing.com"); + } + + private get isDebugModeEnabled(): boolean { + const value = Storage.local.getOrAdd("IsDebugModeEnabled", "false"); + return value.toLowerCase() === "true"; + } + + private getSpeechRegionFromIntentRegion(intentRegion: string): string { + switch (intentRegion) { + case "West US": + case "US West": + case "westus": + return "uswest"; + case "West US 2": + case "US West 2": + case "westus2": + return "uswest2"; + case "South Central US": + case "US South Central": + case "southcentralus": + return "ussouthcentral"; + case "West Central US": + case "US West Central": + case "westcentralus": + return "uswestcentral"; + case "East US": + case "US East": + case "eastus": + return "useast"; + case "East US 2": + case "US East 2": + case "eastus2": + return "useast2"; + case "West Europe": + case "Europe West": + case "westeurope": + return "europewest"; + case "North Europe": + case "Europe North": + case "northeurope": + return "europenorth"; + case "Brazil South": + case "South Brazil": + case "southbrazil": + return "brazilsouth"; + case "Australia East": + case "East Australia": + case "eastaustralia": + return "australiaeast"; + case "Southeast Asia": + case "Asia Southeast": + case "southeastasia": + return "asiasoutheast"; + case "East Asia": + case "Asia East": + case "eastasia": + return "asiaeast"; + default: + return intentRegion; + } + } +} diff --git a/src/common.speech/IntentServiceRecognizer.ts b/src/common.speech/IntentServiceRecognizer.ts new file mode 100644 index 00000000..e39ff54b --- /dev/null +++ b/src/common.speech/IntentServiceRecognizer.ts @@ -0,0 +1,290 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +import { IAudioSource, IConnection } from "../common/Exports"; +import { + CancellationErrorCode, + CancellationReason, + IntentRecognitionCanceledEventArgs, + IntentRecognitionEventArgs, + IntentRecognitionResult, + IntentRecognizer, + PropertyCollection, + PropertyId, + ResultReason, + SpeechRecognitionResult, +} from "../sdk/Exports"; +import { + AddedLmIntent, + CancellationErrorCodePropertyName, + EnumTranslation, + IntentResponse, + RequestSession, + ServiceRecognizerBase, + SimpleSpeechPhrase, + SpeechHypothesis, +} from "./Exports"; +import { IAuthentication } from "./IAuthentication"; +import { IConnectionFactory } from "./IConnectionFactory"; +import { RecognizerConfig } from "./RecognizerConfig"; +import { SpeechConnectionMessage } from "./SpeechConnectionMessage.Internal"; + +// tslint:disable-next-line:max-classes-per-file +export class IntentServiceRecognizer extends ServiceRecognizerBase { + private privIntentRecognizer: IntentRecognizer; + private privAddedLmIntents: { [id: string]: AddedLmIntent; }; + private privIntentDataSent: boolean; + private privUmbrellaIntent: AddedLmIntent; + private privPendingIntentArgs: IntentRecognitionEventArgs; + + public constructor( + authentication: IAuthentication, + connectionFactory: IConnectionFactory, + audioSource: IAudioSource, + recognizerConfig: RecognizerConfig, + recognizer: IntentRecognizer, + intentDataSent: boolean) { + super(authentication, connectionFactory, audioSource, recognizerConfig, recognizer); + this.privIntentRecognizer = recognizer; + this.privIntentDataSent = intentDataSent; + } + + public setIntents(addedIntents: { [id: string]: AddedLmIntent; }, umbrellaIntent: AddedLmIntent): void { + this.privAddedLmIntents = addedIntents; + this.privUmbrellaIntent = umbrellaIntent; + } + + protected processTypeSpecificMessages( + connectionMessage: SpeechConnectionMessage, + requestSession: RequestSession, + connection: IConnection, + successCallback?: (e: IntentRecognitionResult) => void, + errorCallBack?: (e: string) => void): void { + + let result: IntentRecognitionResult; + let ev: IntentRecognitionEventArgs; + + switch (connectionMessage.path.toLowerCase()) { + case "speech.hypothesis": + const speechHypothesis: SpeechHypothesis = SpeechHypothesis.fromJSON(connectionMessage.textBody); + + result = new IntentRecognitionResult( + undefined, + requestSession.requestId, + ResultReason.RecognizingIntent, + speechHypothesis.Text, + speechHypothesis.Duration, + speechHypothesis.Offset + requestSession.currentTurnAudioOffset, + undefined, + connectionMessage.textBody, + undefined); + + ev = new IntentRecognitionEventArgs(result, speechHypothesis.Offset + requestSession.currentTurnAudioOffset, requestSession.sessionId); + + if (!!this.privIntentRecognizer.recognizing) { + try { + this.privIntentRecognizer.recognizing(this.privIntentRecognizer, ev); + /* tslint:disable:no-empty */ + } catch (error) { + // Not going to let errors in the event handler + // trip things up. + } + } + + break; + case "speech.phrase": + const simple: SimpleSpeechPhrase = SimpleSpeechPhrase.fromJSON(connectionMessage.textBody); + + result = new IntentRecognitionResult( + undefined, + requestSession.requestId, + EnumTranslation.implTranslateRecognitionResult(simple.RecognitionStatus), + simple.DisplayText, + simple.Duration, + simple.Offset + requestSession.currentTurnAudioOffset, + undefined, + connectionMessage.textBody, + undefined); + + ev = new IntentRecognitionEventArgs(result, result.offset + requestSession.currentTurnAudioOffset, requestSession.sessionId); + + const sendEvent: () => void = () => { + if (this.privRecognizerConfig.isContinuousRecognition) { + // For continuous recognition telemetry has to be sent for every phrase as per spec. + this.sendTelemetryData(requestSession, requestSession.getTelemetry()); + } + + if (!!this.privIntentRecognizer.recognized) { + try { + this.privIntentRecognizer.recognized(this.privIntentRecognizer, ev); + /* tslint:disable:no-empty */ + } catch (error) { + // Not going to let errors in the event handler + // trip things up. + } + } + + // report result to promise. + if (!!successCallback) { + try { + successCallback(result); + } catch (e) { + if (!!errorCallBack) { + errorCallBack(e); + } + } + // Only invoke the call back once. + // and if it's successful don't invoke the + // error after that. + successCallback = undefined; + errorCallBack = undefined; + } + }; + + // If intent data was sent, the terminal result for this recognizer is an intent being found. + // If no intent data was sent, the terminal event is speech recognition being successful. + if (false === this.privIntentDataSent || ResultReason.NoMatch === ev.result.reason) { + sendEvent(); + } else { + // Squirrel away the args, when the response event arrives it will build upon them + // and then return + this.privPendingIntentArgs = ev; + } + + break; + case "response": + // Response from LUIS + if (this.privRecognizerConfig.isContinuousRecognition) { + // For continuous recognition telemetry has to be sent for every phrase as per spec. + this.sendTelemetryData(requestSession, requestSession.getTelemetry()); + } + + ev = this.privPendingIntentArgs; + this.privPendingIntentArgs = undefined; + + if (undefined === ev) { + if ("" === connectionMessage.textBody) { + // This condition happens if there is nothing but silence in the + // audio sent to the service. + return; + } + + // Odd... Not sure this can happen + ev = new IntentRecognitionEventArgs(new IntentRecognitionResult(), 0 /*TODO*/, requestSession.sessionId); + } + + const intentResponse: IntentResponse = IntentResponse.fromJSON(connectionMessage.textBody); + + // If LUIS didn't return anything, send the existing event, else + // modify it to show the match. + // See if the intent found is in the list of intents asked for. + let addedIntent: AddedLmIntent = this.privAddedLmIntents[intentResponse.topScoringIntent.intent]; + + if (this.privUmbrellaIntent !== undefined) { + addedIntent = this.privUmbrellaIntent; + } + + if (null !== intentResponse && addedIntent !== undefined) { + const intentId = addedIntent.intentName === undefined ? intentResponse.topScoringIntent.intent : addedIntent.intentName; + let reason = ev.result.reason; + + if (undefined !== intentId) { + reason = ResultReason.RecognizedIntent; + } + + // make sure, properties is set. + const properties = (undefined !== ev.result.properties) ? + ev.result.properties : new PropertyCollection(); + + properties.setProperty(PropertyId.LanguageUnderstandingServiceResponse_JsonResult, connectionMessage.textBody); + + ev = new IntentRecognitionEventArgs( + new IntentRecognitionResult( + intentId, + ev.result.resultId, + reason, + ev.result.text, + ev.result.duration, + ev.result.offset + requestSession.currentTurnAudioOffset, + ev.result.errorDetails, + ev.result.json, + properties), + ev.offset + requestSession.currentTurnAudioOffset, + ev.sessionId); + } + + if (!!this.privIntentRecognizer.recognized) { + try { + this.privIntentRecognizer.recognized(this.privIntentRecognizer, ev); + /* tslint:disable:no-empty */ + } catch (error) { + // Not going to let errors in the event handler + // trip things up. + } + } + + // report result to promise. + if (!!successCallback) { + try { + successCallback(ev.result); + } catch (e) { + if (!!errorCallBack) { + errorCallBack(e); + } + } + // Only invoke the call back once. + // and if it's successful don't invoke the + // error after that. + successCallback = undefined; + errorCallBack = undefined; + } + break; + default: + break; + } + } + + // Cancels recognition. + protected cancelRecognition( + sessionId: string, + requestId: string, + cancellationReason: CancellationReason, + errorCode: CancellationErrorCode, + error: string, + cancelRecoCallback: (e: SpeechRecognitionResult) => void): void { + + if (!!this.privIntentRecognizer.canceled) { + const properties: PropertyCollection = new PropertyCollection(); + properties.setProperty(CancellationErrorCodePropertyName, CancellationErrorCode[errorCode]); + + const cancelEvent: IntentRecognitionCanceledEventArgs = new IntentRecognitionCanceledEventArgs( + cancellationReason, + error, + errorCode, + undefined, + undefined, + sessionId); + try { + this.privIntentRecognizer.canceled(this.privIntentRecognizer, cancelEvent); + /* tslint:disable:no-empty */ + } catch { } + + if (!!cancelRecoCallback) { + const result: IntentRecognitionResult = new IntentRecognitionResult( + undefined, // Intent Id + requestId, + ResultReason.Canceled, + undefined, // Text + undefined, // Druation + undefined, // Offset + error, + undefined, // Json + properties); + try { + cancelRecoCallback(result); + /* tslint:disable:no-empty */ + } catch { } + } + } + } +} diff --git a/src/common.speech/QueryParameterNames.ts b/src/common.speech/QueryParameterNames.ts new file mode 100644 index 00000000..52683184 --- /dev/null +++ b/src/common.speech/QueryParameterNames.ts @@ -0,0 +1,26 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +export class QueryParameterNames { + public static get TestHooksParamName(): string { + return "testhooks"; + } + public static get ConnectionIdHeader(): string { + return "X-ConnectionId"; + } + public static get DeploymentIdParamName(): string { + return "cid"; + } + public static get FormatParamName(): string { + return "format"; + } + public static get LanguageParamName(): string { + return "language"; + } + public static get TranslationFromParamName(): string { + return "from"; + } + public static get TranslationToParamName(): string { + return "to"; + } +} diff --git a/src/common.speech/RecognitionEvents.ts b/src/common.speech/RecognitionEvents.ts new file mode 100644 index 00000000..9fa9d8c5 --- /dev/null +++ b/src/common.speech/RecognitionEvents.ts @@ -0,0 +1,173 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +import { EventType, PlatformEvent } from "../common/Exports"; + +export class SpeechRecognitionEvent extends PlatformEvent { + private privRequestId: string; + private privSessionId: string; + + constructor(eventName: string, requestId: string, sessionId: string, eventType: EventType = EventType.Info) { + super(eventName, eventType); + + this.privRequestId = requestId; + this.privSessionId = sessionId; + } + + public get requestId(): string { + return this.privRequestId; + } + + public get sessionId(): string { + return this.privSessionId; + } +} + +// tslint:disable-next-line:max-classes-per-file +export class RecognitionTriggeredEvent extends SpeechRecognitionEvent { + private privAudioSourceId: string; + private privAudioNodeId: string; + + constructor(requestId: string, sessionId: string, audioSourceId: string, audioNodeId: string) { + super("RecognitionTriggeredEvent", requestId, sessionId); + + this.privAudioSourceId = audioSourceId; + this.privAudioNodeId = audioNodeId; + } + + public get audioSourceId(): string { + return this.privAudioSourceId; + } + + public get audioNodeId(): string { + return this.privAudioNodeId; + } +} + +// tslint:disable-next-line:max-classes-per-file +export class ListeningStartedEvent extends SpeechRecognitionEvent { + private privAudioSourceId: string; + private privAudioNodeId: string; + + constructor(requestId: string, sessionId: string, audioSourceId: string, audioNodeId: string) { + super("ListeningStartedEvent", requestId, sessionId); + this.privAudioSourceId = audioSourceId; + this.privAudioNodeId = audioNodeId; + } + + public get audioSourceId(): string { + return this.privAudioSourceId; + } + + public get audioNodeId(): string { + return this.privAudioNodeId; + } +} + +// tslint:disable-next-line:max-classes-per-file +export class ConnectingToServiceEvent extends SpeechRecognitionEvent { + private privAuthFetchEventid: string; + + constructor(requestId: string, authFetchEventid: string, sessionId: string) { + super("ConnectingToServiceEvent", requestId, sessionId); + this.privAuthFetchEventid = authFetchEventid; + } + + public get authFetchEventid(): string { + return this.privAuthFetchEventid; + } +} + +// tslint:disable-next-line:max-classes-per-file +export class RecognitionStartedEvent extends SpeechRecognitionEvent { + private privAudioSourceId: string; + private privAudioNodeId: string; + private privAuthFetchEventId: string; + + constructor(requestId: string, audioSourceId: string, audioNodeId: string, authFetchEventId: string, sessionId: string) { + super("RecognitionStartedEvent", requestId, sessionId); + + this.privAudioSourceId = audioSourceId; + this.privAudioNodeId = audioNodeId; + this.privAuthFetchEventId = authFetchEventId; + } + + public get audioSourceId(): string { + return this.privAudioSourceId; + } + + public get audioNodeId(): string { + return this.privAudioNodeId; + } + + public get authFetchEventId(): string { + return this.privAuthFetchEventId; + } +} + +export enum RecognitionCompletionStatus { + Success, + AudioSourceError, + AudioSourceTimeout, + AuthTokenFetchError, + AuthTokenFetchTimeout, + UnAuthorized, + ConnectTimeout, + ConnectError, + ClientRecognitionActivityTimeout, + UnknownError, +} + +// tslint:disable-next-line:max-classes-per-file +export class RecognitionEndedEvent extends SpeechRecognitionEvent { + private privAudioSourceId: string; + private privAudioNodeId: string; + private privAuthFetchEventId: string; + private privServiceTag: string; + private privStatus: RecognitionCompletionStatus; + private privError: string; + + constructor( + requestId: string, + audioSourceId: string, + audioNodeId: string, + authFetchEventId: string, + sessionId: string, + serviceTag: string, + status: RecognitionCompletionStatus, + error: string) { + + super("RecognitionEndedEvent", requestId, sessionId, status === RecognitionCompletionStatus.Success ? EventType.Info : EventType.Error); + + this.privAudioSourceId = audioSourceId; + this.privAudioNodeId = audioNodeId; + this.privAuthFetchEventId = authFetchEventId; + this.privStatus = status; + this.privError = error; + this.privServiceTag = serviceTag; + } + + public get audioSourceId(): string { + return this.privAudioSourceId; + } + + public get audioNodeId(): string { + return this.privAudioNodeId; + } + + public get authFetchEventId(): string { + return this.privAuthFetchEventId; + } + + public get serviceTag(): string { + return this.privServiceTag; + } + + public get status(): RecognitionCompletionStatus { + return this.privStatus; + } + + public get error(): string { + return this.privError; + } +} diff --git a/src/common.speech/RecognizerConfig.ts b/src/common.speech/RecognizerConfig.ts new file mode 100644 index 00000000..4ecab588 --- /dev/null +++ b/src/common.speech/RecognizerConfig.ts @@ -0,0 +1,136 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +import { PropertyCollection } from "../sdk/Exports"; + +export enum RecognitionMode { + Interactive, + Conversation, + Dictation, +} + +export enum SpeechResultFormat { + Simple, + Detailed, +} + +export class RecognizerConfig { + private privRecognitionMode: RecognitionMode = RecognitionMode.Interactive; + private privPlatformConfig: PlatformConfig; + private privRecognitionActivityTimeout: number; + private privSpeechConfig: PropertyCollection; + + constructor( + platformConfig: PlatformConfig, + recognitionMode: RecognitionMode = RecognitionMode.Interactive, + speechConfig: PropertyCollection) { + this.privPlatformConfig = platformConfig ? platformConfig : new PlatformConfig(new Context(null)); + this.privRecognitionMode = recognitionMode; + this.privRecognitionActivityTimeout = recognitionMode === RecognitionMode.Interactive ? 8000 : 25000; + this.privSpeechConfig = speechConfig; + } + + public get parameters(): PropertyCollection { + return this.privSpeechConfig; + } + + public get recognitionMode(): RecognitionMode { + return this.privRecognitionMode; + } + + public get platformConfig(): PlatformConfig { + return this.privPlatformConfig; + } + + public get recognitionActivityTimeout(): number { + return this.privRecognitionActivityTimeout; + } + + public get isContinuousRecognition(): boolean { + return this.privRecognitionMode !== RecognitionMode.Interactive; + } +} + +// tslint:disable-next-line:max-classes-per-file +export class PlatformConfig { + private context: Context; + + constructor(context: Context) { + this.context = context; + } + + public serialize = (): string => { + return JSON.stringify(this, (key: any, value: any): any => { + if (value && typeof value === "object") { + const replacement: any = {}; + for (const k in value) { + if (Object.hasOwnProperty.call(value, k)) { + replacement[k && k.charAt(0).toLowerCase() + k.substring(1)] = value[k]; + } + } + return replacement; + } + return value; + }); + } + + public get Context(): Context { + return this.context; + } + +} + +// tslint:disable-next-line:max-classes-per-file +export class Context { + public system: System; + public os: OS; + + constructor(os: OS) { + this.system = new System(); + this.os = os; + } +} + +// tslint:disable-next-line:max-classes-per-file +export class System { + public name: string; + public version: string; + public build: string; + public lang: string; + + constructor() { + // Note: below will be patched for official builds. + const SPEECHSDK_CLIENTSDK_VERSION = "1.1.0-alpha.0.1"; + + this.name = "SpeechSDK"; + this.version = SPEECHSDK_CLIENTSDK_VERSION; + this.build = "JavaScript"; + this.lang = "JavaScript"; + } +} + +// tslint:disable-next-line:max-classes-per-file +export class OS { + public platform: string; + public name: string; + public version: string; + + constructor(platform: string, name: string, version: string) { + this.platform = platform; + this.name = name; + this.version = version; + } +} + +// tslint:disable-next-line:max-classes-per-file +export class Device { + public manufacturer: string; + public model: string; + public version: string; + + constructor(manufacturer: string, model: string, version: string) { + this.manufacturer = manufacturer; + this.model = model; + this.version = version; + } +} diff --git a/src/common.speech/RequestSession.ts b/src/common.speech/RequestSession.ts new file mode 100644 index 00000000..a50f7f8e --- /dev/null +++ b/src/common.speech/RequestSession.ts @@ -0,0 +1,191 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +import { ReplayableAudioNode } from "../common.browser/Exports"; +import { + createNoDashGuid, + Deferred, + Events, + IAudioStreamNode, + IDetachable, + IEventSource, + PlatformEvent, + Promise, +} from "../common/Exports"; +import { + ConnectingToServiceEvent, + ListeningStartedEvent, + RecognitionStartedEvent, + RecognitionTriggeredEvent, + SpeechRecognitionEvent, +} from "./RecognitionEvents"; +import { ServiceTelemetryListener } from "./ServiceTelemetryListener.Internal"; + +export class RequestSession { + private privIsDisposed: boolean = false; + private privServiceTelemetryListener: ServiceTelemetryListener; + private privDetachables: IDetachable[] = new Array(); + private privRequestId: string; + private privAudioSourceId: string; + private privAudioNodeId: string; + private privAudioNode: ReplayableAudioNode; + private privAuthFetchEventId: string; + private privIsAudioNodeDetached: boolean = false; + private privIsCompleted: boolean = false; + private privRequestCompletionDeferral: Deferred; + private privIsSpeechEnded: boolean = false; + private privIsCanceled: boolean = false; + private privContextJson: string; + private privTurnStartAudioOffset: number = 0; + private privLastRecoOffset: number = 0; + + protected privSessionId: string; + + constructor(audioSourceId: string, contextJson: string) { + this.privAudioSourceId = audioSourceId; + this.privRequestId = createNoDashGuid(); + this.privAudioNodeId = createNoDashGuid(); + this.privRequestCompletionDeferral = new Deferred(); + this.privContextJson = contextJson; + this.privServiceTelemetryListener = new ServiceTelemetryListener(this.privRequestId, this.privAudioSourceId, this.privAudioNodeId); + + this.onEvent(new RecognitionTriggeredEvent(this.requestId, this.privSessionId, this.privAudioSourceId, this.privAudioNodeId)); + } + + public get contextJson(): string { + return this.privContextJson; + } + + public get sessionId(): string { + return this.privSessionId; + } + + public get requestId(): string { + return this.privRequestId; + } + + public get audioNodeId(): string { + return this.privAudioNodeId; + } + + public get completionPromise(): Promise { + return this.privRequestCompletionDeferral.promise(); + } + + public get isSpeechEnded(): boolean { + return this.privIsSpeechEnded; + } + + public get isCompleted(): boolean { + return this.privIsCompleted; + } + + public get isCanceled(): boolean { + return this.privIsCanceled; + } + + public get currentTurnAudioOffset(): number { + return this.privTurnStartAudioOffset; + } + + public listenForServiceTelemetry(eventSource: IEventSource): void { + this.privDetachables.push(eventSource.attachListener(this.privServiceTelemetryListener)); + } + + public onAudioSourceAttachCompleted = (audioNode: ReplayableAudioNode, isError: boolean, error?: string): void => { + this.privAudioNode = audioNode; + + if (isError) { + this.onComplete(); + } else { + this.onEvent(new ListeningStartedEvent(this.privRequestId, this.privSessionId, this.privAudioSourceId, this.privAudioNodeId)); + } + } + + public onPreConnectionStart = (authFetchEventId: string, connectionId: string): void => { + this.privAuthFetchEventId = authFetchEventId; + this.privSessionId = connectionId; + this.onEvent(new ConnectingToServiceEvent(this.privRequestId, this.privAuthFetchEventId, this.privSessionId)); + } + + public onAuthCompleted = (isError: boolean, error?: string): void => { + if (isError) { + this.onComplete(); + } + } + + public onConnectionEstablishCompleted = (statusCode: number, reason?: string): void => { + if (statusCode === 200) { + this.onEvent(new RecognitionStartedEvent(this.requestId, this.privAudioSourceId, this.privAudioNodeId, this.privAuthFetchEventId, this.privSessionId)); + this.privAudioNode.replay(); + this.privTurnStartAudioOffset = this.privLastRecoOffset; + return; + } else if (statusCode === 403) { + this.onComplete(); + } else { + this.onComplete(); + } + } + + public onServiceTurnEndResponse = (continuousRecognition: boolean): void => { + if (!continuousRecognition || this.isSpeechEnded) { + this.onComplete(); + } else { + // Start a new request set. + this.privTurnStartAudioOffset = this.privLastRecoOffset; + this.privRequestId = createNoDashGuid(); + this.privAudioNode.replay(); + } + } + + public onServiceRecognized(offset: number): void { + this.privLastRecoOffset = offset; + this.privAudioNode.shrinkBuffers(offset); + } + + public dispose = (error?: string): void => { + if (!this.privIsDisposed) { + // we should have completed by now. If we did not its an unknown error. + this.privIsDisposed = true; + for (const detachable of this.privDetachables) { + detachable.detach(); + } + + this.privServiceTelemetryListener.dispose(); + } + } + + public getTelemetry = (): string => { + return this.privServiceTelemetryListener.getTelemetry(); + } + + public onCancelled(): void { + this.privIsCanceled = true; + } + + // Should be called with the audioNode for this session has indicated that it is out of speech. + public onSpeechEnded(): void { + this.privIsSpeechEnded = true; + } + + protected onEvent = (event: SpeechRecognitionEvent): void => { + this.privServiceTelemetryListener.onEvent(event); + Events.instance.onEvent(event); + } + + private onComplete = (): void => { + if (!this.privIsCompleted) { + this.privIsCompleted = true; + this.detachAudioNode(); + } + } + + private detachAudioNode = (): void => { + if (!this.privIsAudioNodeDetached) { + this.privIsAudioNodeDetached = true; + if (this.privAudioNode) { + this.privAudioNode.detach(); + } + } + } +} diff --git a/src/common.speech/ServiceMessages/DetailedSpeechPhrase.ts b/src/common.speech/ServiceMessages/DetailedSpeechPhrase.ts new file mode 100644 index 00000000..a3b19e79 --- /dev/null +++ b/src/common.speech/ServiceMessages/DetailedSpeechPhrase.ts @@ -0,0 +1,46 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +import { RecognitionStatus } from "../Exports"; + +// speech.phrase for detailed +export interface IDetailedSpeechPhrase { + RecognitionStatus: RecognitionStatus; + NBest: IPhrase[]; + Duration?: number; + Offset?: number; +} + +export interface IPhrase { + Confidence?: number; + Lexical: string; + ITN: string; + MaskedITN: string; + Display: string; +} + +export class DetailedSpeechPhrase implements IDetailedSpeechPhrase { + private privDetailedSpeechPhrase: IDetailedSpeechPhrase; + + private constructor(json: string) { + this.privDetailedSpeechPhrase = JSON.parse(json); + this.privDetailedSpeechPhrase.RecognitionStatus = (RecognitionStatus as any)[this.privDetailedSpeechPhrase.RecognitionStatus]; + } + + public static fromJSON(json: string): DetailedSpeechPhrase { + return new DetailedSpeechPhrase(json); + } + + public get RecognitionStatus(): RecognitionStatus { + return this.privDetailedSpeechPhrase.RecognitionStatus; + } + public get NBest(): IPhrase[] { + return this.privDetailedSpeechPhrase.NBest; + } + public get Duration(): number { + return this.privDetailedSpeechPhrase.Duration; + } + public get Offset(): number { + return this.privDetailedSpeechPhrase.Offset; + } +} diff --git a/src/common.speech/ServiceMessages/Enums.ts b/src/common.speech/ServiceMessages/Enums.ts new file mode 100644 index 00000000..25426d30 --- /dev/null +++ b/src/common.speech/ServiceMessages/Enums.ts @@ -0,0 +1,35 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +/** + * @class SynthesisStatus + * @private + */ +export enum SynthesisStatus { + /** + * The response contains valid audio data. + * @member SynthesisStatus.Success + */ + Success, + + /** + * Indicates the end of audio data. No valid audio data is included in the message. + * @member SynthesisStatus.SynthesisEnd + */ + SynthesisEnd, + + /** + * Indicates an error occurred during synthesis data processing. + * @member SynthesisStatus.Error + */ + Error, +} + +export enum RecognitionStatus { + Success, + NoMatch, + InitialSilenceTimeout, + BabbleTimeout, + Error, + EndOfDictation, +} diff --git a/src/common.speech/ServiceMessages/IntentResponse.ts b/src/common.speech/ServiceMessages/IntentResponse.ts new file mode 100644 index 00000000..737c67b0 --- /dev/null +++ b/src/common.speech/ServiceMessages/IntentResponse.ts @@ -0,0 +1,46 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. +// response + +export interface IIntentResponse { + query: string; + topScoringIntent: ISingleIntent; + entities: IIntentEntity[]; +} + +export interface IIntentEntity { + entity: string; + type: string; + startIndex: number; + endIndex: number; + score: number; +} + +export interface ISingleIntent { + intent: string; + score: number; +} + +export class IntentResponse implements IIntentResponse { + private privIntentResponse: IIntentResponse; + + private constructor(json: string) { + this.privIntentResponse = JSON.parse(json); + } + + public static fromJSON(json: string): IntentResponse { + return new IntentResponse(json); + } + + public get query(): string { + return this.privIntentResponse.query; + } + + public get topScoringIntent(): ISingleIntent { + return this.privIntentResponse.topScoringIntent; + } + + public get entities(): IIntentEntity[] { + return this.privIntentResponse.entities; + } +} diff --git a/src/common.speech/ServiceMessages/SimpleSpeechPhrase.ts b/src/common.speech/ServiceMessages/SimpleSpeechPhrase.ts new file mode 100644 index 00000000..13100d90 --- /dev/null +++ b/src/common.speech/ServiceMessages/SimpleSpeechPhrase.ts @@ -0,0 +1,41 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +import { RecognitionStatus } from "../Exports"; + +// speech.phrase +export interface ISimpleSpeechPhrase { + RecognitionStatus: RecognitionStatus; + DisplayText: string; + Offset?: number; + Duration?: number; +} + +export class SimpleSpeechPhrase implements ISimpleSpeechPhrase { + private privSimpleSpeechPhrase: ISimpleSpeechPhrase; + + private constructor(json: string) { + this.privSimpleSpeechPhrase = JSON.parse(json); + this.privSimpleSpeechPhrase.RecognitionStatus = (RecognitionStatus as any)[this.privSimpleSpeechPhrase.RecognitionStatus]; + } + + public static fromJSON(json: string): SimpleSpeechPhrase { + return new SimpleSpeechPhrase(json); + } + + public get RecognitionStatus(): RecognitionStatus { + return this.privSimpleSpeechPhrase.RecognitionStatus; + } + + public get DisplayText(): string { + return this.privSimpleSpeechPhrase.DisplayText; + } + + public get Offset(): number { + return this.privSimpleSpeechPhrase.Offset; + } + + public get Duration(): number { + return this.privSimpleSpeechPhrase.Duration; + } +} diff --git a/src/common.speech/ServiceMessages/SpeechDetected.ts b/src/common.speech/ServiceMessages/SpeechDetected.ts new file mode 100644 index 00000000..0d0d88aa --- /dev/null +++ b/src/common.speech/ServiceMessages/SpeechDetected.ts @@ -0,0 +1,23 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +// speech.endDetected +export interface ISpeechDetected { + Offset: number; +} + +export class SpeechDetected implements ISpeechDetected { + private privSpeechStartDetected: ISpeechDetected; + + private constructor(json: string) { + this.privSpeechStartDetected = JSON.parse(json); + } + + public static fromJSON(json: string): SpeechDetected { + return new SpeechDetected(json); + } + + public get Offset(): number { + return this.privSpeechStartDetected.Offset; + } +} diff --git a/src/common.speech/ServiceMessages/SpeechHypothesis.ts b/src/common.speech/ServiceMessages/SpeechHypothesis.ts new file mode 100644 index 00000000..a559b160 --- /dev/null +++ b/src/common.speech/ServiceMessages/SpeechHypothesis.ts @@ -0,0 +1,33 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +// speech.hypothesis +export interface ISpeechHypothesis { + Text: string; + Offset: number; + Duration: number; +} + +export class SpeechHypothesis implements ISpeechHypothesis { + private privSpeechHypothesis: ISpeechHypothesis; + + private constructor(json: string) { + this.privSpeechHypothesis = JSON.parse(json); + } + + public static fromJSON(json: string): SpeechHypothesis { + return new SpeechHypothesis(json); + } + + public get Text(): string { + return this.privSpeechHypothesis.Text; + } + + public get Offset(): number { + return this.privSpeechHypothesis.Offset; + } + + public get Duration(): number { + return this.privSpeechHypothesis.Duration; + } +} diff --git a/src/common.speech/ServiceMessages/TranslationHypothesis.ts b/src/common.speech/ServiceMessages/TranslationHypothesis.ts new file mode 100644 index 00000000..dc467c14 --- /dev/null +++ b/src/common.speech/ServiceMessages/TranslationHypothesis.ts @@ -0,0 +1,42 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +import { ITranslations } from "../Exports"; +import { TranslationStatus } from "../TranslationStatus"; + +// translation.hypothesis +export interface ITranslationHypothesis { + Duration: number; + Offset: number; + Text: string; + Translation: ITranslations; +} + +export class TranslationHypothesis implements ITranslationHypothesis { + private privTranslationHypothesis: ITranslationHypothesis; + + private constructor(json: string) { + this.privTranslationHypothesis = JSON.parse(json); + this.privTranslationHypothesis.Translation.TranslationStatus = (TranslationStatus as any)[this.privTranslationHypothesis.Translation.TranslationStatus]; + } + + public static fromJSON(json: string): TranslationHypothesis { + return new TranslationHypothesis(json); + } + + public get Duration(): number { + return this.privTranslationHypothesis.Duration; + } + + public get Offset(): number { + return this.privTranslationHypothesis.Offset; + } + + public get Text(): string { + return this.privTranslationHypothesis.Text; + } + + public get Translation(): ITranslations { + return this.privTranslationHypothesis.Translation; + } +} diff --git a/src/common.speech/ServiceMessages/TranslationPhrase.ts b/src/common.speech/ServiceMessages/TranslationPhrase.ts new file mode 100644 index 00000000..17d7e7ae --- /dev/null +++ b/src/common.speech/ServiceMessages/TranslationPhrase.ts @@ -0,0 +1,50 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +import { ITranslations, RecognitionStatus } from "../Exports"; +import { TranslationStatus } from "../TranslationStatus"; + +// translation.phrase +export interface ITranslationPhrase { + RecognitionStatus: RecognitionStatus; + Offset: number; + Duration: number; + Text: string; + Translation: ITranslations; +} + +export class TranslationPhrase implements ITranslationPhrase { + private privTranslationPhrase: ITranslationPhrase; + + private constructor(json: string) { + this.privTranslationPhrase = JSON.parse(json); + this.privTranslationPhrase.RecognitionStatus = (RecognitionStatus as any)[this.privTranslationPhrase.RecognitionStatus]; + if (this.privTranslationPhrase.Translation !== undefined) { + this.privTranslationPhrase.Translation.TranslationStatus = (TranslationStatus as any)[this.privTranslationPhrase.Translation.TranslationStatus]; + } + } + + public static fromJSON(json: string): TranslationPhrase { + return new TranslationPhrase(json); + } + + public get RecognitionStatus(): RecognitionStatus { + return this.privTranslationPhrase.RecognitionStatus; + } + + public get Offset(): number { + return this.privTranslationPhrase.Offset; + } + + public get Duration(): number { + return this.privTranslationPhrase.Duration; + } + + public get Text(): string { + return this.privTranslationPhrase.Text; + } + + public get Translation(): ITranslations { + return this.privTranslationPhrase.Translation; + } +} diff --git a/src/common.speech/ServiceMessages/TranslationSynthesisEnd.ts b/src/common.speech/ServiceMessages/TranslationSynthesisEnd.ts new file mode 100644 index 00000000..982bca7f --- /dev/null +++ b/src/common.speech/ServiceMessages/TranslationSynthesisEnd.ts @@ -0,0 +1,31 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +import { SynthesisStatus } from "../Exports"; + +// translation.synthesis.end +export interface ITranslationSynthesisEnd { + SynthesisStatus: SynthesisStatus; + FailureReason: string; +} + +export class TranslationSynthesisEnd implements ITranslationSynthesisEnd { + private privSynthesisEnd: ITranslationSynthesisEnd; + + private constructor(json: string) { + this.privSynthesisEnd = JSON.parse(json); + this.privSynthesisEnd.SynthesisStatus = (SynthesisStatus as any)[this.privSynthesisEnd.SynthesisStatus]; + } + + public static fromJSON(json: string): TranslationSynthesisEnd { + return new TranslationSynthesisEnd(json); + } + + public get SynthesisStatus(): SynthesisStatus { + return this.privSynthesisEnd.SynthesisStatus; + } + + public get FailureReason(): string { + return this.privSynthesisEnd.FailureReason; + } +} diff --git a/src/common.speech/ServiceRecognizerBase.ts b/src/common.speech/ServiceRecognizerBase.ts new file mode 100644 index 00000000..bc0cbccf --- /dev/null +++ b/src/common.speech/ServiceRecognizerBase.ts @@ -0,0 +1,500 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +import { ReplayableAudioNode } from "../common.browser/Exports"; +import { + ArgumentNullError, + ConnectionClosedEvent, + ConnectionEvent, + ConnectionMessage, + ConnectionOpenResponse, + ConnectionState, + createNoDashGuid, + Deferred, + IAudioSource, + IAudioStreamNode, + IConnection, + IDisposable, + IStreamChunk, + MessageType, + Promise, + PromiseHelper, + PromiseResult, +} from "../common/Exports"; +import { AudioStreamFormatImpl } from "../sdk/Audio/AudioStreamFormat"; +import { + CancellationErrorCode, + CancellationReason, + PropertyId, + RecognitionEventArgs, + Recognizer, + SessionEventArgs, + SpeechRecognitionResult, +} from "../sdk/Exports"; +import { + RequestSession, + SpeechDetected, +} from "./Exports"; +import { + AuthInfo, + IAuthentication, +} from "./IAuthentication"; +import { IConnectionFactory } from "./IConnectionFactory"; +import { RecognizerConfig } from "./RecognizerConfig"; +import { SpeechConnectionMessage } from "./SpeechConnectionMessage.Internal"; + +export abstract class ServiceRecognizerBase implements IDisposable { + private privAuthentication: IAuthentication; + private privConnectionFactory: IConnectionFactory; + private privAudioSource: IAudioSource; + private privSpeechConfigConnectionId: string; + private privConnectionConfigurationPromise: Promise; + private privConnectionId: string; + private privAuthFetchEventId: string; + private privIsDisposed: boolean; + private privRecognizer: Recognizer; + protected privRecognizerConfig: RecognizerConfig; + + public constructor( + authentication: IAuthentication, + connectionFactory: IConnectionFactory, + audioSource: IAudioSource, + recognizerConfig: RecognizerConfig, + recognizer: Recognizer) { + + if (!authentication) { + throw new ArgumentNullError("authentication"); + } + + if (!connectionFactory) { + throw new ArgumentNullError("connectionFactory"); + } + + if (!audioSource) { + throw new ArgumentNullError("audioSource"); + } + + if (!recognizerConfig) { + throw new ArgumentNullError("recognizerConfig"); + } + + this.privAuthentication = authentication; + this.privConnectionFactory = connectionFactory; + this.privAudioSource = audioSource; + this.privRecognizerConfig = recognizerConfig; + this.privIsDisposed = false; + this.privRecognizer = recognizer; + } + + public get audioSource(): IAudioSource { + return this.privAudioSource; + } + + public isDisposed(): boolean { + return this.privIsDisposed; + } + public dispose(reason?: string): void { + this.privIsDisposed = true; + if (this.privConnectionConfigurationPromise) { + this.privConnectionConfigurationPromise.onSuccessContinueWith((connection: IConnection) => { + connection.dispose(reason); + }); + } + } + + public recognize( + speechContextJson: string, + successCallback: (e: SpeechRecognitionResult) => void, + errorCallBack: (e: string) => void, + ): Promise { + const requestSession = new RequestSession(this.privAudioSource.id(), speechContextJson); + + requestSession.listenForServiceTelemetry(this.privAudioSource.events); + + return this.audioSource + .attach(requestSession.audioNodeId) + .continueWithPromise((result: PromiseResult) => { + let audioNode: ReplayableAudioNode; + + if (result.isError) { + this.cancelRecognitionLocal(requestSession, CancellationReason.Error, CancellationErrorCode.ConnectionFailure, result.error, successCallback); + return PromiseHelper.fromError(result.error); + } else { + audioNode = new ReplayableAudioNode(result.result, this.audioSource.format as AudioStreamFormatImpl); + requestSession.onAudioSourceAttachCompleted(audioNode, false); + } + + return this.configureConnection(requestSession) + .on((_: IConnection) => { + + const sessionStartEventArgs: SessionEventArgs = new SessionEventArgs(requestSession.sessionId); + + if (!!this.privRecognizer.sessionStarted) { + this.privRecognizer.sessionStarted(this.privRecognizer, sessionStartEventArgs); + } + + const messageRetrievalPromise = this.receiveMessage(requestSession, successCallback, errorCallBack); + const audioSendPromise = this.sendAudio(audioNode, requestSession); + + /* tslint:disable:no-empty */ + audioSendPromise.on((_: boolean) => { }, (error: string) => { + this.cancelRecognitionLocal(requestSession, CancellationReason.Error, CancellationErrorCode.RuntimeError, error, successCallback); + }); + + const completionPromise = PromiseHelper.whenAll([messageRetrievalPromise, audioSendPromise]); + + return completionPromise.on((r: boolean) => { + requestSession.dispose(); + this.sendTelemetryData(requestSession, requestSession.getTelemetry()); + }, (error: string) => { + requestSession.dispose(error); + this.sendTelemetryData(requestSession, requestSession.getTelemetry()); + this.cancelRecognitionLocal(requestSession, CancellationReason.Error, CancellationErrorCode.RuntimeError, error, successCallback); + }); + + }, (error: string) => { + this.cancelRecognitionLocal(requestSession, CancellationReason.Error, CancellationErrorCode.ConnectionFailure, error, successCallback); + }).on(() => { + return requestSession.completionPromise; + }, (error: string) => { + this.cancelRecognitionLocal(requestSession, CancellationReason.Error, CancellationErrorCode.RuntimeError, error, successCallback); + }).onSuccessContinueWithPromise((_: IConnection): Promise => { + return PromiseHelper.fromResult(true); + }); + }); + } + + // Called when telemetry data is sent to the service. + // Used for testing Telemetry capture. + public static telemetryData: (json: string) => void; + public static telemetryDataEnabled: boolean = true; + + protected abstract processTypeSpecificMessages( + connectionMessage: SpeechConnectionMessage, + requestSession: RequestSession, + connection: IConnection, + successCallback?: (e: SpeechRecognitionResult) => void, + errorCallBack?: (e: string) => void): void; + + protected sendTelemetryData = (requestSession: RequestSession, telemetryData: string) => { + if (ServiceRecognizerBase.telemetryDataEnabled !== true || + this.privIsDisposed) { + return PromiseHelper.fromResult(true); + } + + if (!!ServiceRecognizerBase.telemetryData) { + try { + ServiceRecognizerBase.telemetryData(telemetryData); + /* tslint:disable:no-empty */ + } catch { } + } + + return this.fetchConnection(requestSession).onSuccessContinueWithPromise((connection: IConnection): Promise => { + return connection.send(new SpeechConnectionMessage( + MessageType.Text, + "telemetry", + requestSession.requestId, + "application/json", + telemetryData)); + }); + } + + // Cancels recognition. + protected abstract cancelRecognition( + sessionId: string, + requestId: string, + cancellationReason: CancellationReason, + errorCode: CancellationErrorCode, + error: string, + cancelRecoCallback: (r: SpeechRecognitionResult) => void): void; + + // Cancels recognition. + protected cancelRecognitionLocal( + requestSession: RequestSession, + cancellationReason: CancellationReason, + errorCode: CancellationErrorCode, + error: string, + cancelRecoCallback: (r: SpeechRecognitionResult) => void): void { + + if (!requestSession.isCanceled) { + requestSession.onCancelled(); + + this.cancelRecognition( + requestSession.sessionId, + requestSession.requestId, + cancellationReason, + errorCode, + error, + cancelRecoCallback); + } + } + + private fetchConnection = (requestSession: RequestSession): Promise => { + return this.configureConnection(requestSession); + } + + private configureConnection = (requestSession: RequestSession, isUnAuthorized: boolean = false): Promise => { + if (this.privConnectionConfigurationPromise) { + if (this.privConnectionConfigurationPromise.result().isCompleted && + (this.privConnectionConfigurationPromise.result().isError + || this.privConnectionConfigurationPromise.result().result.state() === ConnectionState.Disconnected)) { + + this.privConnectionId = null; + this.privConnectionConfigurationPromise = null; + return this.configureConnection(requestSession); + } else { + // requestSession.onConnectionEstablishCompleted(200); + return this.privConnectionConfigurationPromise; + } + } + + this.privAuthFetchEventId = createNoDashGuid(); + this.privConnectionId = createNoDashGuid(); + + requestSession.onPreConnectionStart(this.privAuthFetchEventId, this.privConnectionId); + + const authPromise = isUnAuthorized ? this.privAuthentication.fetchOnExpiry(this.privAuthFetchEventId) : this.privAuthentication.fetch(this.privAuthFetchEventId); + + this.privConnectionConfigurationPromise = authPromise + .continueWithPromise((result: PromiseResult) => { + if (result.isError) { + requestSession.onAuthCompleted(true, result.error); + throw new Error(result.error); + } else { + requestSession.onAuthCompleted(false); + } + + const connection: IConnection = this.privConnectionFactory.create(this.privRecognizerConfig, result.result, this.privConnectionId); + requestSession.listenForServiceTelemetry(connection.events); + + return connection.open().onSuccessContinueWithPromise((response: ConnectionOpenResponse): Promise => { + if (response.statusCode === 200) { + requestSession.onPreConnectionStart(this.privAuthFetchEventId, this.privConnectionId); + requestSession.onConnectionEstablishCompleted(response.statusCode); + // requestSession.listenForServiceTelemetry(this.privConnectionFetchPromise.result().result.events); + return this.sendSpeechConfig(connection, requestSession, this.privRecognizerConfig.platformConfig.serialize()) + .onSuccessContinueWithPromise((_: boolean) => { + return this.sendSpeechContext(connection, requestSession, requestSession.contextJson).onSuccessContinueWith((_: boolean) => { + return connection; + }); + }); + + } else if (response.statusCode === 403 && !isUnAuthorized) { + return this.configureConnection(requestSession, true); + } else { + requestSession.onConnectionEstablishCompleted(response.statusCode, response.reason); + return PromiseHelper.fromError(`Unable to contact server. StatusCode: ${response.statusCode}, ${this.privRecognizerConfig.parameters.getProperty(PropertyId.SpeechServiceConnection_Endpoint)} Reason: ${response.reason}`); + } + }); + }); + + return this.privConnectionConfigurationPromise; + } + + private receiveMessage = ( + requestSession: RequestSession, + successCallback: (e: SpeechRecognitionResult) => void, + errorCallBack: (e: string) => void, + ): Promise => { + return this.fetchConnection(requestSession).onSuccessContinueWithPromise((connection: IConnection): Promise => { + return connection.read() + .onSuccessContinueWithPromise((message: ConnectionMessage) => { + if (this.privIsDisposed) { + // We're done. + return PromiseHelper.fromResult(true); + } + + // indicates we are draining the queue and it came with no message; + if (!message) { + if (requestSession.isCompleted) { + return PromiseHelper.fromResult(true); + } else { + return this.receiveMessage(requestSession, successCallback, errorCallBack); + } + } + + const connectionMessage = SpeechConnectionMessage.fromConnectionMessage(message); + + if (connectionMessage.requestId.toLowerCase() === requestSession.requestId.toLowerCase()) { + switch (connectionMessage.path.toLowerCase()) { + case "turn.start": + break; + case "speech.startdetected": + const speechStartDetected: SpeechDetected = SpeechDetected.fromJSON(connectionMessage.textBody); + + const speechStartEventArgs = new RecognitionEventArgs(speechStartDetected.Offset, requestSession.sessionId); + + if (!!this.privRecognizer.speechStartDetected) { + this.privRecognizer.speechStartDetected(this.privRecognizer, speechStartEventArgs); + } + + break; + case "speech.enddetected": + + let json: string; + + if (connectionMessage.textBody.length > 0) { + json = connectionMessage.textBody; + } else { + // If the request was empty, the JSON returned is empty. + json = "{ Offset: 0 }"; + } + + const speechStopDetected: SpeechDetected = SpeechDetected.fromJSON(json); + + requestSession.onServiceRecognized(speechStopDetected.Offset + requestSession.currentTurnAudioOffset); + + const speechStopEventArgs = new RecognitionEventArgs(speechStopDetected.Offset + requestSession.currentTurnAudioOffset, requestSession.sessionId); + + if (!!this.privRecognizer.speechEndDetected) { + this.privRecognizer.speechEndDetected(this.privRecognizer, speechStopEventArgs); + } + + if (requestSession.isSpeechEnded && this.privRecognizerConfig.isContinuousRecognition) { + this.cancelRecognitionLocal(requestSession, CancellationReason.EndOfStream, CancellationErrorCode.NoError, undefined, successCallback); + } + break; + case "turn.end": + const sessionStopEventArgs: SessionEventArgs = new SessionEventArgs(requestSession.sessionId); + requestSession.onServiceTurnEndResponse(this.privRecognizerConfig.isContinuousRecognition); + if (!this.privRecognizerConfig.isContinuousRecognition || requestSession.isSpeechEnded) { + if (!!this.privRecognizer.sessionStopped) { + this.privRecognizer.sessionStopped(this.privRecognizer, sessionStopEventArgs); + } + + return PromiseHelper.fromResult(true); + } else { + this.fetchConnection(requestSession).onSuccessContinueWith((connection: IConnection) => { + this.sendSpeechContext(connection, requestSession, requestSession.contextJson); + }); + } + default: + this.processTypeSpecificMessages( + connectionMessage, + requestSession, + connection, + successCallback, + errorCallBack); + } + } + + return this.receiveMessage(requestSession, successCallback, errorCallBack); + }); + }); + } + + private sendSpeechConfig = (connection: IConnection, requestSession: RequestSession, speechConfigJson: string): Promise => { + // filter out anything that is not required for the service to work. + if (ServiceRecognizerBase.telemetryDataEnabled !== true) { + const withTelemetry = JSON.parse(speechConfigJson); + + const replacement: any = { + context: { + system: withTelemetry.context.system, + }, + }; + + speechConfigJson = JSON.stringify(replacement); + } + + if (speechConfigJson && this.privConnectionId !== this.privSpeechConfigConnectionId) { + this.privSpeechConfigConnectionId = this.privConnectionId; + return connection.send(new SpeechConnectionMessage( + MessageType.Text, + "speech.config", + requestSession.requestId, + "application/json", + speechConfigJson)); + } + + return PromiseHelper.fromResult(true); + } + + private sendSpeechContext = (connection: IConnection, requestSession: RequestSession, speechContextJson: string): Promise => { + if (speechContextJson) { + return connection.send(new SpeechConnectionMessage( + MessageType.Text, + "speech.context", + requestSession.requestId, + "application/json", + speechContextJson)); + } + return PromiseHelper.fromResult(true); + } + + private sendAudio = ( + audioStreamNode: IAudioStreamNode, + requestSession: RequestSession): Promise => { + // NOTE: Home-baked promises crash ios safari during the invocation + // of the error callback chain (looks like the recursion is way too deep, and + // it blows up the stack). The following construct is a stop-gap that does not + // bubble the error up the callback chain and hence circumvents this problem. + // TODO: rewrite with ES6 promises. + const deferred = new Deferred(); + + // The time we last sent data to the service. + let lastSendTime: number = Date.now(); + + const audioFormat: AudioStreamFormatImpl = this.privAudioSource.format as AudioStreamFormatImpl; + + const readAndUploadCycle = (_: boolean) => { + + // If speech is done, stop sending audio. + if (!this.privIsDisposed && !requestSession.isSpeechEnded && !requestSession.isCompleted) { + this.fetchConnection(requestSession).onSuccessContinueWith((connection: IConnection) => { + audioStreamNode.read().on( + (audioStreamChunk: IStreamChunk) => { + + // we have a new audio chunk to upload. + if (requestSession.isSpeechEnded) { + // If service already recognized audio end then dont send any more audio + deferred.resolve(true); + return; + } + + const payload = (audioStreamChunk.isEnd) ? null : audioStreamChunk.buffer; + const uploaded: Promise = connection.send( + new SpeechConnectionMessage( + MessageType.Binary, "audio", requestSession.requestId, null, payload)); + + if (!audioStreamChunk.isEnd) { + + // Caculate any delay to the audio stream needed. /2 to allow 2x real time transmit rate max. + const minSendTime = ((payload.byteLength / audioFormat.avgBytesPerSec) / 2) * 1000; + + const delay: number = Math.max(0, (lastSendTime - Date.now() + minSendTime)); + + uploaded.onSuccessContinueWith((result: boolean) => { + setTimeout(() => { + lastSendTime = Date.now(); + readAndUploadCycle(result); + }, delay); + }); + } else { + // the audio stream has been closed, no need to schedule next + // read-upload cycle. + requestSession.onSpeechEnded(); + deferred.resolve(true); + } + }, + (error: string) => { + if (requestSession.isSpeechEnded) { + // For whatever reason, Reject is used to remove queue subscribers inside + // the Queue.DrainAndDispose invoked from DetachAudioNode down below, which + // means that sometimes things can be rejected in normal circumstances, without + // any errors. + deferred.resolve(true); // TODO: remove the argument, it's is completely meaningless. + } else { + // Only reject, if there was a proper error. + deferred.reject(error); + } + }); + }); + } + }; + + readAndUploadCycle(true); + + return deferred.promise(); + } +} diff --git a/src/common.speech/ServiceTelemetryListener.Internal.ts b/src/common.speech/ServiceTelemetryListener.Internal.ts new file mode 100644 index 00000000..ab9560da --- /dev/null +++ b/src/common.speech/ServiceTelemetryListener.Internal.ts @@ -0,0 +1,225 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +import { + AudioSourceErrorEvent, + AudioStreamNodeAttachedEvent, + AudioStreamNodeAttachingEvent, + AudioStreamNodeDetachedEvent, + AudioStreamNodeErrorEvent, + ConnectionEstablishedEvent, + ConnectionEstablishErrorEvent, + ConnectionMessageReceivedEvent, + ConnectionStartEvent, + IEventListener, + IStringDictionary, + PlatformEvent, +} from "../common/Exports"; +import { ConnectingToServiceEvent, RecognitionTriggeredEvent } from "./RecognitionEvents"; + +interface ITelemetry { + Metrics: IMetric[]; + ReceivedMessages: IStringDictionary; +} + +// tslint:disable-next-line:max-classes-per-file +interface IMetric { + End: string; + Error?: string; + Id?: string; + Name: string; + Start: string; +} + +// tslint:disable-next-line:max-classes-per-file +export class ServiceTelemetryListener implements IEventListener { + private privIsDisposed: boolean = false; + + private privRequestId: string; + private privAudioSourceId: string; + private privAudioNodeId: string; + + private privListeningTriggerMetric: IMetric = null; + private privMicMetric: IMetric = null; + private privConnectionEstablishMetric: IMetric = null; + + private privMicStartTime: string; + + private privConnectionId: string; + private privConnectionStartTime: string; + + private privReceivedMessages: IStringDictionary; + + constructor(requestId: string, audioSourceId: string, audioNodeId: string) { + this.privRequestId = requestId; + this.privAudioSourceId = audioSourceId; + this.privAudioNodeId = audioNodeId; + + this.privReceivedMessages = {}; + } + + public onEvent = (e: PlatformEvent): void => { + if (this.privIsDisposed) { + return; + } + + if (e instanceof RecognitionTriggeredEvent && e.requestId === this.privRequestId) { + this.privListeningTriggerMetric = { + End: e.eventTime, + Name: "ListeningTrigger", + Start: e.eventTime, + }; + } + + if (e instanceof AudioStreamNodeAttachingEvent && e.audioSourceId === this.privAudioSourceId && e.audioNodeId === this.privAudioNodeId) { + this.privMicStartTime = e.eventTime; + } + + if (e instanceof AudioStreamNodeAttachedEvent && e.audioSourceId === this.privAudioSourceId && e.audioNodeId === this.privAudioNodeId) { + this.privMicStartTime = e.eventTime; + } + + if (e instanceof AudioSourceErrorEvent && e.audioSourceId === this.privAudioSourceId) { + if (!this.privMicMetric) { + this.privMicMetric = { + End: e.eventTime, + Error: e.error, + Name: "Microphone", + Start: this.privMicStartTime, + }; + } + } + + if (e instanceof AudioStreamNodeErrorEvent && e.audioSourceId === this.privAudioSourceId && e.audioNodeId === this.privAudioNodeId) { + if (!this.privMicMetric) { + this.privMicMetric = { + End: e.eventTime, + Error: e.error, + Name: "Microphone", + Start: this.privMicStartTime, + }; + } + } + + if (e instanceof AudioStreamNodeDetachedEvent && e.audioSourceId === this.privAudioSourceId && e.audioNodeId === this.privAudioNodeId) { + if (!this.privMicMetric) { + this.privMicMetric = { + End: e.eventTime, + Name: "Microphone", + Start: this.privMicStartTime, + }; + } + } + + if (e instanceof ConnectingToServiceEvent && e.requestId === this.privRequestId) { + this.privConnectionId = e.sessionId; + } + + if (e instanceof ConnectionStartEvent && e.connectionId === this.privConnectionId) { + this.privConnectionStartTime = e.eventTime; + } + + if (e instanceof ConnectionEstablishedEvent && e.connectionId === this.privConnectionId) { + if (!this.privConnectionEstablishMetric) { + this.privConnectionEstablishMetric = { + End: e.eventTime, + Id: this.privConnectionId, + Name: "Connection", + Start: this.privConnectionStartTime, + }; + } + } + + if (e instanceof ConnectionEstablishErrorEvent && e.connectionId === this.privConnectionId) { + if (!this.privConnectionEstablishMetric) { + this.privConnectionEstablishMetric = { + End: e.eventTime, + Error: this.getConnectionError(e.statusCode), + Id: this.privConnectionId, + Name: "Connection", + Start: this.privConnectionStartTime, + }; + } + } + + if (e instanceof ConnectionMessageReceivedEvent && e.connectionId === this.privConnectionId) { + if (e.message && e.message.headers && e.message.headers.path) { + if (!this.privReceivedMessages[e.message.headers.path]) { + this.privReceivedMessages[e.message.headers.path] = new Array(); + } + + this.privReceivedMessages[e.message.headers.path].push(e.networkReceivedTime); + } + } + } + + public getTelemetry = (): string => { + const metrics = new Array(); + + if (this.privListeningTriggerMetric) { + metrics.push(this.privListeningTriggerMetric); + } + + if (this.privMicMetric) { + metrics.push(this.privMicMetric); + } + + if (this.privConnectionEstablishMetric) { + metrics.push(this.privConnectionEstablishMetric); + } + + const telemetry: ITelemetry = { + Metrics: metrics, + ReceivedMessages: this.privReceivedMessages, + }; + + const json = JSON.stringify(telemetry); + + // We dont want to send the same telemetry again. So clean those out. + this.privReceivedMessages = {}; + this.privListeningTriggerMetric = null; + this.privMicMetric = null; + this.privConnectionEstablishMetric = null; + + return json; + } + + public dispose = (): void => { + this.privIsDisposed = true; + } + + private getConnectionError = (statusCode: number): string => { + /* + -- Websocket status codes -- + NormalClosure = 1000, + EndpointUnavailable = 1001, + ProtocolError = 1002, + InvalidMessageType = 1003, + Empty = 1005, + InvalidPayloadData = 1007, + PolicyViolation = 1008, + MessageTooBig = 1009, + MandatoryExtension = 1010, + InternalServerError = 1011 + */ + + switch (statusCode) { + case 400: + case 1002: + case 1003: + case 1005: + case 1007: + case 1008: + case 1009: return "BadRequest"; + case 401: return "Unauthorized"; + case 403: return "Forbidden"; + case 503: + case 1001: return "ServerUnavailable"; + case 500: + case 1011: return "ServerError"; + case 408: + case 504: return "Timeout"; + default: return "statuscode:" + statusCode.toString(); + } + } +} diff --git a/src/common.speech/SpeechConnectionFactory.ts b/src/common.speech/SpeechConnectionFactory.ts new file mode 100644 index 00000000..8bb7d54f --- /dev/null +++ b/src/common.speech/SpeechConnectionFactory.ts @@ -0,0 +1,86 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +import { WebsocketConnection } from "../common.browser/Exports"; +import { OutputFormatPropertyName } from "../common.speech/Exports"; +import { IConnection, IStringDictionary, Storage } from "../common/Exports"; +import { OutputFormat, PropertyId } from "../sdk/Exports"; +import { AuthInfo, IConnectionFactory, RecognitionMode, RecognizerConfig, WebsocketMessageFormatter } from "./Exports"; +import { QueryParameterNames } from "./QueryParameterNames"; + +export class SpeechConnectionFactory implements IConnectionFactory { + + public create = ( + config: RecognizerConfig, + authInfo: AuthInfo, + connectionId?: string): IConnection => { + + let endpoint: string = config.parameters.getProperty(PropertyId.SpeechServiceConnection_Endpoint, undefined); + + const queryParams: IStringDictionary = {}; + + const endpointId: string = config.parameters.getProperty(PropertyId.SpeechServiceConnection_EndpointId, undefined); + const language: string = config.parameters.getProperty(PropertyId.SpeechServiceConnection_RecoLanguage, undefined); + + if (endpointId) { + if (!endpoint || endpoint.search(QueryParameterNames.DeploymentIdParamName) === -1) { + queryParams[QueryParameterNames.DeploymentIdParamName] = endpointId; + } + } else if (language) { + if (!endpoint || endpoint.search(QueryParameterNames.LanguageParamName) === -1) { + queryParams[QueryParameterNames.LanguageParamName] = language; + } + } + + if (!endpoint || endpoint.search(QueryParameterNames.FormatParamName) === -1) { + queryParams[QueryParameterNames.FormatParamName] = config.parameters.getProperty(OutputFormatPropertyName, OutputFormat[OutputFormat.Simple]).toLowerCase(); + } + + if (this.isDebugModeEnabled) { + queryParams[QueryParameterNames.TestHooksParamName] = "1"; + } + + if (!endpoint) { + const region: string = config.parameters.getProperty(PropertyId.SpeechServiceConnection_Region, undefined); + + switch (config.recognitionMode) { + case RecognitionMode.Conversation: + endpoint = this.host(region) + this.conversationRelativeUri; + break; + case RecognitionMode.Dictation: + endpoint = this.host(region) + this.dictationRelativeUri; + break; + default: + endpoint = this.host(region) + this.interactiveRelativeUri; // default is interactive + break; + } + } + + const headers: IStringDictionary = {}; + headers[authInfo.headerName] = authInfo.token; + headers[QueryParameterNames.ConnectionIdHeader] = connectionId; + + return new WebsocketConnection(endpoint, queryParams, headers, new WebsocketMessageFormatter(), connectionId); + } + + private host(region: string): string { + return Storage.local.getOrAdd("Host", "wss://" + region + ".stt.speech.microsoft.com"); + } + + private get interactiveRelativeUri(): string { + return Storage.local.getOrAdd("InteractiveRelativeUri", "/speech/recognition/interactive/cognitiveservices/v1"); + } + + private get conversationRelativeUri(): string { + return Storage.local.getOrAdd("ConversationRelativeUri", "/speech/recognition/conversation/cognitiveservices/v1"); + } + + private get dictationRelativeUri(): string { + return Storage.local.getOrAdd("DictationRelativeUri", "/speech/recognition/dictation/cognitiveservices/v1"); + } + + private get isDebugModeEnabled(): boolean { + const value = Storage.local.getOrAdd("IsDebugModeEnabled", "false"); + return value.toLowerCase() === "true"; + } +} diff --git a/src/common.speech/SpeechConnectionMessage.Internal.ts b/src/common.speech/SpeechConnectionMessage.Internal.ts new file mode 100644 index 00000000..e4873bce --- /dev/null +++ b/src/common.speech/SpeechConnectionMessage.Internal.ts @@ -0,0 +1,114 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +import { ArgumentNullError, ConnectionMessage, IStringDictionary, MessageType } from "../common/Exports"; + +const PathHeaderName: string = "path"; +const ContentTypeHeaderName: string = "content-type"; +const RequestIdHeaderName: string = "x-requestid"; +const RequestTimestampHeaderName: string = "x-timestamp"; + +export class SpeechConnectionMessage extends ConnectionMessage { + + private privPath: string; + private privRequestId: string; + private privContentType: string; + private privAdditionalHeaders: IStringDictionary; + + public constructor( + messageType: MessageType, + path: string, + requestId: string, + contentType: string, + body: any, + additionalHeaders?: IStringDictionary, + id?: string) { + + if (!path) { + throw new ArgumentNullError("path"); + } + + if (!requestId) { + throw new ArgumentNullError("requestId"); + } + + const headers: IStringDictionary = {}; + headers[PathHeaderName] = path; + headers[RequestIdHeaderName] = requestId; + headers[RequestTimestampHeaderName] = new Date().toISOString(); + if (contentType) { + headers[ContentTypeHeaderName] = contentType; + } + + if (additionalHeaders) { + for (const headerName in additionalHeaders) { + if (headerName) { + headers[headerName] = additionalHeaders[headerName]; + } + + } + } + + if (id) { + super(messageType, body, headers, id); + } else { + super(messageType, body, headers); + } + + this.privPath = path; + this.privRequestId = requestId; + this.privContentType = contentType; + this.privAdditionalHeaders = additionalHeaders; + } + + public get path(): string { + return this.privPath; + } + + public get requestId(): string { + return this.privRequestId; + } + + public get contentType(): string { + return this.privContentType; + } + + public get additionalHeaders(): IStringDictionary { + return this.privAdditionalHeaders; + } + + public static fromConnectionMessage = (message: ConnectionMessage): SpeechConnectionMessage => { + let path = null; + let requestId = null; + let contentType = null; + let requestTimestamp = null; + const additionalHeaders: IStringDictionary = {}; + + if (message.headers) { + for (const headerName in message.headers) { + if (headerName) { + if (headerName.toLowerCase() === PathHeaderName.toLowerCase()) { + path = message.headers[headerName]; + } else if (headerName.toLowerCase() === RequestIdHeaderName.toLowerCase()) { + requestId = message.headers[headerName]; + } else if (headerName.toLowerCase() === RequestTimestampHeaderName.toLowerCase()) { + requestTimestamp = message.headers[headerName]; + } else if (headerName.toLowerCase() === ContentTypeHeaderName.toLowerCase()) { + contentType = message.headers[headerName]; + } else { + additionalHeaders[headerName] = message.headers[headerName]; + } + } + } + } + + return new SpeechConnectionMessage( + message.messageType, + path, + requestId, + contentType, + message.body, + additionalHeaders, + message.id); + } +} diff --git a/src/common.speech/SpeechServiceInterfaces.ts b/src/common.speech/SpeechServiceInterfaces.ts new file mode 100644 index 00000000..ad493e83 --- /dev/null +++ b/src/common.speech/SpeechServiceInterfaces.ts @@ -0,0 +1,34 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +import { RecognitionCompletionStatus } from "../../src/common.speech/Exports"; +import { TranslationStatus } from "./TranslationStatus"; + +export interface ITranslations { + TranslationStatus: TranslationStatus; + Translations: ITranslation[]; + FailureReason: string; +} + +export interface ITranslation { + Language: string; + Text: string; +} + +export interface ISpeechEndDetectedResult { + Offset?: number; +} + +// turn.start +export interface ITurnStart { + context: ITurnStartContext; +} + +export interface ITurnStartContext { + serviceTag: string; +} + +export interface IResultErrorDetails { + errorText: string; + recogSate: RecognitionCompletionStatus; +} diff --git a/src/common.speech/SpeechServiceRecognizer.ts b/src/common.speech/SpeechServiceRecognizer.ts new file mode 100644 index 00000000..2ee83277 --- /dev/null +++ b/src/common.speech/SpeechServiceRecognizer.ts @@ -0,0 +1,219 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +import { IAudioSource, IConnection } from "../common/Exports"; +import { + CancellationErrorCode, + CancellationReason, + OutputFormat, + PropertyCollection, + ResultReason, + SpeechRecognitionCanceledEventArgs, + SpeechRecognitionEventArgs, + SpeechRecognitionResult, + SpeechRecognizer, +} from "../sdk/Exports"; +import { + CancellationErrorCodePropertyName, + DetailedSpeechPhrase, + EnumTranslation, + OutputFormatPropertyName, + RecognitionStatus, + RequestSession, + ServiceRecognizerBase, + SimpleSpeechPhrase, + SpeechHypothesis, +} from "./Exports"; +import { IAuthentication } from "./IAuthentication"; +import { IConnectionFactory } from "./IConnectionFactory"; +import { RecognizerConfig } from "./RecognizerConfig"; +import { SpeechConnectionMessage } from "./SpeechConnectionMessage.Internal"; + +// tslint:disable-next-line:max-classes-per-file +export class SpeechServiceRecognizer extends ServiceRecognizerBase { + + private privSpeechRecognizer: SpeechRecognizer; + + public constructor( + authentication: IAuthentication, + connectionFactory: IConnectionFactory, + audioSource: IAudioSource, + recognizerConfig: RecognizerConfig, + speechRecognizer: SpeechRecognizer) { + super(authentication, connectionFactory, audioSource, recognizerConfig, speechRecognizer); + this.privSpeechRecognizer = speechRecognizer; + } + + protected processTypeSpecificMessages( + connectionMessage: SpeechConnectionMessage, + requestSession: RequestSession, + connection: IConnection, + successCallback?: (e: SpeechRecognitionResult) => void, + errorCallBack?: (e: string) => void): void { + + let result: SpeechRecognitionResult; + + switch (connectionMessage.path.toLowerCase()) { + case "speech.hypothesis": + const hypothesis: SpeechHypothesis = SpeechHypothesis.fromJSON(connectionMessage.textBody); + + result = new SpeechRecognitionResult( + requestSession.requestId, + ResultReason.RecognizingSpeech, + hypothesis.Text, + hypothesis.Duration, + hypothesis.Offset + requestSession.currentTurnAudioOffset, + undefined, + connectionMessage.textBody, + undefined); + + const ev = new SpeechRecognitionEventArgs(result, hypothesis.Duration, requestSession.sessionId); + + if (!!this.privSpeechRecognizer.recognizing) { + try { + this.privSpeechRecognizer.recognizing(this.privSpeechRecognizer, ev); + /* tslint:disable:no-empty */ + } catch (error) { + // Not going to let errors in the event handler + // trip things up. + } + } + break; + case "speech.phrase": + // Always send telemetry because we want it to to up for recognize once which will listening to the service + // after recognition happens. + this.sendTelemetryData(requestSession, requestSession.getTelemetry()); + + const simple: SimpleSpeechPhrase = SimpleSpeechPhrase.fromJSON(connectionMessage.textBody); + const resultReason: ResultReason = EnumTranslation.implTranslateRecognitionResult(simple.RecognitionStatus); + + requestSession.onServiceRecognized(requestSession.currentTurnAudioOffset + simple.Offset); + + if (ResultReason.Canceled === resultReason) { + const cancelReason: CancellationReason = EnumTranslation.implTranslateCancelResult(simple.RecognitionStatus); + + result = new SpeechRecognitionResult( + requestSession.requestId, + resultReason, + undefined, + undefined, + undefined, + undefined, + connectionMessage.textBody, + undefined); + + if (!!this.privSpeechRecognizer.canceled) { + const cancelEvent: SpeechRecognitionCanceledEventArgs = new SpeechRecognitionCanceledEventArgs( + cancelReason, + undefined, + cancelReason === CancellationReason.Error ? CancellationErrorCode.ServiceError : CancellationErrorCode.NoError, + undefined, + requestSession.sessionId); + try { + this.privSpeechRecognizer.canceled(this.privSpeechRecognizer, cancelEvent); + /* tslint:disable:no-empty */ + } catch { } + } + } else { + if (!(requestSession.isSpeechEnded && resultReason === ResultReason.NoMatch && simple.RecognitionStatus !== RecognitionStatus.InitialSilenceTimeout)) { + if (this.privRecognizerConfig.parameters.getProperty(OutputFormatPropertyName) === OutputFormat[OutputFormat.Simple]) { + result = new SpeechRecognitionResult( + requestSession.requestId, + resultReason, + simple.DisplayText, + simple.Duration, + simple.Offset + requestSession.currentTurnAudioOffset, + undefined, + connectionMessage.textBody, + undefined); + } else { + const detailed: DetailedSpeechPhrase = DetailedSpeechPhrase.fromJSON(connectionMessage.textBody); + + result = new SpeechRecognitionResult( + requestSession.requestId, + resultReason, + detailed.RecognitionStatus === RecognitionStatus.Success ? detailed.NBest[0].Display : undefined, + detailed.Duration, + detailed.Offset + requestSession.currentTurnAudioOffset, + undefined, + connectionMessage.textBody, + undefined); + } + + const event: SpeechRecognitionEventArgs = new SpeechRecognitionEventArgs(result, result.offset, requestSession.sessionId); + + if (!!this.privSpeechRecognizer.recognized) { + try { + this.privSpeechRecognizer.recognized(this.privSpeechRecognizer, event); + /* tslint:disable:no-empty */ + } catch (error) { + // Not going to let errors in the event handler + // trip things up. + } + } + } + } + + // report result to promise. + if (!!successCallback) { + try { + successCallback(result); + } catch (e) { + if (!!errorCallBack) { + errorCallBack(e); + } + } + // Only invoke the call back once. + // and if it's successful don't invoke the + // error after that. + successCallback = undefined; + errorCallBack = undefined; + } + break; + default: + break; + } + } + + // Cancels recognition. + protected cancelRecognition( + sessionId: string, + requestId: string, + cancellationReason: CancellationReason, + errorCode: CancellationErrorCode, + error: string, + cancelRecoCallback: (e: SpeechRecognitionResult) => void): void { + + const properties: PropertyCollection = new PropertyCollection(); + properties.setProperty(CancellationErrorCodePropertyName, CancellationErrorCode[errorCode]); + + if (!!this.privSpeechRecognizer.canceled) { + const cancelEvent: SpeechRecognitionCanceledEventArgs = new SpeechRecognitionCanceledEventArgs( + cancellationReason, + error, + errorCode, + undefined, + sessionId); + try { + this.privSpeechRecognizer.canceled(this.privSpeechRecognizer, cancelEvent); + /* tslint:disable:no-empty */ + } catch { } + } + + if (!!cancelRecoCallback) { + const result: SpeechRecognitionResult = new SpeechRecognitionResult( + requestId, + ResultReason.Canceled, + undefined, // Text + undefined, // Druation + undefined, // Offset + error, + undefined, // Json + properties); + try { + cancelRecoCallback(result); + /* tslint:disable:no-empty */ + } catch { } + } + } +} diff --git a/src/common.speech/TranslationConnectionFactory.ts b/src/common.speech/TranslationConnectionFactory.ts new file mode 100644 index 00000000..c00aee8d --- /dev/null +++ b/src/common.speech/TranslationConnectionFactory.ts @@ -0,0 +1,58 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +import { WebsocketConnection } from "../common.browser/Exports"; +import { IConnection, IStringDictionary, Storage } from "../common/Exports"; +import { PropertyId } from "../sdk/Exports"; +import { AuthInfo, IConnectionFactory, RecognizerConfig, WebsocketMessageFormatter } from "./Exports"; + +const TestHooksParamName: string = "testhooks"; +const ConnectionIdHeader: string = "X-ConnectionId"; + +export class TranslationConnectionFactory implements IConnectionFactory { + + public create = ( + config: RecognizerConfig, + authInfo: AuthInfo, + connectionId?: string): IConnection => { + + let endpoint: string = config.parameters.getProperty(PropertyId.SpeechServiceConnection_Endpoint, undefined); + if (!endpoint) { + const region: string = config.parameters.getProperty(PropertyId.SpeechServiceConnection_Region, undefined); + + endpoint = this.host(region) + Storage.local.getOrAdd("TranslationRelativeUri", "/speech/translation/cognitiveservices/v1"); + } + + const queryParams: IStringDictionary = { + from: config.parameters.getProperty(PropertyId.SpeechServiceConnection_RecoLanguage), + to: config.parameters.getProperty(PropertyId.SpeechServiceConnection_TranslationToLanguages), + }; + + if (this.isDebugModeEnabled) { + queryParams[TestHooksParamName] = "1"; + } + + const voiceName: string = "voice"; + const featureName: string = "features"; + + if (config.parameters.getProperty(PropertyId.SpeechServiceConnection_TranslationVoice, undefined) !== undefined) { + queryParams[voiceName] = config.parameters.getProperty(PropertyId.SpeechServiceConnection_TranslationVoice); + queryParams[featureName] = "texttospeech"; + } + + const headers: IStringDictionary = {}; + headers[authInfo.headerName] = authInfo.token; + headers[ConnectionIdHeader] = connectionId; + + return new WebsocketConnection(endpoint, queryParams, headers, new WebsocketMessageFormatter(), connectionId); + } + + private host(region: string): string { + return Storage.local.getOrAdd("Host", "wss://" + region + ".s2s.speech.microsoft.com"); + } + + private get isDebugModeEnabled(): boolean { + const value = Storage.local.getOrAdd("IsDebugModeEnabled", "false"); + return value.toLowerCase() === "true"; + } +} diff --git a/src/common.speech/TranslationServiceRecognizer.ts b/src/common.speech/TranslationServiceRecognizer.ts new file mode 100644 index 00000000..98451028 --- /dev/null +++ b/src/common.speech/TranslationServiceRecognizer.ts @@ -0,0 +1,326 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +import { IAudioSource, IConnection, TranslationStatus } from "../common/Exports"; +import { + CancellationErrorCode, + CancellationReason, + PropertyCollection, + ResultReason, + SpeechRecognitionResult, + TranslationRecognitionCanceledEventArgs, + TranslationRecognitionEventArgs, + TranslationRecognitionResult, + TranslationRecognizer, + Translations, + TranslationSynthesisEventArgs, + TranslationSynthesisResult, +} from "../sdk/Exports"; +import { + CancellationErrorCodePropertyName, + EnumTranslation, + RecognitionStatus, + RequestSession, + ServiceRecognizerBase, + SynthesisStatus, + TranslationHypothesis, + TranslationPhrase, + TranslationSynthesisEnd, +} from "./Exports"; +import { IAuthentication } from "./IAuthentication"; +import { IConnectionFactory } from "./IConnectionFactory"; +import { RecognizerConfig } from "./RecognizerConfig"; +import { SpeechConnectionMessage } from "./SpeechConnectionMessage.Internal"; + +// tslint:disable-next-line:max-classes-per-file +export class TranslationServiceRecognizer extends ServiceRecognizerBase { + private privTranslationRecognizer: TranslationRecognizer; + + public constructor( + authentication: IAuthentication, + connectionFactory: IConnectionFactory, + audioSource: IAudioSource, + recognizerConfig: RecognizerConfig, + translationRecognizer: TranslationRecognizer) { + + super(authentication, connectionFactory, audioSource, recognizerConfig, translationRecognizer); + this.privTranslationRecognizer = translationRecognizer; + + } + + protected processTypeSpecificMessages( + connectionMessage: SpeechConnectionMessage, + requestSession: RequestSession, + connection: IConnection, + successCallback?: (e: TranslationRecognitionResult) => void, + errorCallBack?: (e: string) => void): void { + + switch (connectionMessage.path.toLowerCase()) { + case "translation.hypothesis": + + const result: TranslationRecognitionEventArgs = this.fireEventForResult(TranslationHypothesis.fromJSON(connectionMessage.textBody), requestSession); + + if (!!this.privTranslationRecognizer.recognizing) { + try { + this.privTranslationRecognizer.recognizing(this.privTranslationRecognizer, result); + /* tslint:disable:no-empty */ + } catch (error) { + // Not going to let errors in the event handler + // trip things up. + } + } + + break; + case "translation.phrase": + if (this.privRecognizerConfig.isContinuousRecognition) { + // For continuous recognition telemetry has to be sent for every phrase as per spec. + this.sendTelemetryData(requestSession, requestSession.getTelemetry()); + } + + const translatedPhrase: TranslationPhrase = TranslationPhrase.fromJSON(connectionMessage.textBody); + + if (translatedPhrase.RecognitionStatus === RecognitionStatus.Success) { + // OK, the recognition was successful. How'd the translation do? + const result: TranslationRecognitionEventArgs = this.fireEventForResult(translatedPhrase, requestSession); + if (!!this.privTranslationRecognizer.recognized) { + try { + this.privTranslationRecognizer.recognized(this.privTranslationRecognizer, result); + /* tslint:disable:no-empty */ + } catch (error) { + // Not going to let errors in the event handler + // trip things up. + } + } + + // report result to promise. + if (!!successCallback) { + try { + successCallback(result.result); + } catch (e) { + if (!!errorCallBack) { + errorCallBack(e); + } + } + // Only invoke the call back once. + // and if it's successful don't invoke the + // error after that. + successCallback = undefined; + errorCallBack = undefined; + } + + break; + } else { + const reason: ResultReason = EnumTranslation.implTranslateRecognitionResult(translatedPhrase.RecognitionStatus); + + const result = new TranslationRecognitionResult( + undefined, + requestSession.requestId, + reason, + translatedPhrase.Text, + translatedPhrase.Duration, + translatedPhrase.Offset, + undefined, + connectionMessage.textBody, + undefined); + + if (reason === ResultReason.Canceled) { + const cancelReason: CancellationReason = EnumTranslation.implTranslateCancelResult(translatedPhrase.RecognitionStatus); + + const ev = new TranslationRecognitionCanceledEventArgs( + requestSession.sessionId, + cancelReason, + null, + cancelReason === CancellationReason.Error ? CancellationErrorCode.ServiceError : CancellationErrorCode.NoError, + result); + + if (!!this.privTranslationRecognizer.canceled) { + try { + this.privTranslationRecognizer.canceled(this.privTranslationRecognizer, ev); + /* tslint:disable:no-empty */ + } catch (error) { + // Not going to let errors in the event handler + // trip things up. + } + } + } else { + if (!(requestSession.isSpeechEnded && reason === ResultReason.NoMatch && translatedPhrase.RecognitionStatus !== RecognitionStatus.InitialSilenceTimeout)) { + const ev = new TranslationRecognitionEventArgs(result, 0/*offset*/, requestSession.sessionId); + + if (!!this.privTranslationRecognizer.recognized) { + try { + this.privTranslationRecognizer.recognized(this.privTranslationRecognizer, ev); + /* tslint:disable:no-empty */ + } catch (error) { + // Not going to let errors in the event handler + // trip things up. + } + } + } + } + + // report result to promise. + if (!!successCallback) { + try { + successCallback(result); + } catch (e) { + if (!!errorCallBack) { + errorCallBack(e); + } + } + // Only invoke the call back once. + // and if it's successful don't invoke the + // error after that. + successCallback = undefined; + errorCallBack = undefined; + } + } + break; + + case "translation.synthesis": + this.sendSynthesisAudio(connectionMessage.binaryBody, requestSession.sessionId); + break; + + case "translation.synthesis.end": + const synthEnd: TranslationSynthesisEnd = TranslationSynthesisEnd.fromJSON(connectionMessage.textBody); + + switch (synthEnd.SynthesisStatus) { + case SynthesisStatus.Error: + if (!!this.privTranslationRecognizer.synthesizing) { + const result = new TranslationSynthesisResult(ResultReason.Canceled, undefined); + const retEvent: TranslationSynthesisEventArgs = new TranslationSynthesisEventArgs(result, requestSession.sessionId); + + try { + this.privTranslationRecognizer.synthesizing(this.privTranslationRecognizer, retEvent); + /* tslint:disable:no-empty */ + } catch (error) { + // Not going to let errors in the event handler + // trip things up. + } + } + + if (!!this.privTranslationRecognizer.canceled) { + // And raise a canceled event to send the rich(er) error message back. + const canceledResult: TranslationRecognitionCanceledEventArgs = new TranslationRecognitionCanceledEventArgs( + requestSession.sessionId, + CancellationReason.Error, + synthEnd.FailureReason, + CancellationErrorCode.ServiceError, + null); + + try { + this.privTranslationRecognizer.canceled(this.privTranslationRecognizer, canceledResult); + /* tslint:disable:no-empty */ + } catch (error) { + // Not going to let errors in the event handler + // trip things up. + } + } + break; + case SynthesisStatus.Success: + this.sendSynthesisAudio(undefined, requestSession.sessionId); + break; + default: + break; + } + break; + default: + break; + } + } + + // Cancels recognition. + protected cancelRecognition( + sessionId: string, + requestId: string, + cancellationReason: CancellationReason, + errorCode: CancellationErrorCode, + error: string, + cancelRecoCallback: (e: SpeechRecognitionResult) => void): void { + if (!!this.privTranslationRecognizer.canceled) { + const properties: PropertyCollection = new PropertyCollection(); + properties.setProperty(CancellationErrorCodePropertyName, CancellationErrorCode[errorCode]); + + const cancelEvent: TranslationRecognitionCanceledEventArgs = new TranslationRecognitionCanceledEventArgs( + sessionId, + cancellationReason, + error, + errorCode, + undefined); + + try { + this.privTranslationRecognizer.canceled(this.privTranslationRecognizer, cancelEvent); + /* tslint:disable:no-empty */ + } catch { } + + if (!!cancelRecoCallback) { + const result: TranslationRecognitionResult = new TranslationRecognitionResult( + undefined, // Translations + requestId, + ResultReason.Canceled, + undefined, // Text + undefined, // Druation + undefined, // Offset + error, + undefined, // Json + properties); + try { + cancelRecoCallback(result); + /* tslint:disable:no-empty */ + } catch { } + } + } + } + + private fireEventForResult(serviceResult: TranslationHypothesis | TranslationPhrase, requestSession: RequestSession): TranslationRecognitionEventArgs { + let translations: Translations; + + if (undefined !== serviceResult.Translation.Translations) { + translations = new Translations(); + for (const translation of serviceResult.Translation.Translations) { + translations.set(translation.Language, translation.Text); + } + } + + let resultReason: ResultReason; + if (serviceResult instanceof TranslationPhrase) { + if (serviceResult.Translation.TranslationStatus === TranslationStatus.Success) { + resultReason = ResultReason.TranslatedSpeech; + } else { + resultReason = ResultReason.RecognizedSpeech; + } + } else { + resultReason = ResultReason.TranslatingSpeech; + } + + const result = new TranslationRecognitionResult( + translations, + requestSession.requestId, + resultReason, + serviceResult.Text, + serviceResult.Duration, + serviceResult.Offset, + serviceResult.Translation.FailureReason, + JSON.stringify(serviceResult), + undefined); + + const ev = new TranslationRecognitionEventArgs(result, serviceResult.Offset, requestSession.sessionId); + return ev; + } + + private sendSynthesisAudio(audio: ArrayBuffer, sessionId: string): void { + const reason = (undefined === audio) ? ResultReason.SynthesizingAudioCompleted : ResultReason.SynthesizingAudio; + const result = new TranslationSynthesisResult(reason, audio); + const retEvent: TranslationSynthesisEventArgs = new TranslationSynthesisEventArgs(result, sessionId); + + if (!!this.privTranslationRecognizer.synthesizing) { + try { + this.privTranslationRecognizer.synthesizing(this.privTranslationRecognizer, retEvent); + /* tslint:disable:no-empty */ + } catch (error) { + // Not going to let errors in the event handler + // trip things up. + } + } + + } +} diff --git a/src/common.speech/TranslationStatus.ts b/src/common.speech/TranslationStatus.ts new file mode 100644 index 00000000..6b4b6457 --- /dev/null +++ b/src/common.speech/TranslationStatus.ts @@ -0,0 +1,18 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +/** + * Defines translation status. + * @class TranslationStatus + */ +export enum TranslationStatus { + /** + * @member TranslationStatus.Success + */ + Success = 0, + + /** + * @member TranslationStatus.Error + */ + Error, +} diff --git a/src/common.speech/WebsocketMessageFormatter.ts b/src/common.speech/WebsocketMessageFormatter.ts new file mode 100644 index 00000000..56f70ce5 --- /dev/null +++ b/src/common.speech/WebsocketMessageFormatter.ts @@ -0,0 +1,160 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +import { + ConnectionMessage, + Deferred, + IStringDictionary, + IWebsocketMessageFormatter, + MessageType, + Promise, + RawWebsocketMessage, +} from "../common/Exports"; + +const CRLF: string = "\r\n"; + +export class WebsocketMessageFormatter implements IWebsocketMessageFormatter { + + public toConnectionMessage = (message: RawWebsocketMessage): Promise => { + const deferral = new Deferred(); + + try { + if (message.messageType === MessageType.Text) { + const textMessage: string = message.textContent; + let headers: IStringDictionary = {}; + let body: string = null; + + if (textMessage) { + const headerBodySplit = textMessage.split("\r\n\r\n"); + if (headerBodySplit && headerBodySplit.length > 0) { + headers = this.parseHeaders(headerBodySplit[0]); + if (headerBodySplit.length > 1) { + body = headerBodySplit[1]; + } + } + } + + deferral.resolve(new ConnectionMessage(message.messageType, body, headers, message.id)); + } else if (message.messageType === MessageType.Binary) { + const binaryMessage: ArrayBuffer = message.binaryContent; + let headers: IStringDictionary = {}; + let body: ArrayBuffer = null; + + if (!binaryMessage || binaryMessage.byteLength < 2) { + throw new Error("Invalid binary message format. Header length missing."); + } + + const dataView = new DataView(binaryMessage); + const headerLength = dataView.getInt16(0); + + if (binaryMessage.byteLength < headerLength + 2) { + throw new Error("Invalid binary message format. Header content missing."); + } + + let headersString = ""; + for (let i = 0; i < headerLength; i++) { + headersString += String.fromCharCode((dataView).getInt8(i + 2)); + } + + headers = this.parseHeaders(headersString); + + if (binaryMessage.byteLength > headerLength + 2) { + body = binaryMessage.slice(2 + headerLength); + } + + deferral.resolve(new ConnectionMessage(message.messageType, body, headers, message.id)); + } + } catch (e) { + deferral.reject(`Error formatting the message. Error: ${e}`); + } + + return deferral.promise(); + } + + public fromConnectionMessage = (message: ConnectionMessage): Promise => { + const deferral = new Deferred(); + + try { + if (message.messageType === MessageType.Text) { + const payload = `${this.makeHeaders(message)}${CRLF}${message.textBody ? message.textBody : ""}`; + + deferral.resolve(new RawWebsocketMessage(MessageType.Text, payload, message.id)); + + } else if (message.messageType === MessageType.Binary) { + const headersString = this.makeHeaders(message); + const content = message.binaryBody; + + const headerInt8Array = new Int8Array(this.stringToArrayBuffer(headersString)); + + const payload = new ArrayBuffer(2 + headerInt8Array.byteLength + (content ? content.byteLength : 0)); + const dataView = new DataView(payload); + + dataView.setInt16(0, headerInt8Array.length); + + for (let i = 0; i < headerInt8Array.byteLength; i++) { + dataView.setInt8(2 + i, headerInt8Array[i]); + } + + if (content) { + const bodyInt8Array = new Int8Array(content); + for (let i = 0; i < bodyInt8Array.byteLength; i++) { + dataView.setInt8(2 + headerInt8Array.byteLength + i, bodyInt8Array[i]); + } + } + + deferral.resolve(new RawWebsocketMessage(MessageType.Binary, payload, message.id)); + } + } catch (e) { + deferral.reject(`Error formatting the message. ${e}`); + } + + return deferral.promise(); + } + + private makeHeaders = (message: ConnectionMessage): string => { + let headersString: string = ""; + + if (message.headers) { + for (const header in message.headers) { + if (header) { + headersString += `${header}: ${message.headers[header]}${CRLF}`; + } + } + } + + return headersString; + } + + private parseHeaders = (headersString: string): IStringDictionary => { + const headers: IStringDictionary = {}; + + if (headersString) { + const headerMatches = headersString.match(/[^\r\n]+/g); + if (headers) { + for (const header of headerMatches) { + if (header) { + const separatorIndex = header.indexOf(":"); + const headerName = separatorIndex > 0 ? header.substr(0, separatorIndex).trim().toLowerCase() : header; + const headerValue = + separatorIndex > 0 && header.length > (separatorIndex + 1) ? + header.substr(separatorIndex + 1).trim() : + ""; + + headers[headerName] = headerValue; + } + } + } + } + + return headers; + } + + private stringToArrayBuffer = (str: string): ArrayBuffer => { + const buffer = new ArrayBuffer(str.length); + const view = new DataView(buffer); + for (let i = 0; i < str.length; i++) { + view.setUint8(i, str.charCodeAt(i)); + } + return buffer; + } +} diff --git a/src/common/AudioSourceEvents.ts b/src/common/AudioSourceEvents.ts new file mode 100644 index 00000000..37e8a6f7 --- /dev/null +++ b/src/common/AudioSourceEvents.ts @@ -0,0 +1,101 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +import { EventType, PlatformEvent } from "./PlatformEvent"; + +export class AudioSourceEvent extends PlatformEvent { + private privAudioSourceId: string; + + constructor(eventName: string, audioSourceId: string, eventType: EventType = EventType.Info) { + super(eventName, eventType); + this.privAudioSourceId = audioSourceId; + } + + public get audioSourceId(): string { + return this.privAudioSourceId; + } +} + +// tslint:disable-next-line:max-classes-per-file +export class AudioSourceInitializingEvent extends AudioSourceEvent { + constructor(audioSourceId: string) { + super("AudioSourceInitializingEvent", audioSourceId); + } +} + +// tslint:disable-next-line:max-classes-per-file +export class AudioSourceReadyEvent extends AudioSourceEvent { + constructor(audioSourceId: string) { + super("AudioSourceReadyEvent", audioSourceId); + } +} + +// tslint:disable-next-line:max-classes-per-file +export class AudioSourceOffEvent extends AudioSourceEvent { + constructor(audioSourceId: string) { + super("AudioSourceOffEvent", audioSourceId); + } +} + +// tslint:disable-next-line:max-classes-per-file +export class AudioSourceErrorEvent extends AudioSourceEvent { + private privError: string; + + constructor(audioSourceId: string, error: string) { + super("AudioSourceErrorEvent", audioSourceId, EventType.Error); + this.privError = error; + } + + public get error(): string { + return this.privError; + } +} + +// tslint:disable-next-line:max-classes-per-file +export class AudioStreamNodeEvent extends AudioSourceEvent { + private privAudioNodeId: string; + + constructor(eventName: string, audioSourceId: string, audioNodeId: string) { + super(eventName, audioSourceId); + this.privAudioNodeId = audioNodeId; + } + + public get audioNodeId(): string { + return this.privAudioNodeId; + } +} + +// tslint:disable-next-line:max-classes-per-file +export class AudioStreamNodeAttachingEvent extends AudioStreamNodeEvent { + constructor(audioSourceId: string, audioNodeId: string) { + super("AudioStreamNodeAttachingEvent", audioSourceId, audioNodeId); + } +} + +// tslint:disable-next-line:max-classes-per-file +export class AudioStreamNodeAttachedEvent extends AudioStreamNodeEvent { + constructor(audioSourceId: string, audioNodeId: string) { + super("AudioStreamNodeAttachedEvent", audioSourceId, audioNodeId); + } +} + +// tslint:disable-next-line:max-classes-per-file +export class AudioStreamNodeDetachedEvent extends AudioStreamNodeEvent { + constructor(audioSourceId: string, audioNodeId: string) { + super("AudioStreamNodeDetachedEvent", audioSourceId, audioNodeId); + } +} + +// tslint:disable-next-line:max-classes-per-file +export class AudioStreamNodeErrorEvent extends AudioStreamNodeEvent { + private privError: string; + + constructor(audioSourceId: string, audioNodeId: string, error: string) { + super("AudioStreamNodeErrorEvent", audioSourceId, audioNodeId); + this.privError = error; + } + + public get error(): string { + return this.privError; + } +} diff --git a/src/common/ConnectionEvents.ts b/src/common/ConnectionEvents.ts new file mode 100644 index 00000000..daf049c3 --- /dev/null +++ b/src/common/ConnectionEvents.ts @@ -0,0 +1,126 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +import { ConnectionMessage } from "./ConnectionMessage"; +import { IStringDictionary } from "./IDictionary"; +import { EventType, PlatformEvent } from "./PlatformEvent"; + +export class ConnectionEvent extends PlatformEvent { + private privConnectionId: string; + + constructor(eventName: string, connectionId: string, eventType: EventType = EventType.Info) { + super(eventName, eventType); + this.privConnectionId = connectionId; + } + + public get connectionId(): string { + return this.privConnectionId; + } +} + +// tslint:disable-next-line:max-classes-per-file +export class ConnectionStartEvent extends ConnectionEvent { + private privUri: string; + private privHeaders: IStringDictionary; + + constructor(connectionId: string, uri: string, headers?: IStringDictionary) { + super("ConnectionStartEvent", connectionId); + this.privUri = uri; + this.privHeaders = headers; + } + + public get uri(): string { + return this.privUri; + } + + public get headers(): IStringDictionary { + return this.privHeaders; + } +} + +// tslint:disable-next-line:max-classes-per-file +export class ConnectionEstablishedEvent extends ConnectionEvent { + constructor(connectionId: string, metadata?: IStringDictionary) { + super("ConnectionEstablishedEvent", connectionId); + } +} + +// tslint:disable-next-line:max-classes-per-file +export class ConnectionClosedEvent extends ConnectionEvent { + private privRreason: string; + private privStatusCode: number; + + constructor(connectionId: string, statusCode: number, reason: string) { + super("ConnectionClosedEvent", connectionId, EventType.Debug); + this.privRreason = reason; + this.privStatusCode = statusCode; + } + + public get reason(): string { + return this.privRreason; + } + + public get statusCode(): number { + return this.privStatusCode; + } +} + +// tslint:disable-next-line:max-classes-per-file +export class ConnectionEstablishErrorEvent extends ConnectionEvent { + private privStatusCode: number; + private privReason: string; + + constructor(connectionId: string, statuscode: number, reason: string) { + super("ConnectionEstablishErrorEvent", connectionId, EventType.Error); + this.privStatusCode = statuscode; + this.privReason = reason; + } + + public get reason(): string { + return this.privReason; + } + + public get statusCode(): number { + return this.privStatusCode; + } +} + +// tslint:disable-next-line:max-classes-per-file +export class ConnectionMessageReceivedEvent extends ConnectionEvent { + private privNetworkReceivedTime: string; + private privMessage: ConnectionMessage; + + constructor(connectionId: string, networkReceivedTimeISO: string, message: ConnectionMessage) { + super("ConnectionMessageReceivedEvent", connectionId); + this.privNetworkReceivedTime = networkReceivedTimeISO; + this.privMessage = message; + } + + public get networkReceivedTime(): string { + return this.privNetworkReceivedTime; + } + + public get message(): ConnectionMessage { + return this.privMessage; + } +} + +// tslint:disable-next-line:max-classes-per-file +export class ConnectionMessageSentEvent extends ConnectionEvent { + private privNetworkSentTime: string; + private privMessage: ConnectionMessage; + + constructor(connectionId: string, networkSentTimeISO: string, message: ConnectionMessage) { + super("ConnectionMessageSentEvent", connectionId); + this.privNetworkSentTime = networkSentTimeISO; + this.privMessage = message; + } + + public get networkSentTime(): string { + return this.privNetworkSentTime; + } + + public get message(): ConnectionMessage { + return this.privMessage; + } +} diff --git a/src/common/ConnectionMessage.ts b/src/common/ConnectionMessage.ts new file mode 100644 index 00000000..3f299e52 --- /dev/null +++ b/src/common/ConnectionMessage.ts @@ -0,0 +1,72 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +import { InvalidOperationError } from "./Error"; +import { createNoDashGuid } from "./Guid"; +import { IStringDictionary } from "./IDictionary"; + +export enum MessageType { + Text, + Binary, +} + +export class ConnectionMessage { + + private privMessageType: MessageType; + private privHeaders: IStringDictionary; + private privBody: any = null; + + private privId: string; + + public constructor( + messageType: MessageType, + body: any, + headers?: IStringDictionary, + id?: string) { + + if (messageType === MessageType.Text && body && !(typeof (body) === "string")) { + throw new InvalidOperationError("Payload must be a string"); + } + + if (messageType === MessageType.Binary && body && !(body instanceof ArrayBuffer)) { + throw new InvalidOperationError("Payload must be ArrayBuffer"); + } + + this.privMessageType = messageType; + this.privBody = body; + this.privHeaders = headers ? headers : {}; + this.privId = id ? id : createNoDashGuid(); + } + + public get messageType(): MessageType { + return this.privMessageType; + } + + public get headers(): any { + return this.privHeaders; + } + + public get body(): any { + return this.privBody; + } + + public get textBody(): string { + if (this.privMessageType === MessageType.Binary) { + throw new InvalidOperationError("Not supported for binary message"); + } + + return this.privBody as string; + } + + public get binaryBody(): ArrayBuffer { + if (this.privMessageType === MessageType.Text) { + throw new InvalidOperationError("Not supported for text message"); + } + + return this.privBody; + } + + public get id(): string { + return this.privId; + } +} diff --git a/src/common/ConnectionOpenResponse.ts b/src/common/ConnectionOpenResponse.ts new file mode 100644 index 00000000..e10961b6 --- /dev/null +++ b/src/common/ConnectionOpenResponse.ts @@ -0,0 +1,20 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +export class ConnectionOpenResponse { + private privStatusCode: number; + private privReason: string; + + constructor(statusCode: number, reason: string) { + this.privStatusCode = statusCode; + this.privReason = reason; + } + + public get statusCode(): number { + return this.privStatusCode; + } + + public get reason(): string { + return this.privReason; + } +} diff --git a/src/common/Error.ts b/src/common/Error.ts new file mode 100644 index 00000000..c7309478 --- /dev/null +++ b/src/common/Error.ts @@ -0,0 +1,74 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +/** + * The error that is thrown when an argument passed in is null. + * + * @export + * @class ArgumentNullError + * @extends {Error} + */ +export class ArgumentNullError extends Error { + + /** + * Creates an instance of ArgumentNullError. + * + * @param {string} argumentName - Name of the argument that is null + * + * @memberOf ArgumentNullError + */ + public constructor(argumentName: string) { + super(argumentName); + this.name = "ArgumentNull"; + this.message = argumentName; + } +} + +/** + * The error that is thrown when an invalid operation is performed in the code. + * + * @export + * @class InvalidOperationError + * @extends {Error} + */ +// tslint:disable-next-line:max-classes-per-file +export class InvalidOperationError extends Error { + + /** + * Creates an instance of InvalidOperationError. + * + * @param {string} error - The error + * + * @memberOf InvalidOperationError + */ + public constructor(error: string) { + super(error); + this.name = "InvalidOperation"; + this.message = error; + } +} + +/** + * The error that is thrown when an object is disposed. + * + * @export + * @class ObjectDisposedError + * @extends {Error} + */ +// tslint:disable-next-line:max-classes-per-file +export class ObjectDisposedError extends Error { + + /** + * Creates an instance of ObjectDisposedError. + * + * @param {string} objectName - The object that is disposed + * @param {string} error - The error + * + * @memberOf ObjectDisposedError + */ + public constructor(objectName: string, error?: string) { + super(error); + this.name = objectName + "ObjectDisposed"; + this.message = error; + } +} diff --git a/src/common/EventSource.ts b/src/common/EventSource.ts new file mode 100644 index 00000000..325a401f --- /dev/null +++ b/src/common/EventSource.ts @@ -0,0 +1,70 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +import { ObjectDisposedError } from "./Error"; +import { createNoDashGuid } from "./Guid"; +import { IDetachable } from "./IDetachable"; +import { IStringDictionary } from "./IDictionary"; +import { IEventListener, IEventSource } from "./IEventSource"; +import { PlatformEvent } from "./PlatformEvent"; + +export class EventSource implements IEventSource { + private privEventListeners: IStringDictionary<(event: TEvent) => void> = {}; + private privMetadata: IStringDictionary; + private privIsDisposed: boolean = false; + + constructor(metadata?: IStringDictionary) { + this.privMetadata = metadata; + } + + public onEvent = (event: TEvent): void => { + if (this.isDisposed()) { + throw (new ObjectDisposedError("EventSource")); + } + + if (this.metadata) { + for (const paramName in this.metadata) { + if (paramName) { + if (event.metadata) { + if (!event.metadata[paramName]) { + event.metadata[paramName] = this.metadata[paramName]; + } + } + } + } + } + + for (const eventId in this.privEventListeners) { + if (eventId && this.privEventListeners[eventId]) { + this.privEventListeners[eventId](event); + } + } + } + + public attach = (onEventCallback: (event: TEvent) => void): IDetachable => { + const id = createNoDashGuid(); + this.privEventListeners[id] = onEventCallback; + return { + detach: () => { + delete this.privEventListeners[id]; + }, + }; + } + + public attachListener = (listener: IEventListener): IDetachable => { + return this.attach(listener.onEvent); + } + + public isDisposed = (): boolean => { + return this.privIsDisposed; + } + + public dispose = (): void => { + this.privEventListeners = null; + this.privIsDisposed = true; + } + + public get metadata(): IStringDictionary { + return this.privMetadata; + } +} diff --git a/src/common/Events.ts b/src/common/Events.ts new file mode 100644 index 00000000..e77389d8 --- /dev/null +++ b/src/common/Events.ts @@ -0,0 +1,23 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +import { ArgumentNullError } from "./Error"; +import { EventSource } from "./EventSource"; +import { IEventSource } from "./IEventSource"; +import { PlatformEvent } from "./PlatformEvent"; + +export class Events { + private static privInstance: IEventSource = new EventSource(); + + public static setEventSource = (eventSource: IEventSource): void => { + if (!eventSource) { + throw new ArgumentNullError("eventSource"); + } + + Events.privInstance = eventSource; + } + + public static get instance(): IEventSource { + return Events.privInstance; + } +} diff --git a/src/common/Exports.ts b/src/common/Exports.ts new file mode 100644 index 00000000..4e76a621 --- /dev/null +++ b/src/common/Exports.ts @@ -0,0 +1,30 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +export * from "./AudioSourceEvents"; +export * from "./ConnectionEvents"; +export * from "./ConnectionMessage"; +export * from "./ConnectionOpenResponse"; +export * from "./Error"; +export * from "./Events"; +export * from "./EventSource"; +export * from "./Guid"; +export * from "./IAudioSource"; +export * from "./IConnection"; +export * from "./IDetachable"; +export * from "./IDictionary"; +export * from "./IDisposable"; +export * from "./IEventSource"; +export * from "./IKeyValueStorage"; +export * from "./InMemoryStorage"; +export * from "./ITimer"; +export * from "./IWebsocketMessageFormatter"; +export * from "./List"; +export * from "./PlatformEvent"; +export * from "./Promise"; +export * from "./Queue"; +export * from "./RawWebsocketMessage"; +export * from "./RiffPcmEncoder"; +export * from "./Storage"; +export * from "./Stream"; +export { TranslationStatus } from "../common.speech/TranslationStatus"; diff --git a/src/common/Guid.ts b/src/common/Guid.ts new file mode 100644 index 00000000..98d45db5 --- /dev/null +++ b/src/common/Guid.ts @@ -0,0 +1,19 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +const createGuid: () => string = (): string => { + let d = new Date().getTime(); + const guid = "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c: string) => { + const r = (d + Math.random() * 16) % 16 | 0; + d = Math.floor(d / 16); + return (c === "x" ? r : (r & 0x3 | 0x8)).toString(16); + }); + + return guid; +}; + +const createNoDashGuid: () => string = (): string => { + return createGuid().replace(new RegExp("-", "g"), "").toUpperCase(); +}; + +export { createGuid, createNoDashGuid }; diff --git a/src/common/IAudioSource.ts b/src/common/IAudioSource.ts new file mode 100644 index 00000000..da03e868 --- /dev/null +++ b/src/common/IAudioSource.ts @@ -0,0 +1,24 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +import { AudioStreamFormat } from "../../src/sdk/Exports"; +import { AudioSourceEvent } from "./AudioSourceEvents"; +import { EventSource } from "./EventSource"; +import { IDetachable } from "./IDetachable"; +import { Promise } from "./Promise"; +import { IStreamChunk } from "./Stream"; + +export interface IAudioSource { + id(): string; + turnOn(): Promise; + attach(audioNodeId: string): Promise; + detach(audioNodeId: string): void; + turnOff(): Promise; + events: EventSource; + format: AudioStreamFormat; +} + +export interface IAudioStreamNode extends IDetachable { + id(): string; + read(): Promise>; +} diff --git a/src/common/IConnection.ts b/src/common/IConnection.ts new file mode 100644 index 00000000..db066f2d --- /dev/null +++ b/src/common/IConnection.ts @@ -0,0 +1,25 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +import { ConnectionEvent } from "./ConnectionEvents"; +import { ConnectionMessage } from "./ConnectionMessage"; +import { ConnectionOpenResponse } from "./ConnectionOpenResponse"; +import { EventSource } from "./EventSource"; +import { IDisposable } from "./IDisposable"; +import { Promise } from "./Promise"; + +export enum ConnectionState { + None, + Connected, + Connecting, + Disconnected, +} + +export interface IConnection extends IDisposable { + id: string; + state(): ConnectionState; + open(): Promise; + send(message: ConnectionMessage): Promise; + read(): Promise; + events: EventSource; +} diff --git a/src/common/IDetachable.ts b/src/common/IDetachable.ts new file mode 100644 index 00000000..860b3520 --- /dev/null +++ b/src/common/IDetachable.ts @@ -0,0 +1,6 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +export interface IDetachable { + detach(): void; +} diff --git a/src/common/IDictionary.ts b/src/common/IDictionary.ts new file mode 100644 index 00000000..8297b51a --- /dev/null +++ b/src/common/IDictionary.ts @@ -0,0 +1,10 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +export interface IStringDictionary { + [propName: string]: TValue; +} + +export interface INumberDictionary extends Object { + [propName: number]: TValue; +} diff --git a/src/common/IDisposable.ts b/src/common/IDisposable.ts new file mode 100644 index 00000000..b9a05df9 --- /dev/null +++ b/src/common/IDisposable.ts @@ -0,0 +1,26 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +/** + * @export + * @interface IDisposable + */ +export interface IDisposable { + + /** + * @returns {boolean} + * + * @memberOf IDisposable + */ + isDisposed(): boolean; + + /** + * Performs cleanup operations on this instance + * + * @param {string} [reason] - optional reason for disposing the instance. + * This will be used to throw errors when a operations are performed on the disposed object. + * + * @memberOf IDisposable + */ + dispose(reason?: string): void; +} diff --git a/src/common/IEventSource.ts b/src/common/IEventSource.ts new file mode 100644 index 00000000..b4c967ab --- /dev/null +++ b/src/common/IEventSource.ts @@ -0,0 +1,21 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +import { IDetachable } from "./IDetachable"; +import { IStringDictionary } from "./IDictionary"; +import { IDisposable } from "./IDisposable"; +import { PlatformEvent } from "./PlatformEvent"; + +export interface IEventListener { + onEvent(e: TEvent): void; +} + +export interface IEventSource extends IDisposable { + metadata: IStringDictionary; + + onEvent(e: TEvent): void; + + attach(onEventCallback: (event: TEvent) => void): IDetachable; + + attachListener(listener: IEventListener): IDetachable; +} diff --git a/src/common/IKeyValueStorage.ts b/src/common/IKeyValueStorage.ts new file mode 100644 index 00000000..9665d9d7 --- /dev/null +++ b/src/common/IKeyValueStorage.ts @@ -0,0 +1,9 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +export interface IKeyValueStorage { + get(key: string): string; + getOrAdd(key: string, valueToAdd: string): string; + set(key: string, value: string): void; + remove(key: string): void; +} diff --git a/src/common/ITimer.ts b/src/common/ITimer.ts new file mode 100644 index 00000000..b6ee1bb9 --- /dev/null +++ b/src/common/ITimer.ts @@ -0,0 +1,24 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +export interface ITimer { + /** + * start timer + * + * @param {number} delay + * @param {(...args: any[]) => any} successCallback + * @returns {*} + * + * @memberOf ITimer + */ + start(): void; + + /** + * stops timer + * + * @param {*} timerId + * + * @memberOf ITimer + */ + stop(): void; +} diff --git a/src/common/IWebsocketMessageFormatter.ts b/src/common/IWebsocketMessageFormatter.ts new file mode 100644 index 00000000..a31248fe --- /dev/null +++ b/src/common/IWebsocketMessageFormatter.ts @@ -0,0 +1,11 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +import { ConnectionMessage } from "./ConnectionMessage"; +import { Promise } from "./Promise"; +import { RawWebsocketMessage } from "./RawWebsocketMessage"; + +export interface IWebsocketMessageFormatter { + toConnectionMessage(message: RawWebsocketMessage): Promise; + fromConnectionMessage(message: ConnectionMessage): Promise; +} diff --git a/src/common/InMemoryStorage.ts b/src/common/InMemoryStorage.ts new file mode 100644 index 00000000..36548d61 --- /dev/null +++ b/src/common/InMemoryStorage.ts @@ -0,0 +1,49 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +import { ArgumentNullError } from "./Error"; +import { IStringDictionary } from "./IDictionary"; +import { IKeyValueStorage } from "./IKeyValueStorage"; + +export class InMemoryStorage implements IKeyValueStorage { + + private privStore: IStringDictionary = {}; + + public get = (key: string): string => { + if (!key) { + throw new ArgumentNullError("key"); + } + + return this.privStore[key]; + } + + public getOrAdd = (key: string, valueToAdd: string): string => { + if (!key) { + throw new ArgumentNullError("key"); + } + + if (this.privStore[key] === undefined) { + this.privStore[key] = valueToAdd; + } + + return this.privStore[key]; + } + + public set = (key: string, value: string): void => { + if (!key) { + throw new ArgumentNullError("key"); + } + + this.privStore[key] = value; + } + + public remove = (key: string): void => { + if (!key) { + throw new ArgumentNullError("key"); + } + + if (this.privStore[key] !== undefined) { + delete this.privStore[key]; + } + } +} diff --git a/src/common/List.ts b/src/common/List.ts new file mode 100644 index 00000000..8ad842a4 --- /dev/null +++ b/src/common/List.ts @@ -0,0 +1,275 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +import { ObjectDisposedError } from "./Error"; +import { IDetachable } from "./IDetachable"; +import { IStringDictionary } from "./IDictionary"; +import { IDisposable } from "./IDisposable"; + +export interface IList extends IDisposable { + get(itemIndex: number): TItem; + first(): TItem; + last(): TItem; + + add(item: TItem): void; + insertAt(index: number, item: TItem): void; + + removeFirst(): TItem; + removeLast(): TItem; + removeAt(index: number): TItem; + remove(index: number, count: number): TItem[]; + clear(): void; + + length(): number; + + onAdded(addedCallback: () => void): IDetachable; + onRemoved(removedCallback: () => void): IDetachable; + onDisposed(disposedCallback: () => void): IDetachable; + + join(seperator?: string): string; + + toArray(): TItem[]; + + any(callback?: (item: TItem, index: number) => boolean): boolean; + all(callback: (item: TItem) => boolean): boolean; + forEach(callback: (item: TItem, index: number) => void): void; + select(callback: (item: TItem, index: number) => T2): List; + where(callback: (item: TItem, index: number) => boolean): List; + orderBy(compareFn: (a: TItem, b: TItem) => number): List; + orderByDesc(compareFn: (a: TItem, b: TItem) => number): List; + clone(): List; + concat(list: List): List; + concatArray(array: TItem[]): List; +} + +export class List implements IList { + private privList: TItem[]; + private privSubscriptionIdCounter: number = 0; + private privAddSubscriptions: IStringDictionary<() => void> = {}; + private privRemoveSubscriptions: IStringDictionary<() => void> = {}; + private privDisposedSubscriptions: IStringDictionary<() => void> = {}; + private privDisposeReason: string = null; + + public constructor(list?: TItem[]) { + this.privList = []; + // copy the list rather than taking as is. + if (list) { + for (const item of list) { + this.privList.push(item); + } + } + } + + public get = (itemIndex: number): TItem => { + this.throwIfDisposed(); + return this.privList[itemIndex]; + } + + public first = (): TItem => { + return this.get(0); + } + + public last = (): TItem => { + return this.get(this.length() - 1); + } + + public add = (item: TItem): void => { + this.throwIfDisposed(); + this.insertAt(this.privList.length, item); + } + + public insertAt = (index: number, item: TItem): void => { + this.throwIfDisposed(); + if (index === 0) { + this.privList.unshift(item); + } else if (index === this.privList.length) { + this.privList.push(item); + } else { + this.privList.splice(index, 0, item); + } + this.triggerSubscriptions(this.privAddSubscriptions); + } + + public removeFirst = (): TItem => { + this.throwIfDisposed(); + return this.removeAt(0); + } + + public removeLast = (): TItem => { + this.throwIfDisposed(); + return this.removeAt(this.length() - 1); + } + + public removeAt = (index: number): TItem => { + this.throwIfDisposed(); + return this.remove(index, 1)[0]; + } + + public remove = (index: number, count: number): TItem[] => { + this.throwIfDisposed(); + const removedElements = this.privList.splice(index, count); + this.triggerSubscriptions(this.privRemoveSubscriptions); + return removedElements; + } + + public clear = (): void => { + this.throwIfDisposed(); + this.remove(0, this.length()); + } + + public length = (): number => { + this.throwIfDisposed(); + return this.privList.length; + } + + public onAdded = (addedCallback: () => void): IDetachable => { + this.throwIfDisposed(); + const subscriptionId = this.privSubscriptionIdCounter++; + + this.privAddSubscriptions[subscriptionId] = addedCallback; + + return { + detach: () => { + delete this.privAddSubscriptions[subscriptionId]; + }, + }; + } + + public onRemoved = (removedCallback: () => void): IDetachable => { + this.throwIfDisposed(); + const subscriptionId = this.privSubscriptionIdCounter++; + + this.privRemoveSubscriptions[subscriptionId] = removedCallback; + + return { + detach: () => { + delete this.privRemoveSubscriptions[subscriptionId]; + }, + }; + } + + public onDisposed = (disposedCallback: () => void): IDetachable => { + this.throwIfDisposed(); + const subscriptionId = this.privSubscriptionIdCounter++; + + this.privDisposedSubscriptions[subscriptionId] = disposedCallback; + + return { + detach: () => { + delete this.privDisposedSubscriptions[subscriptionId]; + }, + }; + } + + public join = (seperator?: string): string => { + this.throwIfDisposed(); + return this.privList.join(seperator); + } + + public toArray = (): TItem[] => { + const cloneCopy = Array(); + this.privList.forEach((val: TItem) => { + cloneCopy.push(val); + }); + return cloneCopy; + } + + public any = (callback?: (item: TItem, index: number) => boolean): boolean => { + this.throwIfDisposed(); + if (callback) { + return this.where(callback).length() > 0; + } else { + return this.length() > 0; + } + } + + public all = (callback: (item: TItem) => boolean): boolean => { + this.throwIfDisposed(); + return this.where(callback).length() === this.length(); + } + + public forEach = (callback: (item: TItem, index: number) => void): void => { + this.throwIfDisposed(); + for (let i = 0; i < this.length(); i++) { + callback(this.privList[i], i); + } + } + + public select = (callback: (item: TItem, index: number) => T2): List => { + this.throwIfDisposed(); + const selectList: T2[] = []; + for (let i = 0; i < this.privList.length; i++) { + selectList.push(callback(this.privList[i], i)); + } + + return new List(selectList); + } + + public where = (callback: (item: TItem, index: number) => boolean): List => { + this.throwIfDisposed(); + const filteredList = new List(); + for (let i = 0; i < this.privList.length; i++) { + if (callback(this.privList[i], i)) { + filteredList.add(this.privList[i]); + } + } + return filteredList; + } + + public orderBy = (compareFn: (a: TItem, b: TItem) => number): List => { + this.throwIfDisposed(); + const clonedArray = this.toArray(); + const orderedArray = clonedArray.sort(compareFn); + return new List(orderedArray); + } + + public orderByDesc = (compareFn: (a: TItem, b: TItem) => number): List => { + this.throwIfDisposed(); + return this.orderBy((a: TItem, b: TItem) => compareFn(b, a)); + } + + public clone = (): List => { + this.throwIfDisposed(); + return new List(this.toArray()); + } + + public concat = (list: List): List => { + this.throwIfDisposed(); + return new List(this.privList.concat(list.toArray())); + } + + public concatArray = (array: TItem[]): List => { + this.throwIfDisposed(); + return new List(this.privList.concat(array)); + } + + public isDisposed = (): boolean => { + return this.privList == null; + } + + public dispose = (reason?: string): void => { + if (!this.isDisposed()) { + this.privDisposeReason = reason; + this.privList = null; + this.privAddSubscriptions = null; + this.privRemoveSubscriptions = null; + this.triggerSubscriptions(this.privDisposedSubscriptions); + } + } + + private throwIfDisposed = (): void => { + if (this.isDisposed()) { + throw new ObjectDisposedError("List", this.privDisposeReason); + } + } + + private triggerSubscriptions = (subscriptions: IStringDictionary<() => void>): void => { + if (subscriptions) { + for (const subscriptionId in subscriptions) { + if (subscriptionId) { + subscriptions[subscriptionId](); + } + } + } + } +} diff --git a/src/common/PlatformEvent.ts b/src/common/PlatformEvent.ts new file mode 100644 index 00000000..fe68080b --- /dev/null +++ b/src/common/PlatformEvent.ts @@ -0,0 +1,48 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +import { createNoDashGuid } from "./Guid"; +import { IStringDictionary } from "./IDictionary"; + +export enum EventType { + Debug, + Info, + Warning, + Error, +} + +export class PlatformEvent { + private privName: string; + private privEventId: string; + private privEventTime: string; + private privEventType: EventType; + private privMetadata: IStringDictionary; + + constructor(eventName: string, eventType: EventType) { + this.privName = eventName; + this.privEventId = createNoDashGuid(); + this.privEventTime = new Date().toISOString(); + this.privEventType = eventType; + this.privMetadata = { }; + } + + public get name(): string { + return this.privName; + } + + public get eventId(): string { + return this.privEventId; + } + + public get eventTime(): string { + return this.privEventTime; + } + + public get eventType(): EventType { + return this.privEventType; + } + + public get metadata(): IStringDictionary { + return this.privMetadata; + } +} diff --git a/src/common/Promise.ts b/src/common/Promise.ts new file mode 100644 index 00000000..12a80d7d --- /dev/null +++ b/src/common/Promise.ts @@ -0,0 +1,457 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +import { ArgumentNullError } from "./Error"; + +export enum PromiseState { + None, + Resolved, + Rejected, +} + +export interface IPromise { + result(): PromiseResult; + + continueWith( + continuationCallback: (promiseResult: PromiseResult) => TContinuationResult): IPromise; + + continueWithPromise( + continuationCallback: (promiseResult: PromiseResult) => IPromise): IPromise; + + onSuccessContinueWith( + continuationCallback: (result: T) => TContinuationResult): IPromise; + + onSuccessContinueWithPromise( + continuationCallback: (result: T) => IPromise): IPromise; + + on(successCallback: (result: T) => void, errorCallback: (error: string) => void): IPromise; + + finally(callback: () => void): IPromise; +} + +export interface IDeferred { + state(): PromiseState; + + promise(): IPromise; + + resolve(result: T): IDeferred; + + reject(error: string): IDeferred; +} + +export class PromiseResult { + protected privIsCompleted: boolean; + protected privIsError: boolean; + protected privError: string; + protected privResult: T; + + public constructor(promiseResultEventSource: PromiseResultEventSource) { + promiseResultEventSource.on((result: T) => { + if (!this.privIsCompleted) { + this.privIsCompleted = true; + this.privIsError = false; + this.privResult = result; + } + }, (error: string) => { + if (!this.privIsCompleted) { + this.privIsCompleted = true; + this.privIsError = true; + this.privError = error; + } + }); + } + + public get isCompleted(): boolean { + return this.privIsCompleted; + } + + public get isError(): boolean { + return this.privIsError; + } + + public get error(): string { + return this.privError; + } + + public get result(): T { + return this.privResult; + } + + public throwIfError = (): void => { + if (this.isError) { + throw this.error; + } + } +} + +// tslint:disable-next-line:max-classes-per-file +export class PromiseResultEventSource { + + private privOnSetResult: (result: T) => void; + private privOnSetError: (error: string) => void; + + public setResult = (result: T): void => { + this.privOnSetResult(result); + } + + public setError = (error: string): void => { + this.privOnSetError(error); + } + + public on = (onSetResult: (result: T) => void, onSetError: (error: string) => void): void => { + this.privOnSetResult = onSetResult; + this.privOnSetError = onSetError; + } +} + +// tslint:disable-next-line:max-classes-per-file +export class PromiseHelper { + public static whenAll = (promises: Array>): Promise => { + if (!promises || promises.length === 0) { + throw new ArgumentNullError("promises"); + } + + const deferred = new Deferred(); + const errors: string[] = []; + let completedPromises: number = 0; + + const checkForCompletion = () => { + completedPromises++; + if (completedPromises === promises.length) { + if (errors.length === 0) { + deferred.resolve(true); + } else { + deferred.reject(errors.join(", ")); + } + } + }; + + for (const promise of promises) { + promise.on((r: any) => { + checkForCompletion(); + }, (e: string) => { + errors.push(e); + checkForCompletion(); + }); + } + + return deferred.promise(); + } + + public static fromResult = (result: TResult): Promise => { + const deferred = new Deferred(); + deferred.resolve(result); + return deferred.promise(); + } + + public static fromError = (error: string): Promise => { + const deferred = new Deferred(); + deferred.reject(error); + return deferred.promise(); + } +} + +// TODO: replace with ES6 promises +// tslint:disable-next-line:max-classes-per-file +export class Promise implements IPromise { + private privSink: Sink; + + public constructor(sink: Sink) { + this.privSink = sink; + } + + public result = (): PromiseResult => { + return this.privSink.result; + } + + public continueWith = ( + continuationCallback: (promiseResult: PromiseResult) => TContinuationResult): Promise => { + + if (!continuationCallback) { + throw new ArgumentNullError("continuationCallback"); + } + + const continuationDeferral = new Deferred(); + + this.privSink.on( + (r: T) => { + try { + const continuationResult: TContinuationResult = continuationCallback(this.privSink.result); + continuationDeferral.resolve(continuationResult); + } catch (e) { + continuationDeferral.reject(e); + } + }, + (error: string) => { + try { + const continuationResult: TContinuationResult = continuationCallback(this.privSink.result); + continuationDeferral.resolve(continuationResult); + } catch (e) { + continuationDeferral.reject(`'Error handler for error ${error} threw error ${e}'`); + } + }, + ); + + return continuationDeferral.promise(); + } + + public onSuccessContinueWith = ( + continuationCallback: (result: T) => TContinuationResult): Promise => { + + if (!continuationCallback) { + throw new ArgumentNullError("continuationCallback"); + } + + const continuationDeferral = new Deferred(); + + this.privSink.on( + (r: T) => { + try { + const continuationResult: TContinuationResult = continuationCallback(r); + continuationDeferral.resolve(continuationResult); + } catch (e) { + continuationDeferral.reject(e); + } + }, + (error: string) => { + continuationDeferral.reject(error); + }, + ); + + return continuationDeferral.promise(); + } + + public continueWithPromise = ( + continuationCallback: (promiseResult: PromiseResult) => Promise): Promise => { + + if (!continuationCallback) { + throw new ArgumentNullError("continuationCallback"); + } + + const continuationDeferral = new Deferred(); + + this.privSink.on( + (r: T) => { + try { + const continuationPromise: Promise = continuationCallback(this.privSink.result); + if (!continuationPromise) { + throw new Error("'Continuation callback did not return promise'"); + } + continuationPromise.on((continuationResult: TContinuationResult) => { + continuationDeferral.resolve(continuationResult); + }, (e: string) => { + continuationDeferral.reject(e); + }); + } catch (e) { + continuationDeferral.reject(e); + } + }, + (error: string) => { + try { + const continuationPromise: Promise = continuationCallback(this.privSink.result); + if (!continuationPromise) { + throw new Error("Continuation callback did not return promise"); + } + continuationPromise.on((continuationResult: TContinuationResult) => { + continuationDeferral.resolve(continuationResult); + }, (e: string) => { + continuationDeferral.reject(e); + }); + } catch (e) { + continuationDeferral.reject(`'Error handler for error ${error} threw error ${e}'`); + } + }, + ); + + return continuationDeferral.promise(); + } + + public onSuccessContinueWithPromise = ( + continuationCallback: (result: T) => Promise): Promise => { + + if (!continuationCallback) { + throw new ArgumentNullError("continuationCallback"); + } + + const continuationDeferral = new Deferred(); + + this.privSink.on( + (r: T) => { + try { + const continuationPromise: Promise = continuationCallback(r); + if (!continuationPromise) { + throw new Error("Continuation callback did not return promise"); + } + continuationPromise.on((continuationResult: TContinuationResult) => { + continuationDeferral.resolve(continuationResult); + }, (e: string) => { + continuationDeferral.reject(e); + }); + } catch (e) { + continuationDeferral.reject(e); + } + }, + (error: string) => { + continuationDeferral.reject(error); + }, + ); + + return continuationDeferral.promise(); + } + + public on = ( + successCallback: (result: T) => void, + errorCallback: (error: string) => void): Promise => { + if (!successCallback) { + throw new ArgumentNullError("successCallback"); + } + + if (!errorCallback) { + throw new ArgumentNullError("errorCallback"); + } + + this.privSink.on(successCallback, errorCallback); + return this; + } + + public finally = (callback: () => void): Promise => { + if (!callback) { + throw new ArgumentNullError("callback"); + } + + const callbackWrapper = (_: any) => { + callback(); + }; + + return this.on(callbackWrapper, callbackWrapper); + } +} + +// tslint:disable-next-line:max-classes-per-file +export class Deferred implements IDeferred { + private privPromise: Promise; + private privSink: Sink; + + public constructor() { + this.privSink = new Sink(); + this.privPromise = new Promise(this.privSink); + } + + public state = (): PromiseState => { + return this.privSink.state; + } + + public promise = (): Promise => { + return this.privPromise; + } + + public resolve = (result: T): Deferred => { + this.privSink.resolve(result); + return this; + } + + public reject = (error: string): Deferred => { + this.privSink.reject(error); + return this; + } +} + +// tslint:disable-next-line:max-classes-per-file +export class Sink { + private privState: PromiseState = PromiseState.None; + private privPromiseResult: PromiseResult = null; + private privPromiseResultEvents: PromiseResultEventSource = null; + + private privSuccessHandlers: Array<((result: T) => void)> = []; + private privErrorHandlers: Array<(e: string) => void> = []; + + public constructor() { + this.privPromiseResultEvents = new PromiseResultEventSource(); + this.privPromiseResult = new PromiseResult(this.privPromiseResultEvents); + } + + public get state(): PromiseState { + return this.privState; + } + + public get result(): PromiseResult { + return this.privPromiseResult; + } + + public resolve = (result: T): void => { + if (this.privState !== PromiseState.None) { + throw new Error("'Cannot resolve a completed promise'"); + } + + this.privState = PromiseState.Resolved; + this.privPromiseResultEvents.setResult(result); + + for (let i = 0; i < this.privSuccessHandlers.length; i++) { + this.executeSuccessCallback(result, this.privSuccessHandlers[i], this.privErrorHandlers[i]); + } + + this.detachHandlers(); + } + + public reject = (error: string): void => { + if (this.privState !== PromiseState.None) { + throw new Error("'Cannot reject a completed promise'"); + } + + this.privState = PromiseState.Rejected; + this.privPromiseResultEvents.setError(error); + + for (const errorHandler of this.privErrorHandlers) { + this.executeErrorCallback(error, errorHandler); + } + + this.detachHandlers(); + } + + public on = ( + successCallback: (result: T) => void, + errorCallback: (error: string) => void): void => { + + if (successCallback == null) { + successCallback = (r: T) => { return; }; + } + + if (this.privState === PromiseState.None) { + this.privSuccessHandlers.push(successCallback); + this.privErrorHandlers.push(errorCallback); + } else { + if (this.privState === PromiseState.Resolved) { + this.executeSuccessCallback(this.privPromiseResult.result, successCallback, errorCallback); + } else if (this.privState === PromiseState.Rejected) { + this.executeErrorCallback(this.privPromiseResult.error, errorCallback); + } + + this.detachHandlers(); + } + } + + private executeSuccessCallback = (result: T, successCallback: (result: T) => void, errorCallback: (error: string) => void): void => { + try { + successCallback(result); + } catch (e) { + this.executeErrorCallback(`'Unhandled callback error: ${e}'`, errorCallback); + } + } + + private executeErrorCallback = (error: string, errorCallback: (error: string) => void): void => { + if (errorCallback) { + try { + errorCallback(error); + } catch (e) { + throw new Error(`'Unhandled callback error: ${e}. InnerError: ${error}'`); + } + } else { + throw new Error(`'Unhandled error: ${error}'`); + } + } + + private detachHandlers = (): void => { + this.privErrorHandlers = []; + this.privSuccessHandlers = []; + } +} diff --git a/src/common/Queue.ts b/src/common/Queue.ts new file mode 100644 index 00000000..d638b3be --- /dev/null +++ b/src/common/Queue.ts @@ -0,0 +1,212 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +import { InvalidOperationError, ObjectDisposedError } from "./Error"; +import { IDetachable } from "./IDetachable"; +import { IDisposable } from "./IDisposable"; +import { List } from "./List"; +import { Deferred, Promise, PromiseHelper } from "./Promise"; + +export interface IQueue extends IDisposable { + enqueue(item: TItem): void; + enqueueFromPromise(promise: Promise): void; + dequeue(): Promise; + peek(): Promise; + length(): number; +} + +enum SubscriberType { + Dequeue, + Peek, +} + +export class Queue implements IQueue { + private privPromiseStore: List> = new List>(); + private privList: List; + private privDetachables: IDetachable[]; + private privSubscribers: List<{ type: SubscriberType, deferral: Deferred }>; + private privIsDrainInProgress: boolean = false; + private privIsDisposing: boolean = false; + private privDisposeReason: string = null; + + public constructor(list?: List) { + this.privList = list ? list : new List(); + this.privDetachables = []; + this.privSubscribers = new List<{ type: SubscriberType, deferral: Deferred }>(); + this.privDetachables.push(this.privList.onAdded(this.drain)); + } + + public enqueue = (item: TItem): void => { + this.throwIfDispose(); + this.enqueueFromPromise(PromiseHelper.fromResult(item)); + } + + public enqueueFromPromise = (promise: Promise): void => { + this.throwIfDispose(); + this.privPromiseStore.add(promise); + promise.finally(() => { + while (this.privPromiseStore.length() > 0) { + if (!this.privPromiseStore.first().result().isCompleted) { + break; + } else { + const p = this.privPromiseStore.removeFirst(); + if (!p.result().isError) { + this.privList.add(p.result().result); + } else { + // TODO: Log as warning. + } + } + } + }); + } + + public dequeue = (): Promise => { + this.throwIfDispose(); + const deferredSubscriber = new Deferred(); + + if (this.privSubscribers) { + this.privSubscribers.add({ deferral: deferredSubscriber, type: SubscriberType.Dequeue }); + this.drain(); + } + + return deferredSubscriber.promise(); + } + + public peek = (): Promise => { + this.throwIfDispose(); + const deferredSubscriber = new Deferred(); + + const subs = this.privSubscribers; + if (subs) { + this.privSubscribers.add({ deferral: deferredSubscriber, type: SubscriberType.Peek }); + this.drain(); + } + + return deferredSubscriber.promise(); + } + + public length = (): number => { + this.throwIfDispose(); + return this.privList.length(); + } + + public isDisposed = (): boolean => { + return this.privSubscribers == null; + } + + public drainAndDispose = (pendingItemProcessor: (pendingItemInQueue: TItem) => void, reason?: string): Promise => { + if (!this.isDisposed() && !this.privIsDisposing) { + this.privDisposeReason = reason; + this.privIsDisposing = true; + + const subs = this.privSubscribers; + if (subs) { + while (subs.length() > 0) { + const subscriber = subs.removeFirst(); + // TODO: this needs work (Resolve(null) instead?). + subscriber.deferral.resolve(undefined); + // subscriber.deferral.reject("Disposed"); + } + + // note: this block assumes cooperative multitasking, i.e., + // between the if-statement and the assignment there are no + // thread switches. + // Reason is that between the initial const = this.; and this + // point there is the derral.resolve() operation that might have + // caused recursive calls to the Queue, especially, calling + // Dispose() on the queue alredy (which would reset the var + // here to null!). + // That should generally hold true for javascript... + if (this.privSubscribers === subs) { + this.privSubscribers = subs; + } + } + + for (const detachable of this.privDetachables) { + detachable.detach(); + } + + if (this.privPromiseStore.length() > 0 && pendingItemProcessor) { + return PromiseHelper + .whenAll(this.privPromiseStore.toArray()) + .continueWith(() => { + this.privSubscribers = null; + this.privList.forEach((item: TItem, index: number): void => { + pendingItemProcessor(item); + }); + this.privList = null; + return true; + }); + } else { + this.privSubscribers = null; + this.privList = null; + } + } + + return PromiseHelper.fromResult(true); + } + + public dispose = (reason?: string): void => { + this.drainAndDispose(null, reason); + } + + private drain = (): void => { + if (!this.privIsDrainInProgress && !this.privIsDisposing) { + this.privIsDrainInProgress = true; + + const subs = this.privSubscribers; + const lists = this.privList; + if (subs && lists) { + while (lists.length() > 0 && subs.length() > 0 && !this.privIsDisposing) { + const subscriber = subs.removeFirst(); + if (subscriber.type === SubscriberType.Peek) { + subscriber.deferral.resolve(lists.first()); + } else { + const dequeuedItem = lists.removeFirst(); + subscriber.deferral.resolve(dequeuedItem); + } + } + + // note: this block assumes cooperative multitasking, i.e., + // between the if-statement and the assignment there are no + // thread switches. + // Reason is that between the initial const = this.; and this + // point there is the derral.resolve() operation that might have + // caused recursive calls to the Queue, especially, calling + // Dispose() on the queue alredy (which would reset the var + // here to null!). + // That should generally hold true for javascript... + if (this.privSubscribers === subs) { + this.privSubscribers = subs; + } + + // note: this block assumes cooperative multitasking, i.e., + // between the if-statement and the assignment there are no + // thread switches. + // Reason is that between the initial const = this.; and this + // point there is the derral.resolve() operation that might have + // caused recursive calls to the Queue, especially, calling + // Dispose() on the queue alredy (which would reset the var + // here to null!). + // That should generally hold true for javascript... + if (this.privList === lists) { + this.privList = lists; + } + } + + this.privIsDrainInProgress = false; + } + } + + private throwIfDispose = (): void => { + if (this.isDisposed()) { + if (this.privDisposeReason) { + throw new InvalidOperationError(this.privDisposeReason); + } + + throw new ObjectDisposedError("Queue"); + } else if (this.privIsDisposing) { + throw new InvalidOperationError("Queue disposing"); + } + } +} diff --git a/src/common/RawWebsocketMessage.ts b/src/common/RawWebsocketMessage.ts new file mode 100644 index 00000000..958640a9 --- /dev/null +++ b/src/common/RawWebsocketMessage.ts @@ -0,0 +1,58 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +import { MessageType } from "./ConnectionMessage"; +import { ArgumentNullError, InvalidOperationError } from "./Error"; +import { createNoDashGuid } from "./Guid"; + +export class RawWebsocketMessage { + private privMessageType: MessageType; + private privPayload: any = null; + private privId: string; + + public constructor(messageType: MessageType, payload: any, id?: string) { + if (!payload) { + throw new ArgumentNullError("payload"); + } + + if (messageType === MessageType.Binary && !(payload instanceof ArrayBuffer)) { + throw new InvalidOperationError("Payload must be ArrayBuffer"); + } + + if (messageType === MessageType.Text && !(typeof (payload) === "string")) { + throw new InvalidOperationError("Payload must be a string"); + } + + this.privMessageType = messageType; + this.privPayload = payload; + this.privId = id ? id : createNoDashGuid(); + } + + public get messageType(): MessageType { + return this.privMessageType; + } + + public get payload(): any { + return this.privPayload; + } + + public get textContent(): string { + if (this.privMessageType === MessageType.Binary) { + throw new InvalidOperationError("Not supported for binary message"); + } + + return this.privPayload as string; + } + + public get binaryContent(): ArrayBuffer { + if (this.privMessageType === MessageType.Text) { + throw new InvalidOperationError("Not supported for text message"); + } + + return this.privPayload; + } + + public get id(): string { + return this.privId; + } +} diff --git a/src/common/RiffPcmEncoder.ts b/src/common/RiffPcmEncoder.ts new file mode 100644 index 00000000..bc1891fd --- /dev/null +++ b/src/common/RiffPcmEncoder.ts @@ -0,0 +1,115 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +export class RiffPcmEncoder { + + private privActualSampleRate: number; + private privDesiredSampleRate: number; + private privChannelCount: number = 1; + + public constructor(actualSampleRate: number, desiredSampleRate: number) { + this.privActualSampleRate = actualSampleRate; + this.privDesiredSampleRate = desiredSampleRate; + } + + public encode = ( + needHeader: boolean, + actualAudioFrame: Float32Array): ArrayBuffer => { + + const audioFrame = this.downSampleAudioFrame(actualAudioFrame, this.privActualSampleRate, this.privDesiredSampleRate); + + if (!audioFrame) { + return null; + } + + const audioLength = audioFrame.length * 2; + + if (!needHeader) { + const buffer = new ArrayBuffer(audioLength); + const view = new DataView(buffer); + this.floatTo16BitPCM(view, 0, audioFrame); + + return buffer; + } + + const buffer = new ArrayBuffer(44 + audioLength); + + const bitsPerSample = 16; + const bytesPerSample = bitsPerSample / 8; + // We dont know ahead of time about the length of audio to stream. So set to 0. + const fileLength = 0; + + // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView + const view = new DataView(buffer); + + /* RIFF identifier */ + this.setString(view, 0, "RIFF"); + /* file length */ + view.setUint32(4, fileLength, true); + /* RIFF type & Format */ + this.setString(view, 8, "WAVEfmt "); + /* format chunk length */ + view.setUint32(16, 16, true); + /* sample format (raw) */ + view.setUint16(20, 1, true); + /* channel count */ + view.setUint16(22, this.privChannelCount, true); + /* sample rate */ + view.setUint32(24, this.privDesiredSampleRate, true); + /* byte rate (sample rate * block align) */ + view.setUint32(28, this.privDesiredSampleRate * this.privChannelCount * bytesPerSample, true); + /* block align (channel count * bytes per sample) */ + view.setUint16(32, this.privChannelCount * bytesPerSample, true); + /* bits per sample */ + view.setUint16(34, bitsPerSample, true); + /* data chunk identifier */ + this.setString(view, 36, "data"); + /* data chunk length */ + view.setUint32(40, fileLength, true); + + this.floatTo16BitPCM(view, 44, audioFrame); + + return buffer; + } + + private setString = (view: DataView, offset: number, str: string): void => { + for (let i = 0; i < str.length; i++) { + view.setUint8(offset + i, str.charCodeAt(i)); + } + } + + private floatTo16BitPCM = (view: DataView, offset: number, input: Float32Array): void => { + for (let i = 0; i < input.length; i++ , offset += 2) { + const s = Math.max(-1, Math.min(1, input[i])); + view.setInt16(offset, s < 0 ? s * 0x8000 : s * 0x7FFF, true); + } + } + + private downSampleAudioFrame = ( + srcFrame: Float32Array, + srcRate: number, + dstRate: number): Float32Array => { + + if (dstRate === srcRate || dstRate > srcRate) { + return srcFrame; + } + + const ratio = srcRate / dstRate; + const dstLength = Math.round(srcFrame.length / ratio); + const dstFrame = new Float32Array(dstLength); + let srcOffset = 0; + let dstOffset = 0; + while (dstOffset < dstLength) { + const nextSrcOffset = Math.round((dstOffset + 1) * ratio); + let accum = 0; + let count = 0; + while (srcOffset < nextSrcOffset && srcOffset < srcFrame.length) { + accum += srcFrame[srcOffset++]; + count++; + } + dstFrame[dstOffset++] = accum / count; + } + + return dstFrame; + } +} diff --git a/src/common/Storage.ts b/src/common/Storage.ts new file mode 100644 index 00000000..ad931a62 --- /dev/null +++ b/src/common/Storage.ts @@ -0,0 +1,35 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +import { ArgumentNullError } from "./Error"; +import { IKeyValueStorage } from "./IKeyValueStorage"; +import { InMemoryStorage } from "./InMemoryStorage"; + +export class Storage { + private static privSessionStorage: IKeyValueStorage = new InMemoryStorage(); + private static privLocalStorage: IKeyValueStorage = new InMemoryStorage(); + + public static setSessionStorage = (sessionStorage: IKeyValueStorage): void => { + if (!sessionStorage) { + throw new ArgumentNullError("sessionStorage"); + } + + Storage.privSessionStorage = sessionStorage; + } + + public static setLocalStorage = (localStorage: IKeyValueStorage): void => { + if (!localStorage) { + throw new ArgumentNullError("localStorage"); + } + + Storage.privLocalStorage = localStorage; + } + + public static get session(): IKeyValueStorage { + return Storage.privSessionStorage; + } + + public static get local(): IKeyValueStorage { + return Storage.privLocalStorage; + } +} diff --git a/src/common/Stream.ts b/src/common/Stream.ts new file mode 100644 index 00000000..853b05ed --- /dev/null +++ b/src/common/Stream.ts @@ -0,0 +1,137 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +import { InvalidOperationError } from "./Error"; +import { createNoDashGuid } from "./Guid"; +import { IStringDictionary } from "./IDictionary"; +import { Promise } from "./Promise"; +import { Queue } from "./Queue"; +import { IStreamChunk } from "./Stream"; + +export interface IStreamChunk { + isEnd: boolean; + buffer: TBuffer; +} + +export class Stream { + private privId: string; + private privReaderIdCounter: number = 1; + private privStreambuffer: Array>; + private privIsEnded: boolean = false; + private privReaderQueues: IStringDictionary>>; + + public constructor(streamId?: string) { + this.privId = streamId ? streamId : createNoDashGuid(); + this.privStreambuffer = []; + this.privReaderQueues = {}; + } + + public get isClosed(): boolean { + return this.privIsEnded; + } + + public get id(): string { + return this.privId; + } + + public write = (buffer2: TBuffer): void => { + this.throwIfClosed(); + this.writeStreamChunk({ + buffer: buffer2, + isEnd: false, + }); + } + + public getReader = (): StreamReader => { + const readerId = this.privReaderIdCounter; + this.privReaderIdCounter++; + const readerQueue = new Queue>(); + const currentLength = this.privStreambuffer.length; + this.privReaderQueues[readerId] = readerQueue; + for (let i = 0; i < currentLength; i++) { + readerQueue.enqueue(this.privStreambuffer[i]); + } + return new StreamReader( + this.privId, + readerQueue, + () => { + delete this.privReaderQueues[readerId]; + }); + } + + public close = (): void => { + if (!this.privIsEnded) { + this.writeStreamChunk({ + buffer: null, + isEnd: true, + }); + this.privIsEnded = true; + } + } + + private writeStreamChunk = (streamChunk: IStreamChunk): void => { + this.throwIfClosed(); + this.privStreambuffer.push(streamChunk); + for (const readerId in this.privReaderQueues) { + if (!this.privReaderQueues[readerId].isDisposed()) { + try { + this.privReaderQueues[readerId].enqueue(streamChunk); + } catch (e) { + // Do nothing + } + } + } + } + + private throwIfClosed = (): void => { + if (this.privIsEnded) { + throw new InvalidOperationError("Stream closed"); + } + } +} + +// tslint:disable-next-line:max-classes-per-file +export class StreamReader { + private privReaderQueue: Queue>; + private privOnClose: () => void; + private privIsClosed: boolean = false; + private privStreamId: string; + + public constructor(streamId: string, readerQueue: Queue>, onClose: () => void) { + this.privReaderQueue = readerQueue; + this.privOnClose = onClose; + this.privStreamId = streamId; + } + + public get isClosed(): boolean { + return this.privIsClosed; + } + + public get streamId(): string { + return this.privStreamId; + } + + public read = (): Promise> => { + if (this.isClosed) { + throw new InvalidOperationError("StreamReader closed"); + } + + return this.privReaderQueue + .dequeue() + .onSuccessContinueWith((streamChunk: IStreamChunk) => { + if (streamChunk.isEnd) { + this.privReaderQueue.dispose("End of stream reached"); + } + + return streamChunk; + }); + } + + public close = (): void => { + if (!this.privIsClosed) { + this.privIsClosed = true; + this.privReaderQueue.dispose("StreamReader closed"); + this.privOnClose(); + } + } +} diff --git a/src/sdk/Audio/AudioConfig.ts b/src/sdk/Audio/AudioConfig.ts new file mode 100644 index 00000000..7aace969 --- /dev/null +++ b/src/sdk/Audio/AudioConfig.ts @@ -0,0 +1,165 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +import { AudioStreamFormat } from "../../../src/sdk/Exports"; +import { FileAudioSource, MicAudioSource, PcmRecorder } from "../../common.browser/Exports"; +import { AudioSourceEvent, EventSource, IAudioSource, IAudioStreamNode, Promise } from "../../common/Exports"; +import { AudioInputStream, PullAudioInputStreamCallback } from "../Exports"; +import { PullAudioInputStreamImpl, PushAudioInputStreamImpl } from "./AudioInputStream"; + +/** + * Represents audio input configuration used for specifying what type of input to use (microphone, file, stream). + * @class AudioConfig + */ +export abstract class AudioConfig { + /** + * Creates an AudioConfig object representing the default microphone on the system. + * @member AudioConfig.fromDefaultMicrophoneInput + * @function + * @public + * @returns {AudioConfig} The audio input configuration being created. + */ + public static fromDefaultMicrophoneInput(): AudioConfig { + const pcmRecorder = new PcmRecorder(); + return new AudioConfigImpl(new MicAudioSource(pcmRecorder)); + } + + /** + * Creates an AudioConfig object representing the specified file. + * @member AudioConfig.fromWavFileInput + * @function + * @public + * @param {File} fileName - Specifies the audio input file. Currently, only WAV / PCM with 16-bit + * samples, 16 kHz sample rate, and a single channel (Mono) is supported. + * @returns {AudioConfig} The audio input configuration being created. + */ + public static fromWavFileInput(file: File): AudioConfig { + return new AudioConfigImpl(new FileAudioSource(file)); + } + + /** + * Creates an AudioConfig object representing the specified stream. + * @member AudioConfig.fromStreamInput + * @function + * @public + * @param {AudioInputStream | PullAudioInputStreamCallback} audioStream - Specifies the custom audio input + * stream. Currently, only WAV / PCM with 16-bit samples, 16 kHz sample rate, and a single channel + * (Mono) is supported. + * @returns {AudioConfig} The audio input configuration being created. + */ + public static fromStreamInput(audioStream: AudioInputStream | PullAudioInputStreamCallback): AudioConfig { + if (audioStream instanceof PullAudioInputStreamCallback) { + return new AudioConfigImpl(new PullAudioInputStreamImpl(audioStream as PullAudioInputStreamCallback)); + } + + if (audioStream instanceof AudioInputStream) { + return new AudioConfigImpl(audioStream as PushAudioInputStreamImpl); + } + + throw new Error("Not Supported Type"); + } + + /** + * Explicitly frees any external resource attached to the object + * @member AudioConfig.prototype.close + * @function + * @public + */ + public abstract close(): void; +} + +/** + * Represents audio input stream used for custom audio input configurations. + * @private + * @class AudioConfigImpl + */ +// tslint:disable-next-line:max-classes-per-file +export class AudioConfigImpl extends AudioConfig implements IAudioSource { + private privSource: IAudioSource; + + /** + * Creates and initializes an instance of this class. + * @constructor + * @param {IAudioSource} source - An audio source. + */ + public constructor(source: IAudioSource) { + super(); + this.privSource = source; + } + + /** + * Format information for the audio + */ + public get format(): AudioStreamFormat { + return this.privSource.format; + } + + /** + * @member AudioConfigImpl.prototype.close + * @function + * @public + */ + public close(): void { + this.privSource.turnOff(); + } + + /** + * @member AudioConfigImpl.prototype.id + * @function + * @public + */ + public id(): string { + return this.privSource.id(); + } + + /** + * @member AudioConfigImpl.prototype.turnOn + * @function + * @public + * @returns {Promise} A promise. + */ + public turnOn(): Promise { + return this.privSource.turnOn(); + } + + /** + * @member AudioConfigImpl.prototype.attach + * @function + * @public + * @param {string} audioNodeId - The audio node id. + * @returns {Promise} A promise. + */ + public attach(audioNodeId: string): Promise { + return this.privSource.attach(audioNodeId); + } + + /** + * @member AudioConfigImpl.prototype.detach + * @function + * @public + * @param {string} audioNodeId - The audio node id. + */ + public detach(audioNodeId: string): void { + return this.detach(audioNodeId); + } + + /** + * @member AudioConfigImpl.prototype.turnOff + * @function + * @public + * @returns {Promise} A promise. + */ + public turnOff(): Promise { + return this.privSource.turnOff(); + } + + /** + * @member AudioConfigImpl.prototype.events + * @function + * @public + * @returns {EventSource} An event source for audio events. + */ + public get events(): EventSource { + return this.privSource.events; + } +} diff --git a/src/sdk/Audio/AudioInputStream.ts b/src/sdk/Audio/AudioInputStream.ts new file mode 100644 index 00000000..1d0fc413 --- /dev/null +++ b/src/sdk/Audio/AudioInputStream.ts @@ -0,0 +1,386 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +import { createNoDashGuid } from "../../../src/common/Guid"; +import { + AudioSourceEvent, + AudioSourceInitializingEvent, + AudioSourceReadyEvent, + AudioStreamNodeAttachedEvent, + AudioStreamNodeAttachingEvent, + AudioStreamNodeDetachedEvent, + Events, + EventSource, + IAudioSource, + IAudioStreamNode, + IStreamChunk, + Promise, + PromiseHelper, + Stream, + StreamReader, +} from "../../common/Exports"; +import { AudioStreamFormat, PullAudioInputStreamCallback } from "../Exports"; +import { AudioStreamFormatImpl } from "./AudioStreamFormat"; + +const bufferSize: number = 4096; + +/** + * Represents audio input stream used for custom audio input configurations. + * @class AudioInputStream + */ +export abstract class AudioInputStream { + + /** + * Creates and initializes an instance. + * @constructor + */ + protected constructor() { } + + /** + * Creates a memory backed PushAudioInputStream with the specified audio format. + * @member AudioInputStream.createPushStream + * @function + * @public + * @param {AudioStreamFormat} format - The audio data format in which audio will be + * written to the push audio stream's write() method (currently only support 16 kHz 16bit mono PCM). + * @returns {PushAudioInputStream} The audio input stream being created. + */ + public static createPushStream(format?: AudioStreamFormat): PushAudioInputStream { + return PushAudioInputStream.create(format); + } + + /** + * Creates a PullAudioInputStream that delegates to the specified callback interface for read() + * and close() methods. + * @member AudioInputStream.createPullStream + * @function + * @public + * @param {PullAudioInputStreamCallback} callback - The custom audio input object, derived from + * PullAudioInputStreamCallback + * @param {AudioStreamFormat} format - The audio data format in which audio will be returned from + * the callback's read() method (currently only support 16 kHz 16bit mono PCM). + * @returns {PullAudioInputStream} The audio input stream being created. + */ + public static createPullStream(callback: PullAudioInputStreamCallback, format?: AudioStreamFormat): PullAudioInputStream { + return PullAudioInputStream.create(callback, format); + // throw new Error("Oops"); + } + + /** + * Explicitly frees any external resource attached to the object + * @member AudioInputStream.prototype.close + * @function + * @public + */ + public abstract close(): void; +} + +/** + * Represents memory backed push audio input stream used for custom audio input configurations. + * @class PushAudioInputStream + */ +// tslint:disable-next-line:max-classes-per-file +export abstract class PushAudioInputStream extends AudioInputStream { + + /** + * Creates a memory backed PushAudioInputStream with the specified audio format. + * @member PushAudioInputStream.create + * @function + * @public + * @param {AudioStreamFormat} format - The audio data format in which audio will be written to the + * push audio stream's write() method (currently only support 16 kHz 16bit mono PCM). + * @returns {PushAudioInputStream} The push audio input stream being created. + */ + public static create(format?: AudioStreamFormat): PushAudioInputStream { + return new PushAudioInputStreamImpl(format); + } + + /** + * Writes the audio data specified by making an internal copy of the data. + * @member PushAudioInputStream.prototype.write + * @function + * @public + * @param {ArrayBuffer} dataBuffer - The audio buffer of which this function will make a copy. + */ + public abstract write(dataBuffer: ArrayBuffer): void; + + /** + * Closes the stream. + * @member PushAudioInputStream.prototype.close + * @function + * @public + */ + public abstract close(): void; +} + +/** + * Represents memory backed push audio input stream used for custom audio input configurations. + * @private + * @class PushAudioInputStreamImpl + */ +// tslint:disable-next-line:max-classes-per-file +export class PushAudioInputStreamImpl extends PushAudioInputStream implements IAudioSource { + + private privFormat: AudioStreamFormat; + private privId: string; + private privEvents: EventSource; + private privStream: Stream = new Stream(); + + /** + * Creates and initalizes an instance with the given values. + * @constructor + * @param {AudioStreamFormat} format - The audio stream format. + */ + public constructor(format?: AudioStreamFormat) { + super(); + if (format === undefined) { + this.privFormat = AudioStreamFormatImpl.getDefaultInputFormat(); + } else { + this.privFormat = format; + } + this.privEvents = new EventSource(); + this.privId = createNoDashGuid(); + } + + /** + * Format information for the audio + */ + public get format(): AudioStreamFormat { + return this.privFormat; + } + + /** + * Writes the audio data specified by making an internal copy of the data. + * @member PushAudioInputStreamImpl.prototype.write + * @function + * @public + * @param {ArrayBuffer} dataBuffer - The audio buffer of which this function will make a copy. + */ + public write(dataBuffer: ArrayBuffer): void { + // Break the data up into smaller chunks if needed. + let i: number; + for (i = bufferSize - 1; i < dataBuffer.byteLength; i += bufferSize) { + this.privStream.write(dataBuffer.slice(i - (bufferSize - 1), i + 1)); + } + + if ((i - (bufferSize - 1)) !== dataBuffer.byteLength) { + this.privStream.write(dataBuffer.slice(i - (bufferSize - 1), dataBuffer.byteLength)); + } + } + + /** + * Closes the stream. + * @member PushAudioInputStreamImpl.prototype.close + * @function + * @public + */ + public close(): void { + this.privStream.close(); + } + + public id(): string { + return this.privId; + } + + public turnOn(): Promise { + this.onEvent(new AudioSourceInitializingEvent(this.privId)); // no stream id + this.onEvent(new AudioSourceReadyEvent(this.privId)); + return PromiseHelper.fromResult(true); + } + + public attach(audioNodeId: string): Promise { + this.onEvent(new AudioStreamNodeAttachingEvent(this.privId, audioNodeId)); + + return this.turnOn() + .onSuccessContinueWith>((_: boolean) => { + // For now we support a single parallel reader of the pushed stream. + // So we can simiply hand the stream to the recognizer and let it recognize. + + return this.privStream.getReader(); + }) + .onSuccessContinueWith((streamReader: StreamReader) => { + this.onEvent(new AudioStreamNodeAttachedEvent(this.privId, audioNodeId)); + + return { + detach: () => { + streamReader.close(); + this.onEvent(new AudioStreamNodeDetachedEvent(this.privId, audioNodeId)); + this.turnOff(); + }, + id: () => { + return audioNodeId; + }, + read: () => { + return streamReader.read(); + }, + }; + }); + } + + public detach(audioNodeId: string): void { + this.onEvent(new AudioStreamNodeDetachedEvent(this.privId, audioNodeId)); + } + + public turnOff(): Promise { + return PromiseHelper.fromResult(false); + } + + public get events(): EventSource { + return this.privEvents; + } + + private onEvent = (event: AudioSourceEvent): void => { + this.privEvents.onEvent(event); + Events.instance.onEvent(event); + } +} + +/* + * Represents audio input stream used for custom audio input configurations. + * @class PullAudioInputStream + */ +// tslint:disable-next-line:max-classes-per-file +export abstract class PullAudioInputStream extends AudioInputStream { + /** + * Creates and initializes and instance. + * @constructor + */ + protected constructor() { super(); } + + /** + * Creates a PullAudioInputStream that delegates to the specified callback interface for + * read() and close() methods, using the default format (16 kHz 16bit mono PCM). + * @member PullAudioInputStream.create + * @function + * @public + * @param {PullAudioInputStreamCallback} callback - The custom audio input object, + * derived from PullAudioInputStreamCustomCallback + * @param {AudioStreamFormat} format - The audio data format in which audio will be + * returned from the callback's read() method (currently only support 16 kHz 16bit mono PCM). + * @returns {PullAudioInputStream} The push audio input stream being created. + */ + public static create(callback: PullAudioInputStreamCallback, format?: AudioStreamFormat): PullAudioInputStream { + return new PullAudioInputStreamImpl(callback, format); + } + + /** + * Explicitly frees any external resource attached to the object + * @member PullAudioInputStream.prototype.close + * @function + * @public + */ + public abstract close(): void; + +} + +/** + * Represents audio input stream used for custom audio input configurations. + * @private + * @class PullAudioInputStreamImpl + */ +// tslint:disable-next-line:max-classes-per-file +export class PullAudioInputStreamImpl extends PullAudioInputStream implements IAudioSource { + + private privCallback: PullAudioInputStreamCallback; + private privFormat: AudioStreamFormat; + private privId: string; + private privEvents: EventSource; + private privIsClosed: boolean; + + /** + * Creates a PullAudioInputStream that delegates to the specified callback interface for + * read() and close() methods, using the default format (16 kHz 16bit mono PCM). + * @constructor + * @param {PullAudioInputStreamCallback} callback - The custom audio input object, + * derived from PullAudioInputStreamCustomCallback + * @param {AudioStreamFormat} format - The audio data format in which audio will be + * returned from the callback's read() method (currently only support 16 kHz 16bit mono PCM). + */ + public constructor(callback: PullAudioInputStreamCallback, format?: AudioStreamFormat) { + super(); + if (undefined === format) { + this.privFormat = AudioStreamFormat.getDefaultInputFormat(); + } else { + this.privFormat = format; + } + this.privEvents = new EventSource(); + this.privId = createNoDashGuid(); + this.privCallback = callback; + this.privIsClosed = false; + } + + /** + * Format information for the audio + */ + public get format(): AudioStreamFormat { + return this.privFormat; + } + + /** + * Closes the stream. + * @member PullAudioInputStreamImpl.prototype.close + * @function + * @public + */ + public close(): void { + this.privIsClosed = true; + this.privCallback.close(); + } + + public id(): string { + return this.privId; + } + + public turnOn(): Promise { + this.onEvent(new AudioSourceInitializingEvent(this.privId)); // no stream id + this.onEvent(new AudioSourceReadyEvent(this.privId)); + return PromiseHelper.fromResult(true); + } + + public attach(audioNodeId: string): Promise { + this.onEvent(new AudioStreamNodeAttachingEvent(this.privId, audioNodeId)); + + return this.turnOn() + .onSuccessContinueWith((result: boolean) => { + this.onEvent(new AudioStreamNodeAttachedEvent(this.privId, audioNodeId)); + + return { + detach: () => { + this.privCallback.close(); + this.onEvent(new AudioStreamNodeDetachedEvent(this.privId, audioNodeId)); + this.turnOff(); + }, + id: () => { + return audioNodeId; + }, + read: (): Promise> => { + const readBuff: ArrayBuffer = new ArrayBuffer(bufferSize); + const pulledBytes: number = this.privCallback.read(readBuff); + + return PromiseHelper.fromResult>({ + buffer: readBuff.slice(0, pulledBytes), + isEnd: this.privIsClosed, + }); + }, + }; + }); + + } + + public detach(audioNodeId: string): void { + this.onEvent(new AudioStreamNodeDetachedEvent(this.privId, audioNodeId)); + } + + public turnOff(): Promise { + return PromiseHelper.fromResult(false); + } + + public get events(): EventSource { + return this.privEvents; + } + + private onEvent = (event: AudioSourceEvent): void => { + this.privEvents.onEvent(event); + Events.instance.onEvent(event); + } +} diff --git a/src/sdk/Audio/AudioStreamFormat.ts b/src/sdk/Audio/AudioStreamFormat.ts new file mode 100644 index 00000000..b0fb4ac2 --- /dev/null +++ b/src/sdk/Audio/AudioStreamFormat.ts @@ -0,0 +1,134 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +/** + * Represents audio stream format used for custom audio input configurations. + * @class AudioStreamFormat + */ +export abstract class AudioStreamFormat { + /** + * Creates an audio stream format object representing the default audio stream + * format (16KHz 16bit mono PCM). + * @member AudioStreamFormat.getDefaultInputFormat + * @function + * @public + * @returns {AudioStreamFormat} The audio stream format being created. + */ + public static getDefaultInputFormat(): AudioStreamFormat { + return AudioStreamFormatImpl.getDefaultInputFormat(); + } + + /** + * Creates an audio stream format object with the specified pcm waveformat characteristics. + * @member AudioStreamFormat.getWaveFormatPCM + * @function + * @public + * @param {number} samplesPerSecond - Sample rate, in samples per second (Hertz). + * @param {number} bitsPerSample - Bits per sample, typically 16. + * @param {number} channels - Number of channels in the waveform-audio data. Monaural data + * uses one channel and stereo data uses two channels. + * @returns {AudioStreamFormat} The audio stream format being created. + */ + public static getWaveFormatPCM(samplesPerSecond: number, bitsPerSample: number, channels: number): AudioStreamFormat { + return new AudioStreamFormatImpl(samplesPerSecond, bitsPerSample, channels); + } + + /** + * Explicitly frees any external resource attached to the object + * @member AudioStreamFormat.prototype.close + * @function + * @public + */ + public abstract close(): void; +} + +/** + * @private + * @class AudioStreamFormatImpl + */ +// tslint:disable-next-line:max-classes-per-file +export class AudioStreamFormatImpl extends AudioStreamFormat { + /** + * Creates an instance with the given values. + * @constructor + * @param {number} samplesPerSec - Samples per second. + * @param {number} bitsPerSample - Bits per sample. + * @param {number} channels - Number of channels. + */ + public constructor(samplesPerSec: number = 16000, bitsPerSample: number = 16, channels: number = 1) { + super(); + this.formatTag = 1; + this.bitsPerSample = bitsPerSample; + this.samplesPerSec = samplesPerSec; + this.channels = channels; + this.avgBytesPerSec = this.samplesPerSec * this.channels * (this.bitsPerSample / 8); + this.blockAlign = this.channels * Math.max(this.bitsPerSample, 8); + } + + /** + * Retrieves the default input format. + * @member AudioStreamFormatImpl.getDefaultInputFormat + * @function + * @public + * @returns {AudioStreamFormatImpl} The default input format. + */ + public static getDefaultInputFormat(): AudioStreamFormatImpl { + return new AudioStreamFormatImpl(); + } + + /** + * Closes the configuration object. + * @member AudioStreamFormatImpl.prototype.close + * @function + * @public + */ + public close(): void { return; } + + /** + * The format of the audio, valid values: 1 (PCM) + * @member AudioStreamFormatImpl.prototype.formatTag + * @function + * @public + */ + public formatTag: number; + + /** + * The number of channels, valid values: 1 (Mono). + * @member AudioStreamFormatImpl.prototype.channels + * @function + * @public + */ + public channels: number; + + /** + * The sample rate, valid values: 16000. + * @member AudioStreamFormatImpl.prototype.samplesPerSec + * @function + * @public + */ + public samplesPerSec: number; + + /** + * The bits per sample, valid values: 16 + * @member AudioStreamFormatImpl.prototype.b + * @function + * @public + */ + public bitsPerSample: number; + + /** + * Average bytes per second, usually calculated as nSamplesPerSec * nChannels * ceil(wBitsPerSample, 8). + * @member AudioStreamFormatImpl.prototype.avgBytesPerSec + * @function + * @public + */ + public avgBytesPerSec: number; + + /** + * The size of a single frame, valid values: nChannels * ceil(wBitsPerSample, 8). + * @member AudioStreamFormatImpl.prototype.blockAlign + * @function + * @public + */ + public blockAlign: number; +} diff --git a/src/sdk/Audio/PullAudioInputStreamCallback.ts b/src/sdk/Audio/PullAudioInputStreamCallback.ts new file mode 100644 index 00000000..3cf17116 --- /dev/null +++ b/src/sdk/Audio/PullAudioInputStreamCallback.ts @@ -0,0 +1,28 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + /** + * An abstract base class that defines callback methods (read() and close()) for + * custom audio input streams). + * @class PullAudioInputStreamCallback + */ +export abstract class PullAudioInputStreamCallback { + + /** + * Reads data from audio input stream into the data buffer. The maximal number of bytes + * to be read is determined by the size of dataBuffer. + * @member PullAudioInputStreamCallback.prototype.read + * @function + * @public + * @param {ArrayBuffer} dataBuffer - The byte array to store the read data. + * @returns {number} the number of bytes have been read. + */ + public abstract read(dataBuffer: ArrayBuffer): number; + + /** + * Closes the audio input stream. + * @member PullAudioInputStreamCallback.prototype.close + * @function + * @public + */ + public abstract close(): void; +} diff --git a/src/sdk/CancellationDetails.ts b/src/sdk/CancellationDetails.ts new file mode 100644 index 00000000..1245f6e6 --- /dev/null +++ b/src/sdk/CancellationDetails.ts @@ -0,0 +1,84 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +import { CancellationErrorCodePropertyName, EnumTranslation, SimpleSpeechPhrase } from "../common.speech/Exports"; +import { CancellationErrorCode, CancellationReason, RecognitionResult } from "./Exports"; + +/** + * Contains detailed information about why a result was canceled. + * @class CancellationDetails + */ +export class CancellationDetails { + private privReason: CancellationReason; + private privErrorDetails: string; + private privErrorCode: CancellationErrorCode; + + /** + * Creates and initializes an instance of this class. + * @constructor + * @param {CancellationReason} reason - The cancellation reason. + * @param {string} errorDetails - The error details, if provided. + */ + private constructor(reason: CancellationReason, errorDetails: string, errorCode: CancellationErrorCode) { + this.privReason = reason; + this.privErrorDetails = errorDetails; + this.privErrorCode = errorCode; + } + + /** + * Creates an instance of CancellationDetails object for the canceled RecognitionResult. + * @member CancellationDetails.fromResult + * @function + * @public + * @param {RecognitionResult} result - The result that was canceled. + * @returns {CancellationDetails} The cancellation details object being created. + */ + public static fromResult(result: RecognitionResult): CancellationDetails { + let reason = CancellationReason.Error; + let errorCode: CancellationErrorCode = CancellationErrorCode.NoError; + + if (!!result.json) { + const simpleSpeech: SimpleSpeechPhrase = SimpleSpeechPhrase.fromJSON(result.json); + reason = EnumTranslation.implTranslateCancelResult(simpleSpeech.RecognitionStatus); + } + + if (!!result.properties) { + errorCode = (CancellationErrorCode as any)[result.properties.getProperty(CancellationErrorCodePropertyName, CancellationErrorCode[CancellationErrorCode.NoError])]; + } + + return new CancellationDetails(reason, result.errorDetails, errorCode); + + } + + /** + * The reason the recognition was canceled. + * @member CancellationDetails.prototype.reason + * @function + * @public + * @returns {CancellationReason} Specifies the reason canceled. + */ + public get reason(): CancellationReason { + return this.privReason; + } + + /** + * In case of an unsuccessful recognition, provides details of the occurred error. + * @member CancellationDetails.prototype.errorDetails + * @function + * @public + * @returns {string} A String that represents the error details. + */ + public get errorDetails(): string { + return this.privErrorDetails; + } + + /** + * The error code in case of an unsuccessful recognition. + * Added in version 1.1.0. + * @return An error code that represents the error reason. + */ + public get ErrorCode(): CancellationErrorCode { + return this.privErrorCode; + } + +} diff --git a/src/sdk/CancellationErrorCodes.ts b/src/sdk/CancellationErrorCodes.ts new file mode 100644 index 00000000..7b24d075 --- /dev/null +++ b/src/sdk/CancellationErrorCodes.ts @@ -0,0 +1,49 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +/** + * Defines error code in case that CancellationReason is Error. + * Added in version 1.1.0. + */ +export enum CancellationErrorCode { + /** + * Indicates that no error occurred during speech recognition. + */ + NoError, + + /** + * Indicates an authentication error. + */ + AuthenticationFailure, + + /** + * Indicates that one or more recognition parameters are invalid. + */ + BadRequestParameters, + + /** + * Indicates that the number of parallel requests exceeded the number of allowed + * concurrent transcriptions for the subscription. + */ + TooManyRequests, + + /** + * Indicates a connection error. + */ + ConnectionFailure, + + /** + * Indicates a time-out error when waiting for response from service. + */ + ServiceTimeout, + + /** + * Indicates that an error is returned by the service. + */ + ServiceError, + + /** + * Indicates an unexpected runtime error. + */ + RuntimeError, +} diff --git a/src/sdk/CancellationReason.ts b/src/sdk/CancellationReason.ts new file mode 100644 index 00000000..2ae4b5fe --- /dev/null +++ b/src/sdk/CancellationReason.ts @@ -0,0 +1,20 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +/** + * Defines the possible reasons a recognition result might be canceled. + * @class CancellationReason + */ +export enum CancellationReason { + /** + * Indicates that an error occurred during speech recognition. + * @member CancellationReason.Error + */ + Error, + + /** + * Indicates that the end of the audio stream was reached. + * @member CancellationReason.EndOfStream + */ + EndOfStream, +} diff --git a/src/sdk/Contracts.ts b/src/sdk/Contracts.ts new file mode 100644 index 00000000..6c997409 --- /dev/null +++ b/src/sdk/Contracts.ts @@ -0,0 +1,52 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +/** + * @class Contracts + * @private + */ +export class Contracts { + public static throwIfNullOrUndefined(param: any, name: string): void { + if (param === undefined || param === null) { + throw new Error("throwIfNullOrUndefined:" + name); + } + } + + public static throwIfNull(param: any, name: string): void { + if (param === null) { + throw new Error("throwIfNull:" + name); + } + } + + public static throwIfNullOrWhitespace(param: string, name: string): void { + Contracts.throwIfNullOrUndefined(param, name); + + if (("" + param).trim().length < 1) { + throw new Error("throwIfNullOrWhitespace:" + name); + } + } + + public static throwIfDisposed(isDisposed: boolean): void { + if (isDisposed) { + throw new Error("the object is already disposed"); + } + } + + public static throwIfArrayEmptyOrWhitespace(array: string[], name: string): void { + Contracts.throwIfNullOrUndefined(array, name); + + if (array.length === 0) { + throw new Error("throwIfArrayEmptyOrWhitespace:" + name); + } + + for (const item of array) { + Contracts.throwIfNullOrWhitespace(item, name); + } + } + + public static throwIfFileDoesNotExist(param: any, name: string): void { + Contracts.throwIfNullOrWhitespace(param, name); + + // TODO check for file existence. + } +} diff --git a/src/sdk/Exports.ts b/src/sdk/Exports.ts new file mode 100644 index 00000000..4baa1a7c --- /dev/null +++ b/src/sdk/Exports.ts @@ -0,0 +1,39 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +export { AudioConfig } from "./Audio/AudioConfig"; +export { AudioStreamFormat } from "./Audio/AudioStreamFormat"; +export { AudioInputStream, PullAudioInputStream, PushAudioInputStream } from "./Audio/AudioInputStream"; +export { CancellationReason } from "./CancellationReason"; +export { PullAudioInputStreamCallback } from "./Audio/PullAudioInputStreamCallback"; +export { KeywordRecognitionModel } from "./KeywordRecognitionModel"; +export { SessionEventArgs } from "./SessionEventArgs"; +export { RecognitionEventArgs } from "./RecognitionEventArgs"; +export { OutputFormat } from "./OutputFormat"; +export { IntentRecognitionEventArgs } from "./IntentRecognitionEventArgs"; +export { RecognitionResult } from "./RecognitionResult"; +export { SpeechRecognitionResult } from "./SpeechRecognitionResult"; +export { IntentRecognitionResult } from "./IntentRecognitionResult"; +export { LanguageUnderstandingModel } from "./LanguageUnderstandingModel"; +export { SpeechRecognitionEventArgs } from "./SpeechRecognitionEventArgs"; +export { SpeechRecognitionCanceledEventArgs } from "./SpeechRecognitionCanceledEventArgs"; +export { TranslationRecognitionEventArgs } from "./TranslationRecognitionEventArgs"; +export { TranslationSynthesisEventArgs } from "./TranslationSynthesisEventArgs"; +export { TranslationRecognitionResult } from "./TranslationRecognitionResult"; +export { TranslationSynthesisResult } from "./TranslationSynthesisResult"; +export { ResultReason } from "./ResultReason"; +export { SpeechConfig } from "./SpeechConfig"; +export { SpeechTranslationConfig } from "./SpeechTranslationConfig"; +export { PropertyCollection } from "./PropertyCollection"; +export { PropertyId } from "./PropertyId"; +export { Recognizer } from "./Recognizer"; +export { SpeechRecognizer } from "./SpeechRecognizer"; +export { IntentRecognizer } from "./IntentRecognizer"; +export { TranslationRecognizer } from "./TranslationRecognizer"; +export { Translations } from "./Translations"; +export { NoMatchReason } from "./NoMatchReason"; +export { NoMatchDetails } from "./NoMatchDetails"; +export { TranslationRecognitionCanceledEventArgs } from "./TranslationRecognitionCanceledEventArgs"; +export { IntentRecognitionCanceledEventArgs } from "./IntentRecognitionCanceledEventArgs"; +export { CancellationDetails } from "./CancellationDetails"; +export { CancellationErrorCode } from "./CancellationErrorCodes"; diff --git a/src/sdk/IntentRecognitionCanceledEventArgs.ts b/src/sdk/IntentRecognitionCanceledEventArgs.ts new file mode 100644 index 00000000..648f5ba1 --- /dev/null +++ b/src/sdk/IntentRecognitionCanceledEventArgs.ts @@ -0,0 +1,66 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +import { CancellationErrorCode, CancellationReason, IntentRecognitionEventArgs, IntentRecognitionResult } from "./Exports"; + +/** + * Define payload of intent recognition canceled result events. + * @class IntentRecognitionCanceledEventArgs + */ +export class IntentRecognitionCanceledEventArgs extends IntentRecognitionEventArgs { + private privReason: CancellationReason; + private privErrorDetails: string; + private privErrorCode: CancellationErrorCode; + + /** + * Creates and initializes an instance of this class. + * @constructor + * @param {CancellationReason} result - The result of the intent recognition. + * @param {string} offset - The offset. + * @param {IntentRecognitionResult} sessionId - The session id. + */ + public constructor( + reason: CancellationReason, + errorDetails: string, + errorCode: CancellationErrorCode, + result?: IntentRecognitionResult, + offset?: number, + sessionId?: string) { + super(result, offset, sessionId); + + this.privReason = reason; + this.privErrorDetails = errorDetails; + this.privErrorCode = errorCode; + } + + /** + * The reason the recognition was canceled. + * @member IntentRecognitionCanceledEventArgs.prototype.reason + * @function + * @public + * @returns {CancellationReason} Specifies the reason canceled. + */ + public get reason(): CancellationReason { + return this.privReason; + } + + /** + * The error code in case of an unsuccessful recognition. + * Added in version 1.1.0. + * @return An error code that represents the error reason. + */ + public get errorCode(): CancellationErrorCode { + return this.privErrorCode; + } + + /** + * In case of an unsuccessful recognition, provides details of the occurred error. + * @member IntentRecognitionCanceledEventArgs.prototype.errorDetails + * @function + * @public + * @returns {string} A String that represents the error details. + */ + public get errorDetails(): string { + return this.privErrorDetails; + } +} diff --git a/src/sdk/IntentRecognitionEventArgs.ts b/src/sdk/IntentRecognitionEventArgs.ts new file mode 100644 index 00000000..55ebbb58 --- /dev/null +++ b/src/sdk/IntentRecognitionEventArgs.ts @@ -0,0 +1,36 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +import { IntentRecognitionResult, RecognitionEventArgs } from "./Exports"; + +/** + * Intent recognition result event arguments. + * @class + */ +export class IntentRecognitionEventArgs extends RecognitionEventArgs { + private privResult: IntentRecognitionResult; + + /** + * Creates and initializes an instance of this class. + * @constructor + * @param result - The result of the intent recognition. + * @param offset - The offset. + * @param sessionId - The session id. + */ + public constructor(result: IntentRecognitionResult, offset?: number, sessionId?: string) { + super(offset, sessionId); + + this.privResult = result; + } + + /** + * Represents the intent recognition result. + * @member IntentRecognitionEventArgs.prototype.result + * @function + * @public + * @returns {IntentRecognitionResult} Represents the intent recognition result. + */ + public get result(): IntentRecognitionResult { + return this.privResult; + } +} diff --git a/src/sdk/IntentRecognitionResult.ts b/src/sdk/IntentRecognitionResult.ts new file mode 100644 index 00000000..be5e5377 --- /dev/null +++ b/src/sdk/IntentRecognitionResult.ts @@ -0,0 +1,44 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +import { PropertyCollection, ResultReason, SpeechRecognitionResult } from "./Exports"; + +/** + * Intent recognition result. + * @class + */ +export class IntentRecognitionResult extends SpeechRecognitionResult { + private privIntentId: string; + + /** + * Creates and initializes an instance of this class. + * @constructor + * @param intentId - The intent id. + * @param resultId - The result id. + * @param reason - The reason. + * @param text - The recognized text. + * @param duration - The duration. + * @param offset - The offset into the stream. + * @param errorDetails - Error details, if provided. + * @param json - Additional Json, if provided. + * @param properties - Additional properties, if provided. + */ + constructor(intentId?: string, resultId?: string, reason?: ResultReason, text?: string, + duration?: number, offset?: number, errorDetails?: string, json?: string, + properties?: PropertyCollection) { + super(resultId, reason, text, duration, offset, errorDetails, json, properties); + + this.privIntentId = intentId; + } + + /** + * A String that represents the intent identifier being recognized. + * @member IntentRecognitionResult.prototype.intentId + * @function + * @public + * @returns {string} A String that represents the intent identifier being recognized. + */ + public get intentId(): string { + return this.privIntentId; + } +} diff --git a/src/sdk/IntentRecognizer.ts b/src/sdk/IntentRecognizer.ts new file mode 100644 index 00000000..7cb1a9da --- /dev/null +++ b/src/sdk/IntentRecognizer.ts @@ -0,0 +1,476 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +import { + AddedLmIntent, + IAuthentication, + IConnectionFactory, + IntentConnectionFactory, + IntentServiceRecognizer, + PlatformConfig, + RecognitionMode, + RecognizerConfig, + ServiceRecognizerBase, +} from "../common.speech/Exports"; +import { AudioConfigImpl } from "./Audio/AudioConfig"; +import { Contracts } from "./Contracts"; +import { + AudioConfig, + IntentRecognitionCanceledEventArgs, + IntentRecognitionEventArgs, + IntentRecognitionResult, + KeywordRecognitionModel, + LanguageUnderstandingModel, + PropertyCollection, + PropertyId, + Recognizer, + SpeechConfig, +} from "./Exports"; +import { LanguageUnderstandingModelImpl } from "./LanguageUnderstandingModel"; +import { SpeechConfigImpl } from "./SpeechConfig"; + +/** + * Intent recognizer. + * @class + */ +export class IntentRecognizer extends Recognizer { + private privDisposedIntentRecognizer: boolean; + private privProperties: PropertyCollection; + private privReco: ServiceRecognizerBase; + + private privAddedIntents: string[][]; + private privAddedLmIntents: { [id: string]: AddedLmIntent; }; + private privIntentDataSent: boolean; + private privUmbrellaIntent: AddedLmIntent; + + /** + * Initializes an instance of the IntentRecognizer. + * @constructor + * @param {SpeechConfig} speechConfig - The set of configuration properties. + * @param {AudioConfig} audioConfig - An optional audio input config associated with the recognizer + */ + public constructor(speechConfig: SpeechConfig, audioConfig?: AudioConfig) { + Contracts.throwIfNullOrUndefined(speechConfig, "speechConfig"); + const configImpl: SpeechConfigImpl = speechConfig as SpeechConfigImpl; + Contracts.throwIfNullOrUndefined(configImpl, "speechConfig"); + + super(audioConfig); + this.privIntentDataSent = false; + this.privAddedIntents = []; + this.privAddedLmIntents = {}; + + this.privDisposedIntentRecognizer = false; + this.privProperties = configImpl.properties; + + Contracts.throwIfNullOrWhitespace(this.properties.getProperty(PropertyId.SpeechServiceConnection_RecoLanguage), PropertyId[PropertyId.SpeechServiceConnection_RecoLanguage]); + } + + /** + * The event recognizing signals that an intermediate recognition result is received. + * @member IntentRecognizer.prototype.recognizing + * @function + * @public + */ + public recognizing: (sender: IntentRecognizer, event: IntentRecognitionEventArgs) => void; + + /** + * The event recognized signals that a final recognition result is received. + * @member IntentRecognizer.prototype.recognized + * @function + * @public + */ + public recognized: (sender: IntentRecognizer, event: IntentRecognitionEventArgs) => void; + + /** + * The event canceled signals that an error occurred during recognition. + * @member IntentRecognizer.prototype.canceled + * @function + * @public + */ + public canceled: (sender: IntentRecognizer, event: IntentRecognitionCanceledEventArgs) => void; + + /** + * Gets the spoken language of recognition. + * @member IntentRecognizer.prototype.speechRecognitionLanguage + * @function + * @public + * @returns {string} the spoken language of recognition. + */ + public get speechRecognitionLanguage(): string { + Contracts.throwIfDisposed(this.privDisposedIntentRecognizer); + + return this.properties.getProperty(PropertyId.SpeechServiceConnection_RecoLanguage); + } + + /** + * Gets the authorization token used to communicate with the service. + * @member IntentRecognizer.prototype.authorizationToken + * @function + * @public + * @returns {string} Authorization token. + */ + public get authorizationToken(): string { + return this.properties.getProperty(PropertyId.SpeechServiceAuthorization_Token); + } + + /** + * Sets the authorization token used to communicate with the service. + * @member IntentRecognizer.prototype.authorizationToken + * @function + * @public + * @param {string} value - Authorization token. + */ + public set authorizationToken(value: string) { + this.properties.setProperty(PropertyId.SpeechServiceAuthorization_Token, value); + } + + /** + * The collection of properties and their values defined for this IntentRecognizer. + * @member IntentRecognizer.prototype.properties + * @function + * @public + * @returns {PropertyCollection} The collection of properties and their + * values defined for this IntentRecognizer. + */ + public get properties(): PropertyCollection { + return this.privProperties; + } + + /** + * Starts intent recognition, and stops after the first utterance is recognized. + * The task returns the recognition text and intent as result. + * Note: RecognizeOnceAsync() returns when the first utterance has been recognized, + * so it is suitable only for single shot recognition like command or query. + * For long-running recognition, use StartContinuousRecognitionAsync() instead. + * @member IntentRecognizer.prototype.recognizeOnceAsync + * @function + * @public + * @param cb - Callback that received the recognition has finished with an IntentRecognitionResult. + * @param err - Callback invoked in case of an error. + */ + public recognizeOnceAsync(cb?: (e: IntentRecognitionResult) => void, err?: (e: string) => void): void { + try { + Contracts.throwIfDisposed(this.privDisposedIntentRecognizer); + + this.implCloseExistingRecognizer(); + + let contextJson: string; + + if (Object.keys(this.privAddedLmIntents).length !== 0 || undefined !== this.privUmbrellaIntent) { + contextJson = this.buildSpeechContext(); + this.privIntentDataSent = true; + } + + this.privReco = this.implRecognizerSetup( + RecognitionMode.Interactive, + this.properties, + this.audioConfig, + new IntentConnectionFactory()); + + const intentReco: IntentServiceRecognizer = this.privReco as IntentServiceRecognizer; + intentReco.setIntents(this.privAddedLmIntents, this.privUmbrellaIntent); + + this.implRecognizerStart(this.privReco, (e: IntentRecognitionResult) => { + this.implCloseExistingRecognizer(); + if (!!cb) { + cb(e); + } + }, (e: string) => { + this.implCloseExistingRecognizer(); + if (!!err) { + err(e); + } + }, contextJson); + + } catch (error) { + if (!!err) { + if (error instanceof Error) { + const typedError: Error = error as Error; + err(typedError.name + ": " + typedError.message); + } else { + err(error); + } + } + } + } + + /** + * Starts speech recognition, until stopContinuousRecognitionAsync() is called. + * User must subscribe to events to receive recognition results. + * @member IntentRecognizer.prototype.startContinuousRecognitionAsync + * @function + * @public + * @param cb - Callback invoked once the recognition has started. + * @param err - Callback invoked in case of an error. + */ + public startContinuousRecognitionAsync(cb?: () => void, err?: (e: string) => void): void { + try { + Contracts.throwIfDisposed(this.privDisposedIntentRecognizer); + + this.implCloseExistingRecognizer(); + + let contextJson: string; + + if (Object.keys(this.privAddedLmIntents).length !== 0) { + contextJson = this.buildSpeechContext(); + this.privIntentDataSent = true; + } + + this.privReco = this.implRecognizerSetup( + RecognitionMode.Conversation, + this.properties, + this.audioConfig, + new IntentConnectionFactory()); + + const intentReco: IntentServiceRecognizer = this.privReco as IntentServiceRecognizer; + intentReco.setIntents(this.privAddedLmIntents, this.privUmbrellaIntent); + + this.implRecognizerStart(this.privReco, undefined, undefined, contextJson); + + // report result to promise. + if (!!cb) { + try { + cb(); + } catch (e) { + if (!!err) { + err(e); + } + } + cb = undefined; + } + } catch (error) { + if (!!err) { + if (error instanceof Error) { + const typedError: Error = error as Error; + err(typedError.name + ": " + typedError.message); + } else { + err(error); + } + } + } + } + + /** + * Stops continuous intent recognition. + * @member IntentRecognizer.prototype.stopContinuousRecognitionAsync + * @function + * @public + * @param cb - Callback invoked once the recognition has stopped. + * @param err - Callback invoked in case of an error. + */ + public stopContinuousRecognitionAsync(cb?: () => void, err?: (e: string) => void): void { + try { + Contracts.throwIfDisposed(this.privDisposedIntentRecognizer); + + this.implCloseExistingRecognizer(); + + if (!!cb) { + try { + cb(); + } catch (e) { + if (!!err) { + err(e); + } + } + } + } catch (error) { + if (!!err) { + if (error instanceof Error) { + const typedError: Error = error as Error; + err(typedError.name + ": " + typedError.message); + } else { + err(error); + } + } + } + } + + /** + * Starts speech recognition with keyword spotting, until stopKeywordRecognitionAsync() is called. + * User must subscribe to events to receive recognition results. + * Note: Key word spotting functionality is only available on the Speech Devices SDK. + * This functionality is currently not included in the SDK itself. + * @member IntentRecognizer.prototype.startKeywordRecognitionAsync + * @function + * @public + * @param {KeywordRecognitionModel} model - The keyword recognition model that specifies the keyword to be recognized. + * @param cb - Callback invoked once the recognition has started. + * @param err - Callback invoked in case of an error. + */ + public startKeywordRecognitionAsync(model: KeywordRecognitionModel, cb?: () => void, err?: (e: string) => void): void { + Contracts.throwIfNull(model, "model"); + + if (!!err) { + err("Not yet implemented."); + } + } + + /** + * Stops continuous speech recognition. + * Note: Key word spotting functionality is only available on the Speech Devices SDK. + * This functionality is currently not included in the SDK itself. + * @member IntentRecognizer.prototype.stopKeywordRecognitionAsync + * @function + * @public + * @param cb - Callback invoked once the recognition has stopped. + * @param err - Callback invoked in case of an error. + */ + public stopKeywordRecognitionAsync(cb?: () => void, err?: (e: string) => void): void { + if (!!cb) { + cb(); + } + } + + /** + * Adds a phrase that should be recognized as intent. + * @member IntentRecognizer.prototype.addIntent + * @function + * @public + * @param {string} intentId - A String that represents the identifier of the intent to be recognized. + * @param {string} phrase - A String that specifies the phrase representing the intent. + */ + public addIntent(simplePhrase: string, intentId?: string): void { + Contracts.throwIfDisposed(this.privDisposedIntentRecognizer); + Contracts.throwIfNullOrWhitespace(intentId, "intentId"); + Contracts.throwIfNullOrWhitespace(simplePhrase, "simplePhrase"); + + this.privAddedIntents.push([intentId, simplePhrase]); + } + + /** + * Adds an intent from Language Understanding service for recognition. + * @member IntentRecognizer.prototype.addIntentWithLanguageModel + * @function + * @public + * @param {string} intentId - A String that represents the identifier of the intent + * to be recognized. Ignored if intentName is empty. + * @param {string} model - The intent model from Language Understanding service. + * @param {string} intentName - The intent name defined in the intent model. If it + * is empty, all intent names defined in the model will be added. + */ + public addIntentWithLanguageModel(intentId: string, model: LanguageUnderstandingModel, intentName?: string): void { + Contracts.throwIfDisposed(this.privDisposedIntentRecognizer); + Contracts.throwIfNullOrWhitespace(intentId, "intentId"); + Contracts.throwIfNull(model, "model"); + + const modelImpl: LanguageUnderstandingModelImpl = model as LanguageUnderstandingModelImpl; + Contracts.throwIfNullOrWhitespace(modelImpl.appId, "model.appId"); + + this.privAddedLmIntents[intentId] = new AddedLmIntent(modelImpl, intentName); + } + + /** + * @summary Adds all intents from the specified Language Understanding Model. + * @member IntentRecognizer.prototype.addAllIntents + * @function + * @public + * @function + * @public + * @param {LanguageUnderstandingModel} model - The language understanding model containing the intents. + * @param {string} intentId - A custom id String to be returned in the IntentRecognitionResult's getIntentId() method. + */ + public addAllIntents(model: LanguageUnderstandingModel, intentId?: string): void { + Contracts.throwIfNull(model, "model"); + + const modelImpl: LanguageUnderstandingModelImpl = model as LanguageUnderstandingModelImpl; + Contracts.throwIfNullOrWhitespace(modelImpl.appId, "model.appId"); + + this.privUmbrellaIntent = new AddedLmIntent(modelImpl, intentId); + } + + /** + * closes all external resources held by an instance of this class. + * @member IntentRecognizer.prototype.close + * @function + * @public + */ + public close(): void { + Contracts.throwIfDisposed(this.privDisposedIntentRecognizer); + + this.dispose(true); + } + + protected createRecognizerConfig(speecgConfig: PlatformConfig, recognitionMode: RecognitionMode): RecognizerConfig { + return new RecognizerConfig(speecgConfig, recognitionMode, this.properties); + } + protected createServiceRecognizer(authentication: IAuthentication, connectionFactory: IConnectionFactory, audioConfig: AudioConfig, recognizerConfig: RecognizerConfig): ServiceRecognizerBase { + const audioImpl: AudioConfigImpl = audioConfig as AudioConfigImpl; + return new IntentServiceRecognizer(authentication, connectionFactory, audioImpl, recognizerConfig, this, this.privIntentDataSent); + } + + protected dispose(disposing: boolean): void { + if (this.privDisposedIntentRecognizer) { + return; + } + + if (disposing) { + this.privDisposedIntentRecognizer = true; + super.dispose(disposing); + } + } + + private implCloseExistingRecognizer(): void { + if (this.privReco) { + this.privReco.audioSource.turnOff(); + this.privReco.dispose(); + this.privReco = undefined; + } + } + + private buildSpeechContext(): string { + let appId: string; + let region: string; + let subscriptionKey: string; + const refGrammers: string[] = []; + + if (undefined !== this.privUmbrellaIntent) { + appId = this.privUmbrellaIntent.modelImpl.appId; + region = this.privUmbrellaIntent.modelImpl.region; + subscriptionKey = this.privUmbrellaIntent.modelImpl.subscriptionKey; + } + + // Build the reference grammer array. + for (const intentId of Object.keys(this.privAddedLmIntents)) { + const addedLmIntent: AddedLmIntent = this.privAddedLmIntents[intentId]; + + // validate all the same model, region, and key... + if (appId === undefined) { + appId = addedLmIntent.modelImpl.appId; + } else { + if (appId !== addedLmIntent.modelImpl.appId) { + throw new Error("Intents must all be from the same LUIS model"); + } + } + + if (region === undefined) { + region = addedLmIntent.modelImpl.region; + } else { + if (region !== addedLmIntent.modelImpl.region) { + throw new Error("Intents must all be from the same LUIS model in a single region"); + } + } + + if (subscriptionKey === undefined) { + subscriptionKey = addedLmIntent.modelImpl.subscriptionKey; + } else { + if (subscriptionKey !== addedLmIntent.modelImpl.subscriptionKey) { + throw new Error("Intents must all use the same subscription key"); + } + } + + const grammer: string = "luis/" + appId + "-PRODUCTION#" + intentId; + refGrammers.push(grammer); + } + + return JSON.stringify({ + dgi: { + ReferenceGrammars: (undefined === this.privUmbrellaIntent) ? refGrammers : ["luis/" + appId + "-PRODUCTION"], + }, + intent: { + id: appId, + key: (subscriptionKey === undefined) ? this.privProperties.getProperty(PropertyId[PropertyId.SpeechServiceConnection_Key]) : subscriptionKey, + provider: "LUIS", + }, + }); + } +} diff --git a/src/sdk/KeywordRecognitionModel.ts b/src/sdk/KeywordRecognitionModel.ts new file mode 100644 index 00000000..b5ceaef1 --- /dev/null +++ b/src/sdk/KeywordRecognitionModel.ts @@ -0,0 +1,65 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +import { Contracts } from "./Contracts"; + +/** + * Represents a keyword recognition model for recognizing when + * the user says a keyword to initiate further speech recognition. + * @class KeywordRecognitionModel + */ +export class KeywordRecognitionModel { + private privDisposed: boolean = false; + + /** + * Create and initializes a new instance. + * @constructor + */ + private constructor() { + } + + /** + * Creates a keyword recognition model using the specified filename. + * @member KeywordRecognitionModel.fromFile + * @function + * @public + * @param {string} fileName - A string that represents file name for the keyword recognition model. + * Note, the file can point to a zip file in which case the model + * will be extracted from the zip. + * @returns {KeywordRecognitionModel} The keyword recognition model being created. + */ + public static fromFile(fileName: string): KeywordRecognitionModel { + Contracts.throwIfFileDoesNotExist(fileName, "fileName"); + + throw new Error("Not yet implemented."); + } + + /** + * Creates a keyword recognition model using the specified filename. + * @member KeywordRecognitionModel.fromStream + * @function + * @public + * @param {string} file - A File that represents file for the keyword recognition model. + * Note, the file can point to a zip file in which case the model will be extracted from the zip. + * @returns {KeywordRecognitionModel} The keyword recognition model being created. + */ + public static fromStream(file: File): KeywordRecognitionModel { + Contracts.throwIfNull(file, "file"); + + throw new Error("Not yet implemented."); + } + + /** + * Dispose of associated resources. + * @member KeywordRecognitionModel.prototype.close + * @function + * @public + */ + public close(): void { + if (this.privDisposed) { + return; + } + + this.privDisposed = true; + } +} diff --git a/src/sdk/LanguageUnderstandingModel.ts b/src/sdk/LanguageUnderstandingModel.ts new file mode 100644 index 00000000..6766f81e --- /dev/null +++ b/src/sdk/LanguageUnderstandingModel.ts @@ -0,0 +1,113 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +import { Contracts } from "./Contracts"; + +/** + * Language understanding model + * @class LanguageUnderstandingModel + */ +export class LanguageUnderstandingModel { + /** + * Creates and initializes a new instance + * @constructor + */ + protected constructor() { + } + + /** + * Creates an language understanding model using the specified endpoint. + * @member LanguageUnderstandingModel.fromEndpoint + * @function + * @public + * @param {URL} uri - A String that represents the endpoint of the language understanding model. + * @returns {LanguageUnderstandingModel} The language understanding model being created. + */ + public static fromEndpoint(uri: URL): LanguageUnderstandingModel { + Contracts.throwIfNull(uri, "uri"); + Contracts.throwIfNullOrWhitespace(uri.hostname, "uri"); + + const langModelImp: LanguageUnderstandingModelImpl = new LanguageUnderstandingModelImpl(); + // Need to extract the app ID from the URL. + // URL is in the format: https://.api.cognitive.microsoft.com/luis/v2.0/apps/?subscription-key=&timezoneOffset=-360 + + // Start tearing the string apart. + + // region can be extracted from the host name. + const firstDot: number = uri.host.indexOf("."); + if (-1 === firstDot) { + throw new Error("Could not determine region from endpoint"); + } + langModelImp.region = uri.host.substr(0, firstDot); + + // Now the app ID. + + const lastSegment: number = uri.pathname.lastIndexOf("/") + 1; + if (-1 === lastSegment) { + throw new Error("Could not determine appId from endpoint"); + } + + langModelImp.appId = uri.pathname.substr(lastSegment); + + // And finally the key. + langModelImp.subscriptionKey = uri.searchParams.get("subscription-key"); + if (undefined === langModelImp.subscriptionKey) { + throw new Error("Could not determine subscription key from endpoint"); + } + + return langModelImp; + } + + /** + * Creates an language understanding model using the application id of Language Understanding service. + * @member LanguageUnderstandingModel.fromAppId + * @function + * @public + * @param {string} appId - A String that represents the application id of Language Understanding service. + * @returns {LanguageUnderstandingModel} The language understanding model being created. + */ + public static fromAppId(appId: string): LanguageUnderstandingModel { + Contracts.throwIfNullOrWhitespace(appId, "appId"); + + const langModelImp: LanguageUnderstandingModelImpl = new LanguageUnderstandingModelImpl(); + langModelImp.appId = appId; + return langModelImp; + } + + /** + * Creates a language understanding model using hostname, subscription key and application + * id of Language Understanding service. + * @member LanguageUnderstandingModel.fromSubscription + * @function + * @public + * @param {string} subscriptionKey - A String that represents the subscription key of + * Language Understanding service. + * @param {string} appId - A String that represents the application id of Language + * Understanding service. + * @param {LanguageUnderstandingModel} region - A String that represents the region + * of the Language Understanding service (see the region page). + * @returns {LanguageUnderstandingModel} The language understanding model being created. + */ + public static fromSubscription(subscriptionKey: string, appId: string, region: string): LanguageUnderstandingModel { + Contracts.throwIfNullOrWhitespace(subscriptionKey, "subscriptionKey"); + Contracts.throwIfNullOrWhitespace(appId, "appId"); + Contracts.throwIfNullOrWhitespace(region, "region"); + + const langModelImp: LanguageUnderstandingModelImpl = new LanguageUnderstandingModelImpl(); + langModelImp.appId = appId; + langModelImp.region = region; + langModelImp.subscriptionKey = subscriptionKey; + return langModelImp; + } +} + +/** + * @private + * @class LanguageUnderstandingModelImpl + */ +// tslint:disable-next-line:max-classes-per-file +export class LanguageUnderstandingModelImpl extends LanguageUnderstandingModel { + public appId: string; + public region: string; + public subscriptionKey: string; +} diff --git a/src/sdk/NoMatchDetails.ts b/src/sdk/NoMatchDetails.ts new file mode 100644 index 00000000..328b3e4e --- /dev/null +++ b/src/sdk/NoMatchDetails.ts @@ -0,0 +1,62 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +import { RecognitionStatus, SimpleSpeechPhrase } from "../../src/common.speech/Exports"; +import { IntentRecognitionResult, NoMatchReason, SpeechRecognitionResult, TranslationRecognitionResult } from "./Exports"; + +/** + * Contains detailed information for NoMatch recognition results. + * @class NoMatchDetails + */ +export class NoMatchDetails { + private privReason: NoMatchReason; + + /** + * Creates and initializes an instance of this class. + * @constructor + * @param {NoMatchReason} reason - The no-match reason. + */ + private constructor(reason: NoMatchReason) { + this.privReason = reason; + } + + /** + * Creates an instance of NoMatchDetails object for the NoMatch SpeechRecognitionResults. + * @member NoMatchDetails.fromResult + * @function + * @public + * @param {SpeechRecognitionResult | IntentRecognitionResult | TranslationRecognitionResult} + * result - The recognition result that was not recognized. + * @returns {NoMatchDetails} The no match details object being created. + */ + public static fromResult(result: SpeechRecognitionResult | IntentRecognitionResult | TranslationRecognitionResult): NoMatchDetails { + const simpleSpeech: SimpleSpeechPhrase = SimpleSpeechPhrase.fromJSON(result.json); + + let reason: NoMatchReason = NoMatchReason.NotRecognized; + + switch (simpleSpeech.RecognitionStatus) { + case RecognitionStatus.BabbleTimeout: + reason = NoMatchReason.InitialBabbleTimeout; + break; + case RecognitionStatus.InitialSilenceTimeout: + reason = NoMatchReason.InitialSilenceTimeout; + break; + default: + reason = NoMatchReason.NotRecognized; + break; + } + + return new NoMatchDetails(reason); + } + + /** + * The reason the recognition was canceled. + * @member NoMatchDetails.prototype.reason + * @function + * @public + * @returns {NoMatchReason} Specifies the reason canceled. + */ + public get reason(): NoMatchReason { + return this.privReason; + } +} diff --git a/src/sdk/NoMatchReason.ts b/src/sdk/NoMatchReason.ts new file mode 100644 index 00000000..0284effe --- /dev/null +++ b/src/sdk/NoMatchReason.ts @@ -0,0 +1,29 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +/** + * Defines the possible reasons a recognition result might not be recognized. + * @class NoMatchReason + */ +export enum NoMatchReason { + + /** + * Indicates that speech was detected, but not recognized. + * @member NoMatchReason.NotRecognized + */ + NotRecognized, + + /** + * Indicates that the start of the audio stream contained only silence, + * and the service timed out waiting for speech. + * @member NoMatchReason.InitialSilenceTimeout + */ + InitialSilenceTimeout, + + /** + * Indicates that the start of the audio stream contained only noise, + * and the service timed out waiting for speech. + * @member NoMatchReason.InitialBabbleTimeout + */ + InitialBabbleTimeout, +} diff --git a/src/sdk/OutputFormat.ts b/src/sdk/OutputFormat.ts new file mode 100644 index 00000000..2a946f71 --- /dev/null +++ b/src/sdk/OutputFormat.ts @@ -0,0 +1,18 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +/** + * Define Speech Recognizer output formats. + * @class OutputFormat + */ +export enum OutputFormat { + /** + * @member OutputFormat.Simple + */ + Simple = 0, + + /** + * @member OutputFormat.Detailed + */ + Detailed, +} diff --git a/src/sdk/PropertyCollection.ts b/src/sdk/PropertyCollection.ts new file mode 100644 index 00000000..f9083530 --- /dev/null +++ b/src/sdk/PropertyCollection.ts @@ -0,0 +1,89 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +import { PropertyId } from "./Exports"; + +/** + * Represents collection of properties and their values. + * @class PropertyCollection + */ +export class PropertyCollection { + private privKeys: string[] = [] as string[]; + private privValues: string[] = [] as string[]; + + /** + * Returns the property value in type String. The parameter must have the same type as String. + * Currently only String, int and bool are allowed. + * If the name is not available, the specified defaultValue is returned. + * @member PropertyCollection.prototype.getProperty + * @function + * @public + * @param {string} key - The parameter name. + * @param {string} def - The default value which is returned if the parameter + * is not available in the collection. + * @returns {string} value of the parameter. + */ + public getProperty(key: PropertyId | string, def?: string): string { + let keyToUse: string; + + if (typeof key === "string") { + keyToUse = key; + } else { + keyToUse = PropertyId[key]; + } + + for (let n = 0; n < this.privKeys.length; n++) { + if (this.privKeys[n] === keyToUse) { + return this.privValues[n]; + } + } + + return def; + } + + /** + * Sets the String value of the parameter specified by name. + * @member PropertyCollection.prototype.setProperty + * @function + * @public + * @param {string} key - The parameter name. + * @param {string} value - The value of the parameter. + */ + public setProperty(key: string | PropertyId, value: string): void { + let keyToUse: string; + + if (typeof key === "string") { + keyToUse = key; + } else { + keyToUse = PropertyId[key]; + } + + for (let n = 0; n < this.privKeys.length; n++) { + if (this.privKeys[n] === keyToUse) { + this.privValues[n] = value; + return; + } + } + + this.privKeys.push(keyToUse); + this.privValues.push(value); + } + + /** + * Clones the collection. + * @member PropertyCollection.prototype.clone + * @function + * @public + * @returns {PropertyCollection} A copy of the collection. + */ + public clone(): PropertyCollection { + const clonedMap = new PropertyCollection(); + + for (let n = 0; n < this.privKeys.length; n++) { + clonedMap.privKeys.push(this.privKeys[n]); + clonedMap.privValues.push(this.privValues[n]); + } + + return clonedMap; + } +} diff --git a/src/sdk/PropertyId.ts b/src/sdk/PropertyId.ts new file mode 100644 index 00000000..b6712ca5 --- /dev/null +++ b/src/sdk/PropertyId.ts @@ -0,0 +1,135 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +/** + * Defines speech property ids. + * @class PropertyId + */ +export enum PropertyId { + + /** + * Subscription key. + * @member PropertyId.SpeechServiceConnection_Key + */ + SpeechServiceConnection_Key = 0, + + /** + * Endpoint. + * @member PropertyId.SpeechServiceConnection_Endpoint + */ + SpeechServiceConnection_Endpoint, + + /** + * Region. + * @member PropertyId.SpeechServiceConnection_Region + */ + SpeechServiceConnection_Region, + + /** + * Authorization token. + * @member PropertyId.SpeechServiceAuthorization_Token + */ + SpeechServiceAuthorization_Token, + + /** + * Authorization type. + * @member PropertyId.SpeechServiceAuthorization_Type + */ + SpeechServiceAuthorization_Type, + + /** + * Endpoint ID. + * @member PropertyId.SpeechServiceConnection_EndpointId + */ + SpeechServiceConnection_EndpointId, + + /** + * Translation to languages. + * @member PropertyId.SpeechServiceConnection_TranslationToLanguages + */ + SpeechServiceConnection_TranslationToLanguages, + + /** + * Translation output voice. + * @member PropertyId.SpeechServiceConnection_TranslationVoice + */ + SpeechServiceConnection_TranslationVoice, + + /** + * Translation features. + * @member PropertyId.SpeechServiceConnection_TranslationFeatures + */ + SpeechServiceConnection_TranslationFeatures, + + /** + * Intent region. + * @member PropertyId.SpeechServiceConnection_IntentRegion + */ + SpeechServiceConnection_IntentRegion, + + /** + * Recognition mode. + * @member PropertyId.SpeechServiceConnection_RecoMode + */ + SpeechServiceConnection_RecoMode, + + /** + * Recognition language. + * @member PropertyId.SpeechServiceConnection_RecoLanguage + */ + SpeechServiceConnection_RecoLanguage, + + /** + * Session id. + * @member PropertyId.Speech_SessionId + */ + Speech_SessionId, + + /** + * Detailed result required. + * @member PropertyId.SpeechServiceResponse_RequestDetailedResultTrueFalse + */ + SpeechServiceResponse_RequestDetailedResultTrueFalse, + + /** + * Profanity filtering required. + * @member PropertyId.SpeechServiceResponse_RequestProfanityFilterTrueFalse + */ + SpeechServiceResponse_RequestProfanityFilterTrueFalse, + + /** + * JSON in result. + * @member PropertyId.SpeechServiceResponse_JsonResult + */ + SpeechServiceResponse_JsonResult, + + /** + * Error details. + * @member PropertyId.SpeechServiceResponse_JsonErrorDetails + */ + SpeechServiceResponse_JsonErrorDetails, + + /** + * Cancellation reason. + * @member PropertyId.CancellationDetails_Reason + */ + CancellationDetails_Reason, + + /** + * Cancellation text. + * @member PropertyId.CancellationDetails_ReasonText + */ + CancellationDetails_ReasonText, + + /** + * Cancellation detailed text. + * @member PropertyId.CancellationDetails_ReasonDetailedText + */ + CancellationDetails_ReasonDetailedText, + + /** + * JSON result of language understanding service. + * @member PropertyId.LanguageUnderstandingServiceResponse_JsonResult + */ + LanguageUnderstandingServiceResponse_JsonResult, +} diff --git a/src/sdk/RecognitionEventArgs.ts b/src/sdk/RecognitionEventArgs.ts new file mode 100644 index 00000000..6cc41308 --- /dev/null +++ b/src/sdk/RecognitionEventArgs.ts @@ -0,0 +1,34 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +import { SessionEventArgs } from "./Exports"; + +/** + * Defines payload for session events like Speech Start/End Detected + * @class + */ +export class RecognitionEventArgs extends SessionEventArgs { + private privOffset: number; + + /** + * Creates and initializes an instance of this class. + * @constructor + * @param {number} offset - The offset. + * @param {string} sessionId - The session id. + */ + public constructor(offset: number, sessionId?: string) { + super(sessionId); + + this.privOffset = offset; + } + + /** + * Represents the message offset + * @member RecognitionEventArgs.prototype.offset + * @function + * @public + */ + public get offset(): number { + return this.privOffset; + } +} diff --git a/src/sdk/RecognitionResult.ts b/src/sdk/RecognitionResult.ts new file mode 100644 index 00000000..1bc5d10f --- /dev/null +++ b/src/sdk/RecognitionResult.ts @@ -0,0 +1,131 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +import { PropertyCollection, ResultReason } from "./Exports"; + +/** + * Defines result of speech recognition. + * @class RecognitionResult + */ +export class RecognitionResult { + private privResultId: string; + private privReason: ResultReason; + private privText: string; + private privDuration: number; + private privOffset: number; + private privErrorDetails: string; + private privJson: string; + private privProperties: PropertyCollection; + + /** + * Creates and initializes an instance of this class. + * @constructor + * @param {string} resultId - The result id. + * @param {ResultReason} reason - The reason. + * @param {string} text - The recognized text. + * @param {number} duration - The duration. + * @param {number} offset - The offset into the stream. + * @param {string} errorDetails - Error details, if provided. + * @param {string} json - Additional Json, if provided. + * @param {PropertyCollection} properties - Additional properties, if provided. + */ + constructor(resultId?: string, reason?: ResultReason, text?: string, duration?: number, + offset?: number, errorDetails?: string, json?: string, properties?: PropertyCollection) { + this.privResultId = resultId; + this.privReason = reason; + this.privText = text; + this.privDuration = duration; + this.privOffset = offset; + this.privErrorDetails = errorDetails; + this.privJson = json; + this.privProperties = properties; + } + + /** + * Specifies the result identifier. + * @member RecognitionResult.prototype.resultId + * @function + * @public + * @returns {string} Specifies the result identifier. + */ + public get resultId(): string { + return this.privResultId; + } + + /** + * Specifies status of the result. + * @member RecognitionResult.prototype.reason + * @function + * @public + * @returns {ResultReason} Specifies status of the result. + */ + public get reason(): ResultReason { + return this.privReason; + } + + /** + * Presents the recognized text in the result. + * @member RecognitionResult.prototype.text + * @function + * @public + * @returns {string} Presents the recognized text in the result. + */ + public get text(): string { + return this.privText; + } + + /** + * Duration of recognized speech in 100 nano second incements. + * @member RecognitionResult.prototype.duration + * @function + * @public + * @returns {number} Duration of recognized speech in 100 nano second incements. + */ + public get duration(): number { + return this.privDuration; + } + + /** + * Offset of recognized speech in 100 nano second incements. + * @member RecognitionResult.prototype.offset + * @function + * @public + * @returns {number} Offset of recognized speech in 100 nano second incements. + */ + public get offset(): number { + return this.privOffset; + } + + /** + * In case of an unsuccessful recognition, provides details of the occurred error. + * @member RecognitionResult.prototype.errorDetails + * @function + * @public + * @returns {string} a brief description of an error. + */ + public get errorDetails(): string { + return this.privErrorDetails; + } + + /** + * A string containing Json serialized recognition result as it was received from the service. + * @member RecognitionResult.prototype.json + * @function + * @private + * @returns {string} Json serialized representation of the result. + */ + public get json(): string { + return this.privJson; + } + + /** + * The set of properties exposed in the result. + * @member RecognitionResult.prototype.properties + * @function + * @public + * @returns {PropertyCollection} The set of properties exposed in the result. + */ + public get properties(): PropertyCollection { + return this.privProperties; + } +} diff --git a/src/sdk/Recognizer.ts b/src/sdk/Recognizer.ts new file mode 100644 index 00000000..1d520d8d --- /dev/null +++ b/src/sdk/Recognizer.ts @@ -0,0 +1,206 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +import { + CognitiveSubscriptionKeyAuthentication, + CognitiveTokenAuthentication, + Context, + IAuthentication, + IConnectionFactory, + OS, + PlatformConfig, + RecognitionMode, + RecognizerConfig, + ServiceRecognizerBase, +} from "../common.speech/Exports"; +import { Promise, PromiseHelper } from "../common/Exports"; +import { Contracts } from "./Contracts"; +import { + AudioConfig, + PropertyCollection, + PropertyId, + RecognitionEventArgs, + SessionEventArgs, + SpeechRecognitionResult, +} from "./Exports"; + +/** + * Defines the base class Recognizer which mainly contains common event handlers. + * @class Recognizer + */ +export abstract class Recognizer { + private privDisposed: boolean; + protected audioConfig: AudioConfig; + + /** + * Creates and initializes an instance of a Recognizer + * @constructor + * @param {AudioConfig} audioInput - An optional audio input stream associated with the recognizer + */ + protected constructor(audioConfig: AudioConfig) { + this.audioConfig = (audioConfig !== undefined) ? audioConfig : AudioConfig.fromDefaultMicrophoneInput(); + + this.privDisposed = false; + } + + /** + * Defines event handler for session started events. + * @member Recognizer.prototype.sessionStarted + * @function + * @public + */ + public sessionStarted: (sender: Recognizer, event: SessionEventArgs) => void; + + /** + * Defines event handler for session stopped events. + * @member Recognizer.prototype.sessionStopped + * @function + * @public + */ + public sessionStopped: (sender: Recognizer, event: SessionEventArgs) => void; + + /** + * Defines event handler for speech started events. + * @member Recognizer.prototype.speechStartDetected + * @function + * @public + */ + public speechStartDetected: (sender: Recognizer, event: RecognitionEventArgs) => void; + + /** + * Defines event handler for speech stopped events. + * @member Recognizer.prototype.speechEndDetected + * @function + * @public + */ + public speechEndDetected: (sender: Recognizer, event: RecognitionEventArgs) => void; + + /** + * Dispose of associated resources. + * @member Recognizer.prototype.close + * @function + * @public + */ + public close(): void { + Contracts.throwIfDisposed(this.privDisposed); + + this.dispose(true); + } + + /** + * This method performs cleanup of resources. + * The Boolean parameter disposing indicates whether the method is called + * from Dispose (if disposing is true) or from the finalizer (if disposing is false). + * Derived classes should override this method to dispose resource if needed. + * @member Recognizer.prototype.dispose + * @function + * @public + * @param {boolean} disposing - Flag to request disposal. + */ + protected dispose(disposing: boolean): void { + if (this.privDisposed) { + return; + } + + if (disposing) { + // disconnect + } + + this.privDisposed = true; + } + + /** + * This method returns the current state of the telemetry setting. + * @member Recognizer.prototype.telemetryEnabled + * @function + * @public + * @returns true if the telemetry is enabled, false otherwise. + */ + public static get telemetryEnabled(): boolean { + return ServiceRecognizerBase.telemetryDataEnabled; + } + + /** + * This method globally enables or disables telemetry. + * @member Recognizer.prototype.enableTelemetry + * @function + * @public + * @param enabled - Global setting for telemetry collection. + * If set to true, telemetry information like microphone errors, + * recognition errors are collected and sent to Microsoft. + * If set to false, no telemetry is sent to Microsoft. + */ + /* tslint:disable:member-ordering */ + public static enableTelemetry(enabled: boolean): void { + ServiceRecognizerBase.telemetryDataEnabled = enabled; + } + + // + // ################################################################################################################ + // IMPLEMENTATION. + // Move to independent class + // ################################################################################################################ + // + + protected abstract createRecognizerConfig(speecgConfig: PlatformConfig, recognitionMode: RecognitionMode): RecognizerConfig; + + protected abstract createServiceRecognizer(authentication: IAuthentication, connectionFactory: IConnectionFactory, + audioConfig: AudioConfig, recognizerConfig: RecognizerConfig): ServiceRecognizerBase; + + // Setup the recognizer + protected implRecognizerSetup(recognitionMode: RecognitionMode, speechProperties: PropertyCollection, + audioConfig: AudioConfig, speechConnectionFactory: IConnectionFactory): ServiceRecognizerBase { + + let osPlatform = (typeof window !== "undefined") ? "Browser" : "Node"; + let osName = "unknown"; + let osVersion = "unknown"; + + if (typeof navigator !== "undefined") { + osPlatform = osPlatform + "/" + navigator.platform; + osName = navigator.userAgent; + osVersion = navigator.appVersion; + } + + const recognizerConfig = this.createRecognizerConfig( + new PlatformConfig( + new Context(new OS(osPlatform, osName, osVersion))), + recognitionMode); // SDK.SpeechResultFormat.Simple (Options - Simple/Detailed) + + const subscriptionKey = speechProperties.getProperty(PropertyId.SpeechServiceConnection_Key, undefined); + const authentication = subscriptionKey ? + new CognitiveSubscriptionKeyAuthentication(subscriptionKey) : + new CognitiveTokenAuthentication( + (authFetchEventId: string): Promise => { + const authorizationToken = speechProperties.getProperty(PropertyId.SpeechServiceAuthorization_Token, undefined); + return PromiseHelper.fromResult(authorizationToken); + }, + (authFetchEventId: string): Promise => { + const authorizationToken = speechProperties.getProperty(PropertyId.SpeechServiceAuthorization_Token, undefined); + return PromiseHelper.fromResult(authorizationToken); + }); + + return this.createServiceRecognizer( + authentication, + speechConnectionFactory, + audioConfig, + recognizerConfig); + } + + // Start the recognition + protected implRecognizerStart( + recognizer: ServiceRecognizerBase, + successCallback: (e: SpeechRecognitionResult) => void, + errorCallback: (e: string) => void, + speechContext?: string, + ): void { + recognizer.recognize(speechContext, successCallback, errorCallback).on( + /* tslint:disable:no-empty */ + (result: boolean): void => { }, + (error: string): void => { + if (!!errorCallback) { + // Internal error with service communication. + errorCallback("Runtime error: " + error); + } + }); + } +} diff --git a/src/sdk/ResultReason.ts b/src/sdk/ResultReason.ts new file mode 100644 index 00000000..ce7f608a --- /dev/null +++ b/src/sdk/ResultReason.ts @@ -0,0 +1,73 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +/** + * Defines the possible reasons a recognition result might be generated. + * @class ResultReason + */ +export enum ResultReason { + /** + * Indicates speech could not be recognized. More details + * can be found in the NoMatchDetails object. + * @member ResultReason.NoMatch + */ + NoMatch, + + /** + * Indicates that the recognition was canceled. More details + * can be found using the CancellationDetails object. + * @member ResultReason.Canceled + */ + Canceled, + + /** + * Indicates the speech result contains hypothesis text. + * @member ResultReason.RecognizedSpeech + */ + RecognizingSpeech, + + /** + * Indicates the speech result contains final text that has been recognized. + * Speech Recognition is now complete for this phrase. + * @member ResultReason.RecognizedSpeech + */ + RecognizedSpeech, + + /** + * Indicates the intent result contains hypothesis text and intent. + * @member ResultReason.RecognizingIntent + */ + RecognizingIntent, + + /** + * Indicates the intent result contains final text and intent. + * Speech Recognition and Intent determination are now complete for this phrase. + * @member ResultReason.RecognizedIntent + */ + RecognizedIntent, + + /** + * Indicates the translation result contains hypothesis text and its translation(s). + * @member ResultReason.TranslatingSpeech + */ + TranslatingSpeech, + + /** + * Indicates the translation result contains final text and corresponding translation(s). + * Speech Recognition and Translation are now complete for this phrase. + * @member ResultReason.TranslatedSpeech + */ + TranslatedSpeech, + + /** + * Indicates the synthesized audio result contains a non-zero amount of audio data + * @member ResultReason.SynthesizingAudio + */ + SynthesizingAudio, + + /** + * Indicates the synthesized audio is now complete for this phrase. + * @member ResultReason.SynthesizingAudioCompleted + */ + SynthesizingAudioCompleted, +} diff --git a/src/sdk/SessionEventArgs.ts b/src/sdk/SessionEventArgs.ts new file mode 100644 index 00000000..2aea34ef --- /dev/null +++ b/src/sdk/SessionEventArgs.ts @@ -0,0 +1,30 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +/** + * Defines content for session events like SessionStarted/Stopped, SoundStarted/Stopped. + * @class SessionEventArgs + */ +export class SessionEventArgs { + private privSessionId: string; + + /** + * Creates and initializes an instance of this class. + * @constructor + * @param {string} sessionId - The session id. + */ + public constructor(sessionId: string) { + this.privSessionId = sessionId; + } + + /** + * Represents the session identifier. + * @member SessionEventArgs.prototype.sessionId + * @function + * @public + * @returns {string} Represents the session identifier. + */ + public get sessionId(): string { + return this.privSessionId; + } +} diff --git a/src/sdk/SpeechConfig.ts b/src/sdk/SpeechConfig.ts new file mode 100644 index 00000000..3141f7c0 --- /dev/null +++ b/src/sdk/SpeechConfig.ts @@ -0,0 +1,263 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +import { OutputFormatPropertyName } from "../common.speech/Exports"; +import { Contracts } from "./Contracts"; +import { OutputFormat, PropertyCollection, PropertyId } from "./Exports"; + +/** + * Speech configuration. + * @class SpeechConfig + */ +export abstract class SpeechConfig { + /** + * Creates and initializes an instance. + * @constructor + */ + protected constructor() { } + + /** + * Static instance of SpeechConfig returned by passing subscriptionKey and service region. + * @member SpeechConfig.fromSubscription + * @function + * @public + * @param {string} subscriptionKey - The subscription key. + * @param {string} region - The region name (see the region page). + * @returns {SpeechConfig} The speech factory + */ + public static fromSubscription(subscriptionKey: string, region: string): SpeechConfig { + Contracts.throwIfNullOrWhitespace(subscriptionKey, "subscriptionKey"); + Contracts.throwIfNullOrWhitespace(region, "region"); + + const speechImpl: SpeechConfigImpl = new SpeechConfigImpl(); + speechImpl.setProperty(PropertyId.SpeechServiceConnection_Region, region); + speechImpl.setProperty(PropertyId.SpeechServiceConnection_IntentRegion, region); + speechImpl.setProperty(PropertyId.SpeechServiceConnection_Key, subscriptionKey); + + return speechImpl; + } + + /** + * Creates an instance of the speech factory with specified endpoint and subscription key. + * This method is intended only for users who use a non-standard service endpoint or paramters. + * the language setting in uri takes precedence, and the effective language is "de-DE". + * @member SpeechConfig.fromEndpoint + * @function + * @public + * @param {URL} endpoint - The service endpoint to connect to. + * @param {string} subscriptionKey - The subscription key. + * @returns {SpeechConfig} A speech factory instance. + */ + public static fromEndpoint(endpoint: URL, subscriptionKey: string): SpeechConfig { + Contracts.throwIfNull(endpoint, "endpoint"); + Contracts.throwIfNullOrWhitespace(subscriptionKey, "subscriptionKey"); + + const speechImpl: SpeechConfigImpl = new SpeechConfigImpl(); + speechImpl.setProperty(PropertyId.SpeechServiceConnection_Endpoint, endpoint.href); + speechImpl.setProperty(PropertyId.SpeechServiceConnection_Key, subscriptionKey); + return speechImpl; + } + + /** + * Creates an instance of the speech factory with specified initial authorization token and region. + * @member SpeechConfig.fromAuthorizationToken + * @function + * @public + * @param {string} authorizationToken - The initial authorization token. + * @param {string} region - The region name (see the region page). + * @returns {SpeechConfig} A speech factory instance. + */ + public static fromAuthorizationToken(authorizationToken: string, region: string): SpeechConfig { + Contracts.throwIfNull(authorizationToken, "authorizationToken"); + Contracts.throwIfNullOrWhitespace(region, "region"); + + const speechImpl: SpeechConfigImpl = new SpeechConfigImpl(); + speechImpl.setProperty(PropertyId.SpeechServiceConnection_Region, region); + speechImpl.setProperty(PropertyId.SpeechServiceConnection_IntentRegion, region); + speechImpl.authorizationToken = authorizationToken; + return speechImpl; + } + + /** + * Returns the current authorization token. + * @member SpeechConfig.prototype.authorizationToken + * @function + * @public + */ + public abstract get authorizationToken(): string; + + /** + * Sets the authorization token. + * If this is set, subscription key is ignored. + * User needs to make sure the provided authorization token is valid and not expired. + * @member SpeechConfig.prototype.authorizationToken + * @function + * @public + * @param {string} value - The authorization token. + */ + public abstract set authorizationToken(value: string); + + /** + * Returns the configured language. + * @member SpeechConfig.prototype.speechRecognitionLanguage + * @function + * @public + */ + public abstract get speechRecognitionLanguage(): string; + + /** + * Sets the input language. + * @member SpeechConfig.prototype.speechRecognitionLanguage + * @function + * @public + * @param {string} value - The authorization token. + */ + public abstract set speechRecognitionLanguage(value: string); + + /** + * Sets an arbitrary property. + * @member SpeechConfig.prototype.setProperty + * @function + * @public + * @param {string} name - The name of the property to set. + * @param {string} value - The new value of the property. + */ + public abstract setProperty(name: string, value: string): void; + + /** + * Returns the current value of an arbitrary property. + * @member SpeechConfig.prototype.getProperty + * @function + * @public + * @param {string} name - The name of the property to query. + * @param {string} def - The value to return in case the property is not known. + * @returns {string} The current value, or provided default, of the given property. + */ + public abstract getProperty(name: string, def?: string): string; + + /** + * Sets output format. + * @member SpeechConfig.prototype.outputFormat + * @function + * @public + */ + public abstract set outputFormat(format: OutputFormat); + + /** + * Gets output format. + * @member SpeechConfig.prototype.outputFormat + * @function + * @public + * @returns {OutputFormat} Returns the output format. + */ + public abstract get outputFormat(): OutputFormat; + + /** + * Sets the endpoint ID of a customized speech model that is used for speech recognition. + * @member SpeechConfig.prototype.endpointId + * @function + * @public + * @param {string} value - The endpoint ID + */ + public abstract set endpointId(value: string); + + /** + * Gets the endpoint ID of a customized speech model that is used for speech recognition. + * @member SpeechConfig.prototype.endpointId + * @function + * @public + * @return {string} The endpoint ID + */ + public abstract get endpointId(): string; + + /** + * Closes the configuration. + * @member SpeechConfig.prototype.close + * @function + * @public + */ + /* tslint:disable:no-empty */ + public close(): void { } +} + +/** + * @private + * @class SpeechConfigImpl + */ +// tslint:disable-next-line:max-classes-per-file +export class SpeechConfigImpl extends SpeechConfig { + + private privProperties: PropertyCollection; + + public constructor() { + super(); + this.privProperties = new PropertyCollection(); + this.speechRecognitionLanguage = "en-US"; // Should we have a default? + this.outputFormat = OutputFormat.Simple; + } + + public get properties(): PropertyCollection { + return this.privProperties; + } + + public get endPoint(): URL { + return new URL(this.privProperties.getProperty(PropertyId.SpeechServiceConnection_Endpoint)); + } + + public get subscriptionKey(): string { + return this.privProperties.getProperty(PropertyId.SpeechServiceConnection_Key); + } + + public get region(): string { + return this.privProperties.getProperty(PropertyId.SpeechServiceConnection_Region); + } + + public get authorizationToken(): string { + return this.privProperties.getProperty(PropertyId.SpeechServiceAuthorization_Token); + } + + public set authorizationToken(value: string) { + this.privProperties.setProperty(PropertyId.SpeechServiceAuthorization_Token, value); + } + + public get speechRecognitionLanguage(): string { + return this.privProperties.getProperty(PropertyId.SpeechServiceConnection_RecoLanguage); + } + + public set speechRecognitionLanguage(value: string) { + this.privProperties.setProperty(PropertyId.SpeechServiceConnection_RecoLanguage, value); + } + + public get outputFormat(): OutputFormat { + return (OutputFormat as any)[this.privProperties.getProperty(OutputFormatPropertyName, undefined)]; + } + + public set outputFormat(value: OutputFormat) { + this.privProperties.setProperty(OutputFormatPropertyName, OutputFormat[value]); + } + + public set endpointId(value: string) { + this.privProperties.setProperty(PropertyId.SpeechServiceConnection_EndpointId, value); + } + + public get endpointId(): string { + return this.privProperties.getProperty(PropertyId.SpeechServiceConnection_EndpointId); + } + + public setProperty(name: string | PropertyId, value: string): void { + Contracts.throwIfNullOrWhitespace(value, "value"); + + this.privProperties.setProperty(name, value); + } + + public getProperty(name: string | PropertyId, def?: string): string { + + return this.privProperties.getProperty(name, def); + } + + public clone(): SpeechConfigImpl { + const ret: SpeechConfigImpl = new SpeechConfigImpl(); + ret.privProperties = this.privProperties.clone(); + return ret; + } +} diff --git a/src/sdk/SpeechRecognitionCanceledEventArgs.ts b/src/sdk/SpeechRecognitionCanceledEventArgs.ts new file mode 100644 index 00000000..9af495df --- /dev/null +++ b/src/sdk/SpeechRecognitionCanceledEventArgs.ts @@ -0,0 +1,62 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +import { CancellationErrorCode, CancellationReason, RecognitionEventArgs } from "./Exports"; + +/** + * Defines content of a RecognitionErrorEvent. + * @class SpeechRecognitionCanceledEventArgs + */ +export class SpeechRecognitionCanceledEventArgs extends RecognitionEventArgs { + private privReason: CancellationReason; + private privErrorDetails: string; + private privErrorCode: CancellationErrorCode; + + /** + * Creates and initializes an instance of this class. + * @constructor + * @param {CancellationReason} reason - The cancellation reason. + * @param {string} errorDetails - Error details, if provided. + * @param {number} offset - The offset. + * @param {string} sessionId - The session id. + */ + public constructor(reason: CancellationReason, errorDetails: string, + errorCode: CancellationErrorCode, offset?: number, sessionId?: string) { + super(offset, sessionId); + + this.privReason = reason; + this.privErrorDetails = errorDetails; + this.privErrorCode = errorCode; + } + + /** + * The reason the recognition was canceled. + * @member SpeechRecognitionCanceledEventArgs.prototype.reason + * @function + * @public + * @returns {CancellationReason} Specifies the reason canceled. + */ + public get reason(): CancellationReason { + return this.privReason; + } + + /** + * The error code in case of an unsuccessful recognition. + * Added in version 1.1.0. + * @return An error code that represents the error reason. + */ + public get errorCode(): CancellationErrorCode { + return this.privErrorCode; + } + + /** + * In case of an unsuccessful recognition, provides details of the occurred error. + * @member SpeechRecognitionCanceledEventArgs.prototype.errorDetails + * @function + * @public + * @returns {string} A String that represents the error details. + */ + public get errorDetails(): string { + return this.privErrorDetails; + } +} diff --git a/src/sdk/SpeechRecognitionEventArgs.ts b/src/sdk/SpeechRecognitionEventArgs.ts new file mode 100644 index 00000000..56d171f4 --- /dev/null +++ b/src/sdk/SpeechRecognitionEventArgs.ts @@ -0,0 +1,36 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +import { RecognitionEventArgs, SpeechRecognitionResult } from "./Exports"; + +/** + * Defines contents of speech recognizing/recognized event. + * @class SpeechRecognitionEventArgs + */ +export class SpeechRecognitionEventArgs extends RecognitionEventArgs { + private privResult: SpeechRecognitionResult; + + /** + * Creates and initializes an instance of this class. + * @constructor + * @param {SpeechRecognitionResult} result - The speech recognition result. + * @param {number} offset - The offset. + * @param {string} sessionId - The session id. + */ + public constructor(result: SpeechRecognitionResult, offset?: number, sessionId?: string) { + super(offset, sessionId); + + this.privResult = result; + } + + /** + * Specifies the recognition result. + * @member SpeechRecognitionEventArgs.prototype.result + * @function + * @public + * @returns {SpeechRecognitionResult} the recognition result. + */ + public get result(): SpeechRecognitionResult { + return this.privResult; + } +} diff --git a/src/sdk/SpeechRecognitionResult.ts b/src/sdk/SpeechRecognitionResult.ts new file mode 100644 index 00000000..5b077ec5 --- /dev/null +++ b/src/sdk/SpeechRecognitionResult.ts @@ -0,0 +1,29 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +import { PropertyCollection, RecognitionResult, ResultReason } from "./Exports"; + +/** + * Defines result of speech recognition. + * @class SpeechRecognitionResult + */ +export class SpeechRecognitionResult extends RecognitionResult { + /** + * Creates and initializes an instance of this class. + * @constructor + * @public + * @param {string} resultId - The result id. + * @param {ResultReason} reason - The reason. + * @param {string} text - The recognized text. + * @param {number} duration - The duration. + * @param {number} offset - The offset into the stream. + * @param {string} errorDetails - Error details, if provided. + * @param {string} json - Additional Json, if provided. + * @param {PropertyCollection} properties - Additional properties, if provided. + */ + public constructor(resultId?: string, reason?: ResultReason, text?: string, + duration?: number, offset?: number, errorDetails?: string, + json?: string, properties?: PropertyCollection) { + super(resultId, reason, text, duration, offset, errorDetails, json, properties); + } +} diff --git a/src/sdk/SpeechRecognizer.ts b/src/sdk/SpeechRecognizer.ts new file mode 100644 index 00000000..736003df --- /dev/null +++ b/src/sdk/SpeechRecognizer.ts @@ -0,0 +1,381 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +import { + IAuthentication, + IConnectionFactory, + OutputFormatPropertyName, + PlatformConfig, + RecognitionMode, + RecognizerConfig, + ServiceRecognizerBase, + SpeechServiceRecognizer, +} from "../common.speech/Exports"; +import { SpeechConnectionFactory } from "../common.speech/SpeechConnectionFactory"; +import { AudioConfigImpl } from "./Audio/AudioConfig"; +import { Contracts } from "./Contracts"; +import { + AudioConfig, + KeywordRecognitionModel, + OutputFormat, + PropertyCollection, + PropertyId, + Recognizer, + SpeechRecognitionCanceledEventArgs, + SpeechRecognitionEventArgs, + SpeechRecognitionResult, +} from "./Exports"; +import { SpeechConfig, SpeechConfigImpl } from "./SpeechConfig"; + +/** + * Performs speech recognition from microphone, file, or other audio input streams, and gets transcribed text as result. + * @class SpeechRecognizer + */ +export class SpeechRecognizer extends Recognizer { + private privDisposedSpeechRecognizer: boolean = false; + private privProperties: PropertyCollection; + + /** + * SpeechRecognizer constructor. + * @constructor + * @param {SpeechConfig} speechConfig - An set of initial properties for this recognizer + * @param {AudioConfig} audioConfig - An optional audio configuration associated with the recognizer + */ + public constructor(speechConfig: SpeechConfig, audioConfig?: AudioConfig) { + super(audioConfig); + + const speechConfigImpl: SpeechConfigImpl = speechConfig as SpeechConfigImpl; + Contracts.throwIfNull(speechConfigImpl, "speechConfig"); + this.privProperties = speechConfigImpl.properties.clone(); + + Contracts.throwIfNullOrWhitespace( + speechConfigImpl.properties.getProperty(PropertyId.SpeechServiceConnection_RecoLanguage), + PropertyId[PropertyId.SpeechServiceConnection_RecoLanguage]); + } + + /** + * The event recognizing signals that an intermediate recognition result is received. + * @member SpeechRecognizer.prototype.recognizing + * @function + * @public + */ + public recognizing: (sender: Recognizer, event: SpeechRecognitionEventArgs) => void; + + /** + * The event recognized signals that a final recognition result is received. + * @member SpeechRecognizer.prototype.recognized + * @function + * @public + */ + public recognized: (sender: Recognizer, event: SpeechRecognitionEventArgs) => void; + + /** + * The event canceled signals that an error occurred during recognition. + * @member SpeechRecognizer.prototype.canceled + * @function + * @public + */ + public canceled: (sender: Recognizer, event: SpeechRecognitionCanceledEventArgs) => void; + + /** + * Gets the endpoint id of a customized speech model that is used for speech recognition. + * @member SpeechRecognizer.prototype.endpointId + * @function + * @public + * @returns {string} the endpoint id of a customized speech model that is used for speech recognition. + */ + public get endpointId(): string { + Contracts.throwIfDisposed(this.privDisposedSpeechRecognizer); + + return this.properties.getProperty(PropertyId.SpeechServiceConnection_EndpointId, "00000000-0000-0000-0000-000000000000"); + } + + /** + * Sets the authorization token used to communicate with the service. + * @member SpeechRecognizer.prototype.authorizationToken + * @function + * @public + * @param {string} token - Authorization token. + */ + public set authorizationToken(token: string) { + Contracts.throwIfNullOrWhitespace(token, "token"); + this.properties.setProperty(PropertyId.SpeechServiceAuthorization_Token, token); + } + + /** + * Gets the authorization token used to communicate with the service. + * @member SpeechRecognizer.prototype.authorizationToken + * @function + * @public + * @returns {string} Authorization token. + */ + public get authorizationToken(): string { + return this.properties.getProperty(PropertyId.SpeechServiceAuthorization_Token); + } + + /** + * Gets the spoken language of recognition. + * @member SpeechRecognizer.prototype.speechRecognitionLanguage + * @function + * @public + * @returns {string} The spoken language of recognition. + */ + public get speechRecognitionLanguage(): string { + Contracts.throwIfDisposed(this.privDisposedSpeechRecognizer); + + return this.properties.getProperty(PropertyId.SpeechServiceConnection_RecoLanguage); + } + + /** + * Gets the output format of recognition. + * @member SpeechRecognizer.prototype.outputFormat + * @function + * @public + * @returns {OutputFormat} The output format of recognition. + */ + public get outputFormat(): OutputFormat { + Contracts.throwIfDisposed(this.privDisposedSpeechRecognizer); + + if (this.properties.getProperty(OutputFormatPropertyName, OutputFormat[OutputFormat.Simple]) === OutputFormat[OutputFormat.Simple]) { + return OutputFormat.Simple; + } else { + return OutputFormat.Detailed; + } + } + + /** + * The collection of properties and their values defined for this SpeechRecognizer. + * @member SpeechRecognizer.prototype.properties + * @function + * @public + * @returns {PropertyCollection} The collection of properties and their values defined for this SpeechRecognizer. + */ + public get properties(): PropertyCollection { + return this.privProperties; + } + + /** + * Starts speech recognition, and stops after the first utterance is recognized. + * The task returns the recognition text as result. + * Note: RecognizeOnceAsync() returns when the first utterance has been recognized, + * so it is suitable only for single shot recognition + * like command or query. For long-running recognition, use StartContinuousRecognitionAsync() instead. + * @member SpeechRecognizer.prototype.recognizeOnceAsync + * @function + * @public + * @param cb - Callback that received the SpeechRecognitionResult. + * @param err - Callback invoked in case of an error. + */ + public recognizeOnceAsync(cb?: (e: SpeechRecognitionResult) => void, err?: (e: string) => void): void { + try { + Contracts.throwIfDisposed(this.privDisposedSpeechRecognizer); + + this.implCloseExistingRecognizer(); + + this.privReco = this.implRecognizerSetup( + RecognitionMode.Interactive, + this.properties, + this.audioConfig, + new SpeechConnectionFactory()); + + this.implRecognizerStart(this.privReco, (e: SpeechRecognitionResult) => { + this.implCloseExistingRecognizer(); + if (!!cb) { + cb(e); + } + }, (e: string) => { + this.implCloseExistingRecognizer(); + if (!!err) { + err(e); + } + }); + } catch (error) { + if (!!err) { + if (error instanceof Error) { + const typedError: Error = error as Error; + err(typedError.name + ": " + typedError.message); + } else { + err(error); + } + } + } + } + + /** + * Starts speech recognition, until stopContinuousRecognitionAsync() is called. + * User must subscribe to events to receive recognition results. + * @member SpeechRecognizer.prototype.startContinuousRecognitionAsync + * @function + * @public + * @param cb - Callback invoked once the recognition has started. + * @param err - Callback invoked in case of an error. + */ + public startContinuousRecognitionAsync(cb?: () => void, err?: (e: string) => void): void { + try { + Contracts.throwIfDisposed(this.privDisposedSpeechRecognizer); + + this.implCloseExistingRecognizer(); + + this.privReco = this.implRecognizerSetup( + RecognitionMode.Conversation, + this.properties, + this.audioConfig, + new SpeechConnectionFactory()); + + this.implRecognizerStart(this.privReco, undefined, undefined); + + // report result to promise. + if (!!cb) { + try { + cb(); + } catch (e) { + if (!!err) { + err(e); + } + } + cb = undefined; + } + } catch (error) { + if (!!err) { + if (error instanceof Error) { + const typedError: Error = error as Error; + err(typedError.name + ": " + typedError.message); + } else { + err(error); + } + } + } + } + + /** + * Stops continuous speech recognition. + * @member SpeechRecognizer.prototype.stopContinuousRecognitionAsync + * @function + * @public + * @param cb - Callback invoked once the recognition has stopped. + * @param err - Callback invoked in case of an error. + */ + public stopContinuousRecognitionAsync(cb?: () => void, err?: (e: string) => void): void { + try { + Contracts.throwIfDisposed(this.privDisposedSpeechRecognizer); + + this.implCloseExistingRecognizer(); + + if (!!cb) { + try { + cb(); + } catch (e) { + if (!!err) { + err(e); + } + } + } + } catch (error) { + if (!!err) { + if (error instanceof Error) { + const typedError: Error = error as Error; + err(typedError.name + ": " + typedError.message); + } else { + err(error); + } + } + } + } + + /** + * Starts speech recognition with keyword spotting, until + * stopKeywordRecognitionAsync() is called. + * User must subscribe to events to receive recognition results. + * Note: Key word spotting functionality is only available on the + * Speech Devices SDK. This functionality is currently not included in the SDK itself. + * @member SpeechRecognizer.prototype.startKeywordRecognitionAsync + * @function + * @public + * @param {KeywordRecognitionModel} model The keyword recognition model that + * specifies the keyword to be recognized. + * @param cb - Callback invoked once the recognition has started. + * @param err - Callback invoked in case of an error. + */ + public startKeywordRecognitionAsync(model: KeywordRecognitionModel, cb?: () => void, err?: (e: string) => void): void { + Contracts.throwIfNull(model, "model"); + + if (!!err) { + err("Not yet implemented."); + } + } + + /** + * Stops continuous speech recognition. + * Note: Key word spotting functionality is only available on the + * Speech Devices SDK. This functionality is currently not included in the SDK itself. + * @member SpeechRecognizer.prototype.stopKeywordRecognitionAsync + * @function + * @public + * @param cb - Callback invoked once the recognition has stopped. + * @param err - Callback invoked in case of an error. + */ + public stopKeywordRecognitionAsync(cb?: () => void, err?: (e: string) => void): void { + if (!!cb) { + cb(); + } + } + + /** + * closes all external resources held by an instance of this class. + * @member SpeechRecognizer.prototype.close + * @function + * @public + */ + public close(): void { + Contracts.throwIfDisposed(this.privDisposedSpeechRecognizer); + + this.dispose(true); + } + + /** + * Disposes any resources held by the object. + * @member SpeechRecognizer.prototype.dispose + * @function + * @public + * @param {boolean} disposing - true if disposing the object. + */ + protected dispose(disposing: boolean): void { + if (this.privDisposedSpeechRecognizer) { + return; + } + + if (disposing) { + this.implCloseExistingRecognizer(); + this.privDisposedSpeechRecognizer = true; + } + + super.dispose(disposing); + } + + protected createRecognizerConfig(speechConfig: PlatformConfig, recognitionMode: RecognitionMode): RecognizerConfig { + return new RecognizerConfig( + speechConfig, + recognitionMode, + this.properties); + } + + protected createServiceRecognizer( + authentication: IAuthentication, + connectionFactory: IConnectionFactory, + audioConfig: AudioConfig, + recognizerConfig: RecognizerConfig): ServiceRecognizerBase { + const configImpl: AudioConfigImpl = audioConfig as AudioConfigImpl; + return new SpeechServiceRecognizer(authentication, connectionFactory, configImpl, recognizerConfig, this); + } + + // tslint:disable-next-line:member-ordering + private privReco: ServiceRecognizerBase; + + private implCloseExistingRecognizer(): void { + if (this.privReco) { + this.privReco.audioSource.turnOff(); + this.privReco.dispose(); + this.privReco = undefined; + } + } +} diff --git a/src/sdk/SpeechTranslationConfig.ts b/src/sdk/SpeechTranslationConfig.ts new file mode 100644 index 00000000..44a2f0a4 --- /dev/null +++ b/src/sdk/SpeechTranslationConfig.ts @@ -0,0 +1,361 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +import { OutputFormatPropertyName } from "../common.speech/Exports"; +import { Contracts } from "./Contracts"; +import { OutputFormat, PropertyCollection, PropertyId, SpeechConfig } from "./Exports"; + +/** + * Speech translation configuration. + * @class SpeechTranslationConfig + */ +export abstract class SpeechTranslationConfig extends SpeechConfig { + + /** + * Creates an instance of recognizer config. + */ + protected constructor() { + super(); + } + + /** + * Static instance of SpeechTranslationConfig returned by passing a subscription key and service region. + * @member SpeechTranslationConfig.fromSubscription + * @function + * @public + * @param {string} subscriptionKey - The subscription key. + * @param {string} region - The region name (see the region page). + * @returns {SpeechTranslationConfig} The speech translation config. + */ + public static fromSubscription(subscriptionKey: string, region: string): SpeechTranslationConfig { + Contracts.throwIfNullOrWhitespace(subscriptionKey, "subscriptionKey"); + Contracts.throwIfNullOrWhitespace(region, "region"); + + const ret: SpeechTranslationConfigImpl = new SpeechTranslationConfigImpl(); + ret.properties.setProperty(PropertyId.SpeechServiceConnection_Key, subscriptionKey); + ret.properties.setProperty(PropertyId.SpeechServiceConnection_Region, region); + return ret; + } + + /** + * Static instance of SpeechTranslationConfig returned by passing authorization token and service region. + * Note: The caller needs to ensure that the authorization token is valid. Before the authorization token + * expires, the caller needs to refresh it by setting the property authorizationToken with a new + * valid token. Otherwise, all the recognizers created by this SpeechTranslationConfig instance + * will encounter errors during recognition. + * @member SpeechTranslationConfig.fromAuthorizationToken + * @function + * @public + * @param {string} authorizationToken - The authorization token. + * @param {string} region - The region name (see the region page). + * @returns {SpeechTranslationConfig} The speech translation config. + */ + public static fromAuthorizationToken(authorizationToken: string, region: string): SpeechTranslationConfig { + Contracts.throwIfNullOrWhitespace(authorizationToken, "authorizationToken"); + Contracts.throwIfNullOrWhitespace(region, "region"); + + const ret: SpeechTranslationConfigImpl = new SpeechTranslationConfigImpl(); + ret.properties.setProperty(PropertyId.SpeechServiceAuthorization_Token, authorizationToken); + ret.properties.setProperty(PropertyId.SpeechServiceConnection_Region, region); + return ret; + } + + /** + * Creates an instance of the speech translation config with specified endpoint and subscription key. + * This method is intended only for users who use a non-standard service endpoint or paramters. + * Note: The query properties specified in the endpoint URL are not changed, even if they are + * set by any other APIs. For example, if language is defined in the uri as query parameter + * "language=de-DE", and also set by the speechRecognitionLanguage property, the language + * setting in uri takes precedence, and the effective language is "de-DE". + * Only the properties that are not specified in the endpoint URL can be set by other APIs. + * @member SpeechTranslationConfig.fromEndpoint + * @function + * @public + * @param {URL} endpoint - The service endpoint to connect to. + * @param {string} subscriptionKey - The subscription key. + * @returns {SpeechTranslationConfig} A speech config instance. + */ + public static fromEndpoint(endpoint: URL, subscriptionKey: string): SpeechTranslationConfig { + Contracts.throwIfNull(endpoint, "endpoint"); + Contracts.throwIfNullOrWhitespace(subscriptionKey, "subscriptionKey"); + + const ret: SpeechTranslationConfigImpl = new SpeechTranslationConfigImpl(); + ret.properties.setProperty(PropertyId.SpeechServiceConnection_Endpoint, endpoint.href); + ret.properties.setProperty(PropertyId.SpeechServiceConnection_Key, subscriptionKey); + return ret; + } + + /** + * Sets the authorization token. + * If this is set, subscription key is ignored. + * User needs to make sure the provided authorization token is valid and not expired. + * @member SpeechTranslationConfig.prototype.authorizationToken + * @function + * @public + * @param {string} value - The authorization token. + */ + public abstract set authorizationToken(value: string); + + /** + * Sets the authorization token. + * If this is set, subscription key is ignored. + * User needs to make sure the provided authorization token is valid and not expired. + * @member SpeechTranslationConfig.prototype.speechRecognitionLanguage + * @function + * @public + * @param {string} value - The authorization token. + */ + public abstract set speechRecognitionLanguage(value: string); + + /** + * Add a (text) target language to translate into. + * @member SpeechTranslationConfig.prototype.addTargetLanguage + * @function + * @public + * @param {string} value - The language such as de-DE + */ + public abstract addTargetLanguage(value: string): void; + + /** + * Add a (text) target language to translate into. + * @member SpeechTranslationConfig.prototype.targetLanguages + * @function + * @public + * @param {string} value - The language such as de-DE + */ + public abstract get targetLanguages(): string[]; + + /** + * Returns the selected voice name. + * @member SpeechTranslationConfig.prototype.voiceName + * @function + * @public + * @returns {string} The voice name. + */ + public abstract get voiceName(): string; + + /** + * Sets voice of the translated language, enable voice synthesis output. + * @member SpeechTranslationConfig.prototype.voiceName + * @function + * @public + * @param {string} value - The name of the voice. + */ + public abstract set voiceName(value: string); + + /** + * Sets a named property as value + * @member SpeechTranslationConfig.prototype.setProperty + * @function + * @public + * @param {string} name - The name of the property. + * @param {string} value - The value. + */ + public abstract setProperty(name: string, value: string): void; + + /** + * Dispose of associated resources. + * @member SpeechTranslationConfig.prototype.close + * @function + * @public + */ + public abstract close(): void; +} + +/** + * @private + * @class SpeechTranslationConfigImpl + */ +// tslint:disable-next-line:max-classes-per-file +export class SpeechTranslationConfigImpl extends SpeechTranslationConfig { + private privSpeechProperties: PropertyCollection; + + public constructor() { + super(); + this.privSpeechProperties = new PropertyCollection(); + this.outputFormat = OutputFormat.Simple; + } + /** + * Sets the authorization token. + * If this is set, subscription key is ignored. + * User needs to make sure the provided authorization token is valid and not expired. + * @member SpeechTranslationConfigImpl.prototype.authorizationToken + * @function + * @public + * @param {string} value - The authorization token. + */ + public set authorizationToken(value: string) { + Contracts.throwIfNullOrWhitespace(value, "value"); + + this.privSpeechProperties.setProperty(PropertyId.SpeechServiceAuthorization_Token, value); + } + + /** + * Sets the authorization token. + * If this is set, subscription key is ignored. + * User needs to make sure the provided authorization token is valid and not expired. + * @member SpeechTranslationConfigImpl.prototype.speechRecognitionLanguage + * @function + * @public + * @param {string} value - The authorization token. + */ + public set speechRecognitionLanguage(value: string) { + Contracts.throwIfNullOrWhitespace(value, "value"); + this.privSpeechProperties.setProperty(PropertyId.SpeechServiceConnection_RecoLanguage, value); + } + + /** + * @member SpeechTranslationConfigImpl.prototype.subscriptionKey + * @function + * @public + */ + public get subscriptionKey(): string { + return this.privSpeechProperties.getProperty(PropertyId[PropertyId.SpeechServiceConnection_Key]); + } + + /** + * @member SpeechTranslationConfigImpl.prototype.outputFormat + * @function + * @public + */ + public get outputFormat(): OutputFormat { + return (OutputFormat as any)[this.privSpeechProperties.getProperty(OutputFormatPropertyName, undefined)]; + } + + /** + * @member SpeechTranslationConfigImpl.prototype.outputFormat + * @function + * @public + */ + public set outputFormat(value: OutputFormat) { + this.privSpeechProperties.setProperty(OutputFormatPropertyName, OutputFormat[value]); + } + + /** + * @member SpeechTranslationConfigImpl.prototype.endpointId + * @function + * @public + */ + public set endpointId(value: string) { + this.privSpeechProperties.setProperty(PropertyId.SpeechServiceConnection_Endpoint, value); + } + + /** + * @member SpeechTranslationConfigImpl.prototype.endpointId + * @function + * @public + */ + public get endpointId(): string { + return this.privSpeechProperties.getProperty(PropertyId.SpeechServiceConnection_EndpointId); + } + /** + * Add a (text) target language to translate into. + * @member SpeechTranslationConfigImpl.prototype.addTargetLanguage + * @function + * @public + * @param {string} value - The language such as de-DE + */ + public addTargetLanguage(value: string): void { + Contracts.throwIfNullOrWhitespace(value, "value"); + + const languages: string[] = this.targetLanguages; + languages.push(value); + this.privSpeechProperties.setProperty(PropertyId.SpeechServiceConnection_TranslationToLanguages, languages.join(",")); + } + + /** + * Add a (text) target language to translate into. + * @member SpeechTranslationConfigImpl.prototype.targetLanguages + * @function + * @public + * @param {string} value - The language such as de-DE + */ + public get targetLanguages(): string[] { + + if (this.privSpeechProperties.getProperty(PropertyId.SpeechServiceConnection_TranslationToLanguages, undefined) !== undefined) { + return this.privSpeechProperties.getProperty(PropertyId.SpeechServiceConnection_TranslationToLanguages).split(","); + } else { + return []; + } + + } + + /** + * @member SpeechTranslationConfigImpl.prototype.voiceName + * @function + * @public + */ + public get voiceName(): string { + return this.getProperty(PropertyId[PropertyId.SpeechServiceConnection_TranslationVoice]); + } + + /** + * Sets voice of the translated language, enable voice synthesis output. + * @member SpeechTranslationConfigImpl.prototype.voiceName + * @function + * @public + * @param {string} value - The name of the voice. + */ + public set voiceName(value: string) { + Contracts.throwIfNullOrWhitespace(value, "value"); + + this.privSpeechProperties.setProperty(PropertyId.SpeechServiceConnection_TranslationVoice, value); + } + + /** + * Provides the region. + * @member SpeechTranslationConfigImpl.prototype.region + * @function + * @public + * @returns {string} The region. + */ + public get region(): string { + return this.privSpeechProperties.getProperty(PropertyId.SpeechServiceConnection_Region); + } + + /** + * Allows for setting arbitrary properties. + * @member SpeechTranslationConfigImpl.prototype.setProperty + * @function + * @public + * @param {string} name - The name of the property. + * @param {string} value - The value of the property. + */ + public setProperty(name: string, value: string): void { + this.privSpeechProperties.setProperty(name, value); + } + + /** + * Allows for retrieving arbitrary property values. + * @member SpeechTranslationConfigImpl.prototype.getProperty + * @function + * @public + * @param {string} name - The name of the property. + * @param {string} def - The default value of the property in case it is not set. + * @returns {string} The value of the property. + */ + public getProperty(name: string, def?: string): string { + return this.privSpeechProperties.getProperty(name, def); + } + + /** + * Provides access to custom properties. + * @member SpeechTranslationConfigImpl.prototype.properties + * @function + * @public + * @returns {PropertyCollection} The properties. + */ + public get properties(): PropertyCollection { + return this.privSpeechProperties; + } + + /** + * Dispose of associated resources. + * @member SpeechTranslationConfigImpl.prototype.close + * @function + * @public + */ + public close(): void { + return; + } +} diff --git a/src/sdk/TranslationRecognitionCanceledEventArgs.ts b/src/sdk/TranslationRecognitionCanceledEventArgs.ts new file mode 100644 index 00000000..841498fa --- /dev/null +++ b/src/sdk/TranslationRecognitionCanceledEventArgs.ts @@ -0,0 +1,90 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +import { CancellationErrorCode, CancellationReason, TranslationRecognitionResult } from "./Exports"; + +/** + * Define payload of speech recognition canceled result events. + * @class TranslationRecognitionCanceledEventArgs + */ +export class TranslationRecognitionCanceledEventArgs { + private privResult: TranslationRecognitionResult; + private privSessionId: string; + private privCancelReason: CancellationReason; + private privErrorDetails: string; + private privErrorCode: CancellationErrorCode; + + /** + * Creates and initializes an instance of this class. + * @constructor + * @param {string} sessionid - The session id. + * @param {CancellationReason} cancellationReason - The cancellation reason. + * @param {string} errorDetails - Error details, if provided. + * @param {TranslationRecognitionResult} result - The result. + */ + public constructor( + sessionid: string, + cancellationReason: CancellationReason, + errorDetails: string, + errorCode: CancellationErrorCode, + result: TranslationRecognitionResult) { + this.privCancelReason = cancellationReason; + this.privErrorDetails = errorDetails; + this.privResult = result; + this.privSessionId = sessionid; + this.privErrorCode = errorCode; + } + + /** + * Specifies the recognition result. + * @member TranslationRecognitionCanceledEventArgs.prototype.result + * @function + * @public + * @returns {TranslationRecognitionResult} the recognition result. + */ + public get result(): TranslationRecognitionResult { + return this.privResult; + } + + /** + * Specifies the session identifier. + * @member TranslationRecognitionCanceledEventArgs.prototype.sessionId + * @function + * @public + * @returns {string} the session identifier. + */ + public get sessionId(): string { + return this.privSessionId; + } + + /** + * The reason the recognition was canceled. + * @member TranslationRecognitionCanceledEventArgs.prototype.reason + * @function + * @public + * @returns {CancellationReason} Specifies the reason canceled. + */ + public get reason(): CancellationReason { + return this.privCancelReason; + } + + /** + * The error code in case of an unsuccessful recognition. + * Added in version 1.1.0. + * @return An error code that represents the error reason. + */ + public get errorCode(): CancellationErrorCode { + return this.privErrorCode; + } + + /** + * In case of an unsuccessful recognition, provides details of the occurred error. + * @member TranslationRecognitionCanceledEventArgs.prototype.errorDetails + * @function + * @public + * @returns {string} A String that represents the error details. + */ + public get errorDetails(): string { + return this.privErrorDetails; + } +} diff --git a/src/sdk/TranslationRecognitionEventArgs.ts b/src/sdk/TranslationRecognitionEventArgs.ts new file mode 100644 index 00000000..639034d9 --- /dev/null +++ b/src/sdk/TranslationRecognitionEventArgs.ts @@ -0,0 +1,36 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +import { RecognitionEventArgs, TranslationRecognitionResult } from "./Exports"; + +/** + * Translation text result event arguments. + * @class TranslationRecognitionEventArgs + */ +export class TranslationRecognitionEventArgs extends RecognitionEventArgs { + private privResult: TranslationRecognitionResult; + + /** + * Creates and initializes an instance of this class. + * @constructor + * @param {TranslationRecognitionResult} result - The translation recognition result. + * @param {number} offset - The offset. + * @param {string} sessionId - The session id. + */ + public constructor(result: TranslationRecognitionResult, offset?: number, sessionId?: string) { + super(offset, sessionId); + + this.privResult = result; + } + + /** + * Specifies the recognition result. + * @member TranslationRecognitionEventArgs.prototype.result + * @function + * @public + * @returns {TranslationRecognitionResult} the recognition result. + */ + public get result(): TranslationRecognitionResult { + return this.privResult; + } +} diff --git a/src/sdk/TranslationRecognitionResult.ts b/src/sdk/TranslationRecognitionResult.ts new file mode 100644 index 00000000..9f665667 --- /dev/null +++ b/src/sdk/TranslationRecognitionResult.ts @@ -0,0 +1,47 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +import { PropertyCollection, ResultReason, SpeechRecognitionResult, Translations } from "./Exports"; + +/** + * Translation text result. + * @class TranslationRecognitionResult + */ +export class TranslationRecognitionResult extends SpeechRecognitionResult { + private privTranslations: Translations; + + /** + * Creates and initializes an instance of this class. + * @constructor + * @param {Translations} translations - The translations. + * @param {string} resultId - The result id. + * @param {ResultReason} reason - The reason. + * @param {string} text - The recognized text. + * @param {number} duration - The duration. + * @param {number} offset - The offset into the stream. + * @param {string} errorDetails - Error details, if provided. + * @param {string} json - Additional Json, if provided. + * @param {PropertyCollection} properties - Additional properties, if provided. + */ + public constructor(translations: Translations, resultId?: string, reason?: ResultReason, + text?: string, duration?: number, offset?: number, errorDetails?: string, + json?: string, properties?: PropertyCollection) { + super(resultId, reason, text, duration, offset, errorDetails, json, properties); + + this.privTranslations = translations; + } + + /** + * Presents the translation results. Each item in the dictionary represents + * a translation result in one of target languages, where the key is the name + * of the target language, in BCP-47 format, and the value is the translation + * text in the specified language. + * @member TranslationRecognitionResult.prototype.translations + * @function + * @public + * @returns {Translations} the current translation map that holds all translations requested. + */ + public get translations(): Translations { + return this.privTranslations; + } +} diff --git a/src/sdk/TranslationRecognizer.ts b/src/sdk/TranslationRecognizer.ts new file mode 100644 index 00000000..5a0b4669 --- /dev/null +++ b/src/sdk/TranslationRecognizer.ts @@ -0,0 +1,351 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +import { + IAuthentication, + IConnectionFactory, + PlatformConfig, + RecognitionMode, + RecognizerConfig, + ServiceRecognizerBase, + TranslationConnectionFactory, + TranslationServiceRecognizer, +} from "../common.speech/Exports"; +import { AudioConfigImpl } from "./Audio/AudioConfig"; +import { Contracts } from "./Contracts"; +import { + AudioConfig, + PropertyCollection, + PropertyId, + Recognizer, + TranslationRecognitionCanceledEventArgs, + TranslationRecognitionEventArgs, + TranslationRecognitionResult, + TranslationSynthesisEventArgs, +} from "./Exports"; +import { SpeechTranslationConfig, SpeechTranslationConfigImpl } from "./SpeechTranslationConfig"; + +/** + * Translation recognizer + * @class TranslationRecognizer + */ +export class TranslationRecognizer extends Recognizer { + private privDisposedTranslationRecognizer: boolean; + private privProperties: PropertyCollection; + + /** + * Initializes an instance of the TranslationRecognizer. + * @constructor + * @param {SpeechTranslationConfig} speechConfig - Set of properties to configure this recognizer. + * @param {AudioConfig} audioConfig - An optional audio config associated with the recognizer + */ + public constructor(speechConfig: SpeechTranslationConfig, audioConfig?: AudioConfig) { + const configImpl = speechConfig as SpeechTranslationConfigImpl; + Contracts.throwIfNull(configImpl, "speechConfig"); + + super(audioConfig); + + this.privDisposedTranslationRecognizer = false; + this.privProperties = configImpl.properties.clone(); + + if (this.properties.getProperty(PropertyId.SpeechServiceConnection_TranslationVoice, undefined) !== undefined) { + Contracts.throwIfNullOrWhitespace( + this.properties.getProperty(PropertyId.SpeechServiceConnection_TranslationVoice), + PropertyId[PropertyId.SpeechServiceConnection_TranslationVoice]); + } + + Contracts.throwIfNullOrWhitespace( + this.properties.getProperty(PropertyId.SpeechServiceConnection_TranslationToLanguages), + PropertyId[PropertyId.SpeechServiceConnection_TranslationToLanguages]); + + Contracts.throwIfNullOrWhitespace(this.properties.getProperty( + PropertyId.SpeechServiceConnection_RecoLanguage), + PropertyId[PropertyId.SpeechServiceConnection_RecoLanguage]); + } + + /** + * The event recognizing signals that an intermediate recognition result is received. + * @member TranslationRecognizer.prototype.recognizing + * @function + * @public + */ + public recognizing: (sender: TranslationRecognizer, event: TranslationRecognitionEventArgs) => void; + + /** + * The event recognized signals that a final recognition result is received. + * @member TranslationRecognizer.prototype.recognized + * @function + * @public + */ + public recognized: (sender: TranslationRecognizer, event: TranslationRecognitionEventArgs) => void; + + /** + * The event canceled signals that an error occurred during recognition. + * @member TranslationRecognizer.prototype.canceled + * @function + * @public + */ + public canceled: (sender: TranslationRecognizer, event: TranslationRecognitionCanceledEventArgs) => void; + + /** + * The event synthesizing signals that a translation synthesis result is received. + * @member TranslationRecognizer.prototype.synthesizing + * @function + * @public + */ + public synthesizing: (sender: TranslationRecognizer, event: TranslationSynthesisEventArgs) => void; + + /** + * Gets the language name that was set when the recognizer was created. + * @member TranslationRecognizer.prototype.speechRecognitionLanguage + * @function + * @public + * @returns {string} Gets the language name that was set when the recognizer was created. + */ + public get speechRecognitionLanguage(): string { + Contracts.throwIfDisposed(this.privDisposedTranslationRecognizer); + + return this.properties.getProperty(PropertyId.SpeechServiceConnection_RecoLanguage); + } + + /** + * Gets target languages for translation that were set when the recognizer was created. + * The language is specified in BCP-47 format. The translation will provide translated text for each of language. + * @member TranslationRecognizer.prototype.targetLanguages + * @function + * @public + * @returns {string[]} Gets target languages for translation that were set when the recognizer was created. + */ + public get targetLanguages(): string[] { + Contracts.throwIfDisposed(this.privDisposedTranslationRecognizer); + + return this.properties.getProperty(PropertyId.SpeechServiceConnection_TranslationToLanguages).split(","); + } + + /** + * Gets the name of output voice. + * @member TranslationRecognizer.prototype.voiceName + * @function + * @public + * @returns {string} the name of output voice. + */ + public get voiceName(): string { + Contracts.throwIfDisposed(this.privDisposedTranslationRecognizer); + + return this.properties.getProperty(PropertyId.SpeechServiceConnection_TranslationVoice, undefined); + } + + /** + * Gets the authorization token used to communicate with the service. + * @member TranslationRecognizer.prototype.authorizationToken + * @function + * @public + * @returns {string} Authorization token. + */ + public get authorizationToken(): string { + return this.properties.getProperty(PropertyId.SpeechServiceAuthorization_Token); + } + + /** + * Sets the authorization token used to communicate with the service. + * @member TranslationRecognizer.prototype.authorizationToken + * @function + * @public + * @param {string} value - Authorization token. + */ + public set authorizationToken(value: string) { + this.properties.setProperty(PropertyId.SpeechServiceAuthorization_Token, value); + } + + /** + * The collection of properties and their values defined for this TranslationRecognizer. + * @member TranslationRecognizer.prototype.properties + * @function + * @public + * @returns {PropertyCollection} The collection of properties and their values defined for this TranslationRecognizer. + */ + public get properties(): PropertyCollection { + return this.privProperties; + } + + /** + * Starts recognition and translation, and stops after the first utterance is recognized. + * The task returns the translation text as result. + * Note: recognizeOnceAsync returns when the first utterance has been recognized, so it is suitableonly + * for single shot recognition like command or query. For long-running recognition, + * use startContinuousRecognitionAsync() instead. + * @member TranslationRecognizer.prototype.recognizeOnceAsync + * @function + * @public + * @param cb - Callback that received the result when the translation has completed. + * @param err - Callback invoked in case of an error. + */ + public recognizeOnceAsync(cb?: (e: TranslationRecognitionResult) => void, err?: (e: string) => void): void { + try { + Contracts.throwIfDisposed(this.privDisposedTranslationRecognizer); + + this.implCloseExistingRecognizer(); + + this.privReco = this.implRecognizerSetup( + RecognitionMode.Conversation, + this.properties, + this.audioConfig, + new TranslationConnectionFactory()); + + this.implRecognizerStart( + this.privReco, + (e: TranslationRecognitionResult) => { + this.implCloseExistingRecognizer(); + if (!!cb) { + cb(e); + } + }, (e: string) => { + this.implCloseExistingRecognizer(); + if (!!err) { + err(e); + } + }); + } catch (error) { + if (!!err) { + if (error instanceof Error) { + const typedError: Error = error as Error; + err(typedError.name + ": " + typedError.message); + } else { + err(error); + } + } + } + } + + /** + * Starts recognition and translation, until stopContinuousRecognitionAsync() is called. + * User must subscribe to events to receive translation results. + * @member TranslationRecognizer.prototype.startContinuousRecognitionAsync + * @function + * @public + * @param cb - Callback that received the translation has started. + * @param err - Callback invoked in case of an error. + */ + public startContinuousRecognitionAsync(cb?: () => void, err?: (e: string) => void): void { + try { + Contracts.throwIfDisposed(this.privDisposedTranslationRecognizer); + + this.implCloseExistingRecognizer(); + + this.privReco = this.implRecognizerSetup( + RecognitionMode.Conversation, + this.properties, + this.audioConfig, + new TranslationConnectionFactory()); + + this.implRecognizerStart(this.privReco, undefined, undefined); + + // report result to promise. + if (!!cb) { + try { + cb(); + } catch (e) { + if (!!err) { + err(e); + } + } + cb = undefined; + } + } catch (error) { + if (!!err) { + if (error instanceof Error) { + const typedError: Error = error as Error; + err(typedError.name + ": " + typedError.message); + + } else { + err(error); + } + } + } + } + + /** + * Stops continuous recognition and translation. + * @member TranslationRecognizer.prototype.stopContinuousRecognitionAsync + * @function + * @public + * @param cb - Callback that received the translation has stopped. + * @param err - Callback invoked in case of an error. + */ + public stopContinuousRecognitionAsync(cb?: () => void, err?: (e: string) => void): void { + try { + Contracts.throwIfDisposed(this.privDisposedTranslationRecognizer); + + this.implCloseExistingRecognizer(); + + if (!!cb) { + try { + cb(); + } catch (e) { + if (!!err) { + err(e); + } + } + } + } catch (error) { + if (!!err) { + if (error instanceof Error) { + const typedError: Error = error as Error; + err(typedError.name + ": " + typedError.message); + } else { + err(error); + } + } + } + } + + /** + * closes all external resources held by an instance of this class. + * @member TranslationRecognizer.prototype.close + * @function + * @public + */ + public close(): void { + Contracts.throwIfDisposed(this.privDisposedTranslationRecognizer); + + this.dispose(true); + } + + protected dispose(disposing: boolean): boolean { + if (this.privDisposedTranslationRecognizer) { + return; + } + + if (disposing) { + this.implCloseExistingRecognizer(); + this.privDisposedTranslationRecognizer = true; + super.dispose(disposing); + } + } + + protected createRecognizerConfig(speechConfig: PlatformConfig, recognitionMode: RecognitionMode): RecognizerConfig { + return new RecognizerConfig(speechConfig, RecognitionMode.Conversation, this.properties); + } + + protected createServiceRecognizer( + authentication: IAuthentication, + connectionFactory: IConnectionFactory, + audioConfig: AudioConfig, + recognizerConfig: RecognizerConfig): ServiceRecognizerBase { + + const configImpl: AudioConfigImpl = audioConfig as AudioConfigImpl; + + return new TranslationServiceRecognizer(authentication, connectionFactory, configImpl, recognizerConfig, this); + } + + // tslint:disable-next-line:member-ordering + private privReco: ServiceRecognizerBase; + + private implCloseExistingRecognizer(): void { + if (this.privReco) { + this.privReco.audioSource.turnOff(); + this.privReco.dispose(); + this.privReco = undefined; + } + } +} diff --git a/src/sdk/TranslationSynthesisEventArgs.ts b/src/sdk/TranslationSynthesisEventArgs.ts new file mode 100644 index 00000000..bb95a3fa --- /dev/null +++ b/src/sdk/TranslationSynthesisEventArgs.ts @@ -0,0 +1,35 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +import { SessionEventArgs, TranslationSynthesisResult } from "./Exports"; + +/** + * Translation Synthesis event arguments + * @class TranslationSynthesisEventArgs + */ +export class TranslationSynthesisEventArgs extends SessionEventArgs { + private privResult: TranslationSynthesisResult; + + /** + * Creates and initializes an instance of this class. + * @constructor + * @param {TranslationSynthesisResult} result - The translation synthesis result. + * @param {string} sessionId - The session id. + */ + public constructor(result: TranslationSynthesisResult, sessionId?: string) { + super(sessionId); + + this.privResult = result; + } + + /** + * Specifies the translation synthesis result. + * @member TranslationSynthesisEventArgs.prototype.result + * @function + * @public + * @returns {TranslationSynthesisResult} Specifies the translation synthesis result. + */ + public get result(): TranslationSynthesisResult { + return this.privResult; + } +} diff --git a/src/sdk/TranslationSynthesisResult.ts b/src/sdk/TranslationSynthesisResult.ts new file mode 100644 index 00000000..26ae81e1 --- /dev/null +++ b/src/sdk/TranslationSynthesisResult.ts @@ -0,0 +1,47 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +import { ResultReason } from "./ResultReason"; + +/** + * Defines translation synthesis result, i.e. the voice output of the translated + * text in the target language. + * @class TranslationSynthesisResult + */ +export class TranslationSynthesisResult { + private privReason: ResultReason; + private privAudio: ArrayBuffer; + + /** + * Creates and initializes an instance of this class. + * @constructor + * @param {ResultReason} reason - The synthesis reason. + * @param {ArrayBuffer} audio - The audio data. + */ + constructor(reason: ResultReason, audio: ArrayBuffer) { + this.privReason = reason; + this.privAudio = audio; + } + + /** + * Translated text in the target language. + * @member TranslationSynthesisResult.prototype.audio + * @function + * @public + * @returns {ArrayBuffer} Translated audio in the target language. + */ + public get audio(): ArrayBuffer { + return this.privAudio; + } + + /** + * The synthesis status. + * @member TranslationSynthesisResult.prototype.reason + * @function + * @public + * @returns {ResultReason} The synthesis status. + */ + public get reason(): ResultReason { + return this.privReason; + } +} diff --git a/src/sdk/Translations.ts b/src/sdk/Translations.ts new file mode 100644 index 00000000..7d91fb29 --- /dev/null +++ b/src/sdk/Translations.ts @@ -0,0 +1,40 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +import { PropertyCollection } from "./Exports"; + +/** + * Represents collection of parameters and their values. + * @class Translation + */ +export class Translations { + // Use an PropertyCollection internally, just wrapping it to hide the | enum syntax it has. + private privMap: PropertyCollection = new PropertyCollection(); + + /** + * Returns the parameter value in type String. The parameter must have the same type as String. + * Currently only String, int and bool are allowed. + * If the name is not available, the specified defaultValue is returned. + * @member Translation.prototype.get + * @function + * @public + * @param {string} key - The parameter name. + * @param {string} def - The default value which is returned if the parameter is not available in the collection. + * @returns {string} value of the parameter. + */ + public get(key: string, def?: string): string { + return this.privMap.getProperty(key, def); + } + + /** + * Sets the String value of the parameter specified by name. + * @member Translation.prototype.set + * @function + * @public + * @param {string} key - The parameter name. + * @param {string} value - The value of the parameter. + */ + public set(key: string, value: string): void { + this.privMap.setProperty(key, value); + } +} diff --git a/tests/ByteBufferAudioFile.ts b/tests/ByteBufferAudioFile.ts new file mode 100644 index 00000000..01f97d4b --- /dev/null +++ b/tests/ByteBufferAudioFile.ts @@ -0,0 +1,12 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +export class ByteBufferAudioFile { + public static Load(buffer: ArrayBuffer): File { + + const parts: ArrayBuffer[] = [buffer]; + const file: File = new File(parts, "file.wav"); + + return file; + } +} diff --git a/tests/IntentRecognizerTests.ts b/tests/IntentRecognizerTests.ts new file mode 100644 index 00000000..774f0fd8 --- /dev/null +++ b/tests/IntentRecognizerTests.ts @@ -0,0 +1,911 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. +import { setTimeout } from "timers"; + +import * as sdk from "../microsoft.cognitiveservices.speech.sdk"; +import { ConsoleLoggingListener, WebsocketMessageAdapter } from "../src/common.browser/Exports"; +import { Events, EventType } from "../src/common/Exports"; + +import { ByteBufferAudioFile } from "./ByteBufferAudioFile"; +import { Settings } from "./Settings"; +import { default as WaitForCondition } from "./Utilities"; +import { WaveFileAudioInput } from "./WaveFileAudioInputStream"; + +import * as fs from "fs"; + +let objsToClose: any[]; + +beforeAll(() => { + // override inputs, if necessary + Settings.LoadSettings(); + Events.instance.attachListener(new ConsoleLoggingListener(EventType.Debug)); +}); + +// Test cases are run linerally, the only other mechanism to demark them in the output is to put a console line in each case and +// report the name. +beforeEach(() => { + objsToClose = []; + // tslint:disable-next-line:no-console + console.info("---------------------------------------Starting test case-----------------------------------"); + // tslint:disable-next-line:no-console + console.info("Sart Time: " + new Date(Date.now()).toLocaleString()); +}); + +afterEach(() => { + // tslint:disable-next-line:no-console + console.info("End Time: " + new Date(Date.now()).toLocaleString()); + objsToClose.forEach((value: any, index: number, array: any[]) => { + if (typeof value.close === "function") { + value.close(); + } + }); +}); + +const ValidateResultMatchesWaveFile = (res: sdk.SpeechRecognitionResult): void => { + expect(res).not.toBeUndefined(); + expect(res.text).toEqual(Settings.LuisWavFileText); + expect(Math.abs(res.duration - Settings.LuisWaveFileDuration) / Settings.LuisWaveFileDuration).toBeLessThanOrEqual(0.05); + expect(Math.abs(res.offset - Settings.LuisWaveFileOffset) / Settings.LuisWaveFileOffset).toBeLessThanOrEqual(0.05); +}; + +const BuildRecognizerFromWaveFile: (speechConfig?: sdk.SpeechConfig) => sdk.IntentRecognizer = (speechConfig?: sdk.SpeechConfig): sdk.IntentRecognizer => { + + let s: sdk.SpeechConfig = speechConfig; + if (s === undefined) { + s = BuildSpeechConfig(); + // Since we're not going to return it, mark it for closure. + objsToClose.push(s); + } + + const f: File = WaveFileAudioInput.LoadFile(Settings.LuisWaveFile); + const config: sdk.AudioConfig = sdk.AudioConfig.fromWavFileInput(f); + + const language: string = Settings.WaveFileLanguage; + if (s.speechRecognitionLanguage === undefined) { + s.speechRecognitionLanguage = language; + } + + const r: sdk.IntentRecognizer = new sdk.IntentRecognizer(s, config); + expect(r).not.toBeUndefined(); + + return r; +}; + +const BuildSpeechConfig: () => sdk.SpeechConfig = (): sdk.SpeechConfig => { + const s: sdk.SpeechConfig = sdk.SpeechConfig.fromSubscription(Settings.LuisSubscriptionKey, Settings.LuisRegion); + expect(s).not.toBeUndefined(); + return s; +}; + +describe.each([true, false])("Service based tests", (forceNodeWebSocket: boolean) => { + + beforeEach(() => { + // tslint:disable-next-line:no-console + console.info("forceNodeWebSocket: " + forceNodeWebSocket); + WebsocketMessageAdapter.forceNpmWebSocket = forceNodeWebSocket; + }); + + afterAll(() => { + WebsocketMessageAdapter.forceNpmWebSocket = false; + }); + + test("NoIntentsRecognizesSpeech", (done: jest.DoneCallback) => { + // tslint:disable-next-line:no-console + console.info("Name: NoIntentsRecognizesSpeech"); + const r: sdk.IntentRecognizer = BuildRecognizerFromWaveFile(); + objsToClose.push(r); + + r.recognizeOnceAsync( + (p2: sdk.IntentRecognitionResult) => { + + const res: sdk.IntentRecognitionResult = p2; + expect(res.errorDetails).toBeUndefined(); + expect(res.reason).toEqual(sdk.ResultReason.RecognizedSpeech); + expect(res).not.toBeUndefined(); + ValidateResultMatchesWaveFile(res); + done(); + }, + (error: string) => { + done.fail(error); + }); + }); + + test("AddNullIntent", () => { + // tslint:disable-next-line:no-console + console.info("Name: AddNullIntent"); + const r: sdk.IntentRecognizer = BuildRecognizerFromWaveFile(); + objsToClose.push(r); + + expect(() => r.addIntent("phrase", null)).toThrow(); + }); + + test("AddNullPhrase", () => { + // tslint:disable-next-line:no-console + console.info("Name: AddNullPhrase"); + const r: sdk.IntentRecognizer = BuildRecognizerFromWaveFile(); + objsToClose.push(r); + + expect(() => r.addIntent(null, "ID")).toThrow(); + }); + + test("RoundTripWithGoodIntent", (done: jest.DoneCallback) => { + // tslint:disable-next-line:no-console + console.info("Name: RoundTripWithGoodIntent"); + + const r: sdk.IntentRecognizer = BuildRecognizerFromWaveFile(); + objsToClose.push(r); + + const lm: sdk.LanguageUnderstandingModel = sdk.LanguageUnderstandingModel.fromAppId(Settings.LuisAppId); + + r.addIntentWithLanguageModel(Settings.LuisValidIntentId, lm); + + r.recognizeOnceAsync( + (p2: sdk.IntentRecognitionResult) => { + const res: sdk.IntentRecognitionResult = p2; + expect(res).not.toBeUndefined(); + expect(res.errorDetails).toBeUndefined(); + expect(sdk.ResultReason[res.reason]).toEqual(sdk.ResultReason[sdk.ResultReason.RecognizedIntent]); + expect(res.intentId).toEqual(Settings.LuisValidIntentId); + ValidateResultMatchesWaveFile(res); + done(); + }, + (error: string) => { + done.fail(error); + }); + }); + + class BadLangModel extends sdk.LanguageUnderstandingModel { + public constructor() { + super(); + } + public appId: string; + } + + test("AddIntentWithBadModel", () => { + // tslint:disable-next-line:no-console + console.info("Name: AddIntentWithBadModel"); + const r: sdk.IntentRecognizer = BuildRecognizerFromWaveFile(); + objsToClose.push(r); + + const langModel: BadLangModel = new BadLangModel(); + langModel.appId = ""; + + expect(() => r.addIntentWithLanguageModel("IntentId", langModel, "IntentName")).toThrow(); + }); + + test("InitialSilenceTimeout (pull)", (done: jest.DoneCallback) => { + // tslint:disable-next-line:no-console + console.info("Name: InitialSilenceTimeout (pull)"); + let p: sdk.PullAudioInputStream; + let bytesSent: number = 0; + + // To make sure we don't send a ton of extra data. + // 5s * 16K * 2 * 1.25; + // For reference, before the throttling was implemented, we sent 6-10x the required data. + const expectedBytesSent: number = 5 * 16000 * 2 * 1.25; + + p = sdk.AudioInputStream.createPullStream( + { + close: () => { return; }, + read: (buffer: ArrayBuffer): number => { + bytesSent += buffer.byteLength; + return buffer.byteLength; + }, + }); + + const config: sdk.AudioConfig = sdk.AudioConfig.fromStreamInput(p); + + testInitialSilienceTimeout(config, done, () => expect(bytesSent).toBeLessThan(expectedBytesSent)); + }); + + test("InitialSilenceTimeout (push)", (done: jest.DoneCallback) => { + // tslint:disable-next-line:no-console + console.info("Name: InitialSilenceTimeout (push)"); + + const p: sdk.PushAudioInputStream = sdk.AudioInputStream.createPushStream(); + const bigFileBuffer: Uint8Array = new Uint8Array(1024 * 1024); + const config: sdk.AudioConfig = sdk.AudioConfig.fromStreamInput(p); + + p.write(bigFileBuffer.buffer); + p.close(); + + testInitialSilienceTimeout(config, done); + }); + + test("InitialSilenceTimeout (File)", (done: jest.DoneCallback) => { + // tslint:disable-next-line:no-console + console.info("Name: InitialSilenceTimeout (File)"); + + const bigFileBuffer: Uint8Array = new Uint8Array(1024 * 1024); + const bigFile: File = ByteBufferAudioFile.Load(bigFileBuffer.buffer); + + const config: sdk.AudioConfig = sdk.AudioConfig.fromWavFileInput(bigFile); + + testInitialSilienceTimeout(config, done); + }); + + const testInitialSilienceTimeout = (config: sdk.AudioConfig, done: jest.DoneCallback, addedChecks?: () => void): void => { + const s: sdk.SpeechConfig = BuildSpeechConfig(); + objsToClose.push(s); + + // To validate the data isn't sent too fast. + const startTime: number = Date.now(); + + const r: sdk.IntentRecognizer = new sdk.IntentRecognizer(s, config); + objsToClose.push(r); + + expect(r).not.toBeUndefined(); + expect(r instanceof sdk.Recognizer); + + let numReports: number = 0; + + r.canceled = (o: sdk.Recognizer, e: sdk.IntentRecognitionCanceledEventArgs) => { + done.fail(e.errorDetails); + }; + + r.recognized = (o: sdk.Recognizer, e: sdk.IntentRecognitionEventArgs) => { + try { + const res: sdk.SpeechRecognitionResult = e.result; + expect(res).not.toBeUndefined(); + expect(sdk.ResultReason.NoMatch).toEqual(res.reason); + expect(res.text).toBeUndefined(); + + const nmd: sdk.NoMatchDetails = sdk.NoMatchDetails.fromResult(res); + expect(nmd.reason).toEqual(sdk.NoMatchReason.InitialSilenceTimeout); + } catch (error) { + done.fail(error); + } finally { + numReports++; + } + + }; + + r.recognizeOnceAsync( + (p2: sdk.IntentRecognitionResult) => { + const res: sdk.IntentRecognitionResult = p2; + numReports++; + + expect(res).not.toBeUndefined(); + expect(sdk.ResultReason.NoMatch).toEqual(res.reason); + expect(res.errorDetails).toBeUndefined(); + expect(res.text).toBeUndefined(); + + const nmd: sdk.NoMatchDetails = sdk.NoMatchDetails.fromResult(res); + expect(nmd.reason).toEqual(sdk.NoMatchReason.InitialSilenceTimeout); + expect(Date.now()).toBeGreaterThan(startTime + ((res.offset / 1e+4) / 2)); + + }, + (error: string) => { + fail(error); + }); + + WaitForCondition(() => (numReports === 2), () => { + setTimeout(done, 1); + if (!!addedChecks) { + addedChecks(); + } + }); + }; + + test("Continous Recog With Intent", (done: jest.DoneCallback) => { + // tslint:disable-next-line:no-console + console.info("Name: Continous Recog With Intent"); + + const r: sdk.IntentRecognizer = BuildRecognizerFromWaveFile(); + objsToClose.push(r); + + const lm: sdk.LanguageUnderstandingModel = sdk.LanguageUnderstandingModel.fromSubscription(Settings.LuisAppKey, Settings.LuisAppId, Settings.LuisRegion); + r.addIntentWithLanguageModel(Settings.LuisValidIntentId, lm); + + r.canceled = (o: sdk.Recognizer, e: sdk.IntentRecognitionCanceledEventArgs) => { + try { + expect(e.errorDetails).toBeUndefined(); + expect(sdk.CancellationReason[e.reason]).toEqual(sdk.CancellationReason[sdk.CancellationReason.EndOfStream]); + } catch (error) { + done.fail(error); + } + }; + + r.recognizing = (o: sdk.Recognizer, e: sdk.IntentRecognitionEventArgs): void => { + try { + expect(e.result.reason).toEqual(sdk.ResultReason.RecognizingIntent); + } catch (error) { + done.fail(error); + } + }; + + r.recognized = (o: sdk.Recognizer, e: sdk.IntentRecognitionEventArgs) => { + try { + const res: sdk.IntentRecognitionResult = e.result; + expect(res).not.toBeUndefined(); + expect(res.reason).toEqual(sdk.ResultReason.RecognizedIntent); + expect(res.intentId).toEqual(Settings.LuisValidIntentId); + ValidateResultMatchesWaveFile(res); + + r.stopContinuousRecognitionAsync(() => { + done(); + }, (error: string) => { + done.fail(error); + }); + } catch (error) { + done.fail(error); + } + }; + + r.startContinuousRecognitionAsync( + /* tslint:disable:no-empty */ + () => { }, + (error: string) => { + done.fail(error); + }); + }); + + test("RoundTripWithGoodModelWrongIntent", (done: jest.DoneCallback) => { + // tslint:disable-next-line:no-console + console.info("Name: RoundTripWithGoodModelWrongIntent"); + + const r: sdk.IntentRecognizer = BuildRecognizerFromWaveFile(); + objsToClose.push(r); + + const lm: sdk.LanguageUnderstandingModel = sdk.LanguageUnderstandingModel.fromAppId(Settings.LuisAppId); + + r.addIntentWithLanguageModel(Settings.LuisValidIntentId + "-Bad", lm); + + r.recognizeOnceAsync( + (p2: sdk.IntentRecognitionResult) => { + const res: sdk.IntentRecognitionResult = p2; + expect(res).not.toBeUndefined(); + expect(res.errorDetails).toBeUndefined(); + expect(res.reason).toEqual(sdk.ResultReason.RecognizedSpeech); + expect(res.intentId).toBeUndefined(); + ValidateResultMatchesWaveFile(res); + done(); + }, + (error: string) => { + done.fail(error); + }); + }); + + test("MultiPhrase Intent", (done: jest.DoneCallback) => { + // tslint:disable-next-line:no-console + console.info("Name: MultiPhrase Intent"); + + const s: sdk.SpeechConfig = BuildSpeechConfig(); + objsToClose.push(s); + + expect(s).not.toBeUndefined(); + + s.speechRecognitionLanguage = Settings.LuisWaveFileLanguage; + + const f: ArrayBuffer = WaveFileAudioInput.LoadArrayFromFile(Settings.LuisWaveFile); + const p: sdk.PushAudioInputStream = sdk.AudioInputStream.createPushStream(); + const config: sdk.AudioConfig = sdk.AudioConfig.fromStreamInput(p); + const numPhrases: number = 3; + + // Adding some extra silence to ensure SR goes smmothly since the goal here isn't to test + // the SR engine, but rather the multi-phrase reconnect code. + const silenceBuffer: Uint8Array = new Uint8Array(16 * 1024); // ~500ms + + for (let i: number = 0; i <= 2; i++) { + p.write(f); + p.write(silenceBuffer.buffer); + } + + p.close(); + + const r: sdk.IntentRecognizer = new sdk.IntentRecognizer(s, config); + objsToClose.push(r); + + expect(r).not.toBeUndefined(); + expect(r instanceof sdk.Recognizer); + + const lm: sdk.LanguageUnderstandingModel = sdk.LanguageUnderstandingModel.fromSubscription(Settings.LuisAppKey, Settings.LuisAppId, Settings.LuisRegion); + r.addIntentWithLanguageModel(Settings.LuisValidIntentId, lm); + + let numIntents: number = 0; + let inTurn: boolean = false; + let canceled: boolean = false; + + r.canceled = (o: sdk.Recognizer, e: sdk.IntentRecognitionCanceledEventArgs) => { + try { + switch (e.reason) { + case sdk.CancellationReason.Error: + done.fail(e.errorDetails); + break; + case sdk.CancellationReason.EndOfStream: + canceled = true; + break; + } + } catch (error) { + done.fail(error); + } + }; + + r.sessionStarted = ((s: sdk.Recognizer, e: sdk.SessionEventArgs): void => { + inTurn = true; + }); + + r.sessionStopped = ((s: sdk.Recognizer, e: sdk.SessionEventArgs): void => { + inTurn = false; + }); + + r.recognized = (o: sdk.Recognizer, e: sdk.IntentRecognitionEventArgs) => { + try { + const res: sdk.IntentRecognitionResult = e.result; + expect(res).not.toBeUndefined(); + if (numIntents !== numPhrases) { + expect(sdk.ResultReason[res.reason]).toEqual(sdk.ResultReason[sdk.ResultReason.RecognizedIntent]); + expect(res.intentId).toEqual(Settings.LuisValidIntentId); + expect(res.text).toEqual(Settings.LuisWavFileText); + numIntents++; + } else { + expect(sdk.ResultReason[res.reason]).toEqual(sdk.ResultReason[sdk.ResultReason.NoMatch]); + } + } catch (error) { + done.fail(error); + } + }; + + r.startContinuousRecognitionAsync(() => { + WaitForCondition(() => { + return (canceled && !inTurn); + }, () => { + try { + expect(numIntents).toEqual(numPhrases); + r.stopContinuousRecognitionAsync(() => { + done(); + }, (error: string) => { + done.fail(error); + }); + } catch (error) { + done.fail(error); + } + }); + }, + (error: string) => { + done.fail(error); + }); + }, 15000); + + test("IntentAlias", (done: jest.DoneCallback) => { + // tslint:disable-next-line:no-console + console.info("Name: IntentAlias"); + + const r: sdk.IntentRecognizer = BuildRecognizerFromWaveFile(); + objsToClose.push(r); + + const lm: sdk.LanguageUnderstandingModel = sdk.LanguageUnderstandingModel.fromAppId(Settings.LuisAppId); + const intentName: string = "SomeName"; + + r.addIntentWithLanguageModel(Settings.LuisValidIntentId, lm, intentName); + + r.recognizeOnceAsync( + (p2: sdk.IntentRecognitionResult) => { + const res: sdk.IntentRecognitionResult = p2; + expect(res).not.toBeUndefined(); + expect(res.errorDetails).toBeUndefined(); + expect(res.reason).toEqual(sdk.ResultReason.RecognizedIntent); + expect(res.intentId).toEqual(intentName); + ValidateResultMatchesWaveFile(res); + done(); + }, + (error: string) => { + done.fail(error); + }); + }); + + test("Add All Intents", (done: jest.DoneCallback) => { + // tslint:disable-next-line:no-console + console.info("Name: Add All Intents"); + + const r: sdk.IntentRecognizer = BuildRecognizerFromWaveFile(); + objsToClose.push(r); + + const lm: sdk.LanguageUnderstandingModel = sdk.LanguageUnderstandingModel.fromAppId(Settings.LuisAppId); + + r.addAllIntents(lm); + + r.recognizeOnceAsync( + (p2: sdk.IntentRecognitionResult) => { + const res: sdk.IntentRecognitionResult = p2; + expect(res).not.toBeUndefined(); + expect(res.errorDetails).toBeUndefined(); + expect(sdk.ResultReason[res.reason]).toEqual(sdk.ResultReason[sdk.ResultReason.RecognizedIntent]); + expect(res.intentId).toEqual(Settings.LuisValidIntentId); + ValidateResultMatchesWaveFile(res); + expect(res.properties.getProperty(sdk.PropertyId.LanguageUnderstandingServiceResponse_JsonResult)).not.toBeUndefined(); + done(); + }, + (error: string) => { + done.fail(error); + }); + }); + + test("Add All Intents with alias", (done: jest.DoneCallback) => { + // tslint:disable-next-line:no-console + console.info("Name: Add All Intents with alias"); + + const r: sdk.IntentRecognizer = BuildRecognizerFromWaveFile(); + objsToClose.push(r); + + const lm: sdk.LanguageUnderstandingModel = sdk.LanguageUnderstandingModel.fromAppId(Settings.LuisAppId); + + r.addAllIntents(lm, "alias"); + + r.recognizeOnceAsync( + (p2: sdk.IntentRecognitionResult) => { + const res: sdk.IntentRecognitionResult = p2; + expect(res).not.toBeUndefined(); + expect(res.errorDetails).toBeUndefined(); + expect(sdk.ResultReason[res.reason]).toEqual(sdk.ResultReason[sdk.ResultReason.RecognizedIntent]); + expect(res.intentId).toEqual("alias"); + ValidateResultMatchesWaveFile(res); + done(); + }, + (error: string) => { + done.fail(error); + }); + }); + + test("Audio Config is optional", () => { + // tslint:disable-next-line:no-console + console.info("Name: Audio Config is optional"); + + const s: sdk.SpeechConfig = sdk.SpeechConfig.fromSubscription(Settings.SpeechSubscriptionKey, Settings.SpeechRegion); + objsToClose.push(s); + expect(s).not.toBeUndefined(); + s.speechRecognitionLanguage = "en-US"; + + const r: sdk.IntentRecognizer = new sdk.IntentRecognizer(s); + objsToClose.push(r); + expect(r instanceof sdk.Recognizer).toEqual(true); + }); + + test("Default mic is used when audio config is not specified.", () => { + // tslint:disable-next-line:no-console + console.info("Name: Default mic is used when audio config is not specified."); + + const s: sdk.SpeechConfig = sdk.SpeechConfig.fromSubscription(Settings.SpeechSubscriptionKey, Settings.SpeechRegion); + expect(s).not.toBeUndefined(); + s.speechRecognitionLanguage = "en-US"; + + const r: sdk.IntentRecognizer = new sdk.IntentRecognizer(s); + expect(r instanceof sdk.Recognizer).toEqual(true); + // Node.js doesn't have a microphone natively. So we'll take the specific message that indicates that microphone init failed as evidence it was attempted. + r.recognizeOnceAsync(() => fail("RecognizeOnceAsync returned success when it should have failed"), + (error: string): void => { + expect(error).toEqual("Error: Browser does not support Web Audio API (AudioContext is not available)."); + }); + + r.startContinuousRecognitionAsync(() => fail("startContinuousRecognitionAsync returned success when it should have failed"), + (error: string): void => { + expect(error).toEqual("Error: Browser does not support Web Audio API (AudioContext is not available)."); + }); + }); + + test("Connection Errors Propogate Async", (done: jest.DoneCallback) => { + // tslint:disable-next-line:no-console + console.info("Name: Connection Errors Propogate Async"); + + const s: sdk.SpeechConfig = sdk.SpeechConfig.fromSubscription("badKey", Settings.SpeechRegion); + objsToClose.push(s); + + const r: sdk.IntentRecognizer = BuildRecognizerFromWaveFile(s); + objsToClose.push(r); + + r.canceled = (o: sdk.Recognizer, e: sdk.IntentRecognitionCanceledEventArgs) => { + try { + expect(sdk.CancellationReason[e.reason]).toEqual(sdk.CancellationReason[sdk.CancellationReason.Error]); + expect(sdk.CancellationErrorCode[e.errorCode]).toEqual(sdk.CancellationErrorCode[sdk.CancellationErrorCode.ConnectionFailure]); + done(); + } catch (error) { + done.fail(error); + } + }; + + r.startContinuousRecognitionAsync(); + + }); + + test("Connection Errors Propogate Sync", (done: jest.DoneCallback) => { + // tslint:disable-next-line:no-console + console.info("Name: Connection Errors Propogate Sync"); + + const s: sdk.SpeechConfig = sdk.SpeechConfig.fromSubscription("badKey", Settings.SpeechRegion); + objsToClose.push(s); + + const r: sdk.IntentRecognizer = BuildRecognizerFromWaveFile(s); + objsToClose.push(r); + + let doneCount: number = 0; + r.canceled = (o: sdk.Recognizer, e: sdk.IntentRecognitionCanceledEventArgs) => { + try { + expect(sdk.CancellationReason[e.reason]).toEqual(sdk.CancellationReason[sdk.CancellationReason.Error]); + expect(sdk.CancellationErrorCode[e.errorCode]).toEqual(sdk.CancellationErrorCode[sdk.CancellationErrorCode.ConnectionFailure]); + expect(e.errorDetails).toContain("1006"); + doneCount++; + } catch (error) { + done.fail(error); + } + }; + + r.recognizeOnceAsync((result: sdk.IntentRecognitionResult) => { + try { + const e: sdk.CancellationDetails = sdk.CancellationDetails.fromResult(result); + expect(sdk.CancellationReason[e.reason]).toEqual(sdk.CancellationReason[sdk.CancellationReason.Error]); + expect(sdk.CancellationErrorCode[e.ErrorCode]).toEqual(sdk.CancellationErrorCode[sdk.CancellationErrorCode.ConnectionFailure]); + expect(e.errorDetails).toContain("1006"); + doneCount++; + } catch (error) { + done.fail(error); + } + + WaitForCondition(() => (doneCount === 2), done); + + }); + }); + + // Bing Speech does not behave the same as Unified Speech for a bad language. It closes the connection far more gracefully. + test.skip("RecognizeOnce Bad Language", (done: jest.DoneCallback) => { + // tslint:disable-next-line:no-console + console.info("Name: RecognizeOnce Bad Language"); + + const s: sdk.SpeechConfig = BuildSpeechConfig(); + objsToClose.push(s); + s.speechRecognitionLanguage = "BadLanguage"; + + const r: sdk.IntentRecognizer = BuildRecognizerFromWaveFile(s); + objsToClose.push(r); + let doneCount: number = 0; + + r.canceled = (o: sdk.Recognizer, e: sdk.IntentRecognitionCanceledEventArgs) => { + try { + expect(sdk.CancellationReason[e.reason]).toEqual(sdk.CancellationReason[sdk.CancellationReason.Error]); + expect(sdk.CancellationErrorCode[e.errorCode]).toEqual(sdk.CancellationErrorCode[sdk.CancellationErrorCode.ConnectionFailure]); + expect(e.errorDetails).toContain("1007"); + doneCount++; + } catch (error) { + done.fail(error); + } + }; + + r.recognizeOnceAsync((result: sdk.IntentRecognitionResult) => { + try { + const e: sdk.CancellationDetails = sdk.CancellationDetails.fromResult(result); + expect(sdk.CancellationReason[e.reason]).toEqual(sdk.CancellationReason[sdk.CancellationReason.Error]); + expect(sdk.CancellationErrorCode[e.ErrorCode]).toEqual(sdk.CancellationErrorCode[sdk.CancellationErrorCode.ConnectionFailure]); + expect(e.errorDetails).toContain("1007"); + doneCount++; + } catch (error) { + done.fail(error); + } + + WaitForCondition(() => (doneCount === 2), done); + }); + }); + + test("Silence After Speech", (done: jest.DoneCallback) => { + // tslint:disable-next-line:no-console + console.info("Name: Silence After Speech"); + + // Pump valid speech and then silence until at least one speech end cycle hits. + const p: sdk.PushAudioInputStream = sdk.AudioInputStream.createPushStream(); + const bigFileBuffer: Uint8Array = new Uint8Array(32 * 1024 * 30); // ~30 seconds. + const config: sdk.AudioConfig = sdk.AudioConfig.fromStreamInput(p); + const s: sdk.SpeechConfig = BuildSpeechConfig(); + + objsToClose.push(s); + + p.write(WaveFileAudioInput.LoadArrayFromFile(Settings.WaveFile)); + p.write(bigFileBuffer.buffer); + p.close(); + + const r: sdk.IntentRecognizer = new sdk.IntentRecognizer(s, config); + objsToClose.push(r); + + let speechRecognized: boolean = false; + let noMatchCount: number = 0; + let speechEnded: number = 0; + let inTurn = false; + let canceled: boolean = false; + + r.recognized = (o: sdk.Recognizer, e: sdk.IntentRecognitionEventArgs) => { + try { + if (e.result.reason === sdk.ResultReason.RecognizedSpeech) { + expect(speechRecognized).toEqual(false); + speechRecognized = true; + expect(sdk.ResultReason[e.result.reason]).toEqual(sdk.ResultReason[sdk.ResultReason.RecognizedSpeech]); + expect(e.result.text).toEqual("What's the weather like?"); + } else if (e.result.reason === sdk.ResultReason.NoMatch) { + expect(speechRecognized).toEqual(true); + noMatchCount++; + } + } catch (error) { + done.fail(error); + } + }; + + r.canceled = (o: sdk.Recognizer, e: sdk.IntentRecognitionCanceledEventArgs): void => { + try { + expect(e.errorDetails).toBeUndefined(); + expect(e.reason).toEqual(sdk.CancellationReason.EndOfStream); + canceled = true; + } catch (error) { + done.fail(error); + } + }; + + let sessionDone = false; + r.sessionStopped = (o: sdk.Recognizer, e: sdk.SessionEventArgs): void => { + sessionDone = true; + }; + + r.speechEndDetected = (o: sdk.Recognizer, e: sdk.RecognitionEventArgs): void => { + speechEnded++; + }; + + r.sessionStarted = ((s: sdk.Recognizer, e: sdk.SessionEventArgs): void => { + inTurn = true; + }); + + r.sessionStopped = ((s: sdk.Recognizer, e: sdk.SessionEventArgs): void => { + inTurn = false; + }); + + r.startContinuousRecognitionAsync(() => { }, + (err: string) => { + done.fail(err); + }); + + WaitForCondition(() => (canceled && !inTurn), () => { + r.stopContinuousRecognitionAsync(() => { + try { + expect(speechEnded).toEqual(noMatchCount + 1); // +1 for the end of the valid speech. + expect(noMatchCount).toEqual(7); // 5 seconds for intent based reco. + done(); + } catch (error) { + done.fail(error); + } + + }, (error: string) => { + done.fail(error); + }); + }); + + }, 35000); + + test("Silence Then Speech", (done: jest.DoneCallback) => { + // tslint:disable-next-line:no-console + console.info("Name: Silence Then Speech"); + + // Pump valid speech and then silence until at least one speech end cycle hits. + const p: sdk.PushAudioInputStream = sdk.AudioInputStream.createPushStream(); + const bigFileBuffer: Uint8Array = new Uint8Array(32 * 1024 * 30); // ~30 seconds. + const config: sdk.AudioConfig = sdk.AudioConfig.fromStreamInput(p); + const s: sdk.SpeechConfig = BuildSpeechConfig(); + objsToClose.push(s); + + p.write(bigFileBuffer.buffer); + p.write(WaveFileAudioInput.LoadArrayFromFile(Settings.WaveFile)); + p.close(); + + const r: sdk.IntentRecognizer = new sdk.IntentRecognizer(s, config); + objsToClose.push(r); + + let speechRecognized: boolean = false; + let noMatchCount: number = 0; + let speechEnded: number = 0; + let canceled: boolean = false; + let inTurn: boolean = false; + + r.recognized = (o: sdk.Recognizer, e: sdk.IntentRecognitionEventArgs) => { + try { + if (e.result.reason === sdk.ResultReason.RecognizedSpeech) { + expect(speechRecognized).toEqual(false); + expect(noMatchCount).toBeGreaterThanOrEqual(1); + speechRecognized = true; + expect(sdk.ResultReason[e.result.reason]).toEqual(sdk.ResultReason[sdk.ResultReason.RecognizedSpeech]); + expect(e.result.text).toEqual("What's the weather like?"); + } else if (e.result.reason === sdk.ResultReason.NoMatch) { + expect(speechRecognized).toEqual(false); + noMatchCount++; + } + } catch (error) { + done.fail(error); + } + }; + + r.canceled = (o: sdk.Recognizer, e: sdk.IntentRecognitionCanceledEventArgs): void => { + try { + expect(e.errorDetails).toBeUndefined(); + expect(e.reason).toEqual(sdk.CancellationReason.EndOfStream); + canceled = true; + } catch (error) { + done.fail(error); + } + }; + + r.sessionStarted = ((s: sdk.Recognizer, e: sdk.SessionEventArgs): void => { + inTurn = true; + }); + + r.sessionStopped = ((s: sdk.Recognizer, e: sdk.SessionEventArgs): void => { + inTurn = false; + }); + + r.speechEndDetected = (o: sdk.Recognizer, e: sdk.RecognitionEventArgs): void => { + speechEnded++; + }; + + r.startContinuousRecognitionAsync(() => { }, + (err: string) => { + done.fail(err); + }); + + WaitForCondition(() => (canceled && !inTurn), () => { + try { + expect(speechEnded).toEqual(noMatchCount + 1); + expect(noMatchCount).toEqual(6); // 5 seconds for intent based reco. + } catch (error) { + done.fail(error); + } + + r.stopContinuousRecognitionAsync(() => { + done(); + }, (error: string) => { + done.fail(error); + }); + }); + + }, 35000); +}); + +test("Bad DataType for PushStreams results in error", (done: jest.DoneCallback) => { + // tslint:disable-next-line:no-console + console.info("Name: Bad DataType for PushStreams results in error"); + + const s: sdk.SpeechConfig = BuildSpeechConfig(); + objsToClose.push(s); + + const p: sdk.PushAudioInputStream = sdk.AudioInputStream.createPushStream(); + const config: sdk.AudioConfig = sdk.AudioConfig.fromStreamInput(p); + + // Wrong data type for ReadStreams + fs.createReadStream(Settings.WaveFile).on("data", (buffer: ArrayBuffer) => { + p.write(buffer); + }).on("end", () => { + p.close(); + }); + + const r: sdk.IntentRecognizer = new sdk.IntentRecognizer(s, config); + objsToClose.push(r); + + expect(r).not.toBeUndefined(); + expect(r instanceof sdk.Recognizer); + + r.canceled = (r: sdk.Recognizer, e: sdk.IntentRecognitionCanceledEventArgs) => { + try { + expect(e.errorDetails).not.toBeUndefined(); + expect(sdk.CancellationReason[e.reason]).toEqual(sdk.CancellationReason[sdk.CancellationReason.Error]); + expect(sdk.CancellationErrorCode[e.errorCode]).toEqual(sdk.CancellationErrorCode[sdk.CancellationErrorCode.RuntimeError]); + } catch (error) { + done.fail(error); + } + }; + + r.recognizeOnceAsync( + (p2: sdk.SpeechRecognitionResult) => { + const res: sdk.SpeechRecognitionResult = p2; + try { + expect(res).not.toBeUndefined(); + expect(res.errorDetails).not.toBeUndefined(); + expect(sdk.ResultReason[res.reason]).toEqual(sdk.ResultReason[sdk.ResultReason.Canceled]); + done(); + } catch (error) { + done.fail(error); + } + }, + (error: string) => { + done.fail(error); + }); +}); diff --git a/tests/LanguageModelTests.ts b/tests/LanguageModelTests.ts new file mode 100644 index 00000000..e4640a75 --- /dev/null +++ b/tests/LanguageModelTests.ts @@ -0,0 +1,46 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +import * as sdk from "../microsoft.cognitiveservices.speech.sdk"; +import { LanguageUnderstandingModelImpl } from "../src/sdk/LanguageUnderstandingModel"; +import { Settings } from "./Settings"; + +beforeAll(() => { + // Override inputs, if necessary + Settings.LoadSettings(); +}); + +test("HappyModel", () => { + const lang: sdk.LanguageUnderstandingModel = sdk.LanguageUnderstandingModel.fromAppId(Settings.LuisAppId); + expect(lang instanceof sdk.LanguageUnderstandingModel).toEqual(true); +}); + +test("NullModel", () => { + expect(() => sdk.LanguageUnderstandingModel.fromAppId(null)).toThrow(); +}); + +test("Happy URL", () => { + const fakeRegion: string = "fakeregion"; + + // Just get some random numbers. There should be no guid / key format validation in the fromEndpoint. + const appId: string = Math.random().toString().substr(3); + const subKey: string = Math.random().toString().substr(3); + + const url: string = "https://" + fakeRegion + ".api.cognitive.microsoft.com/luis/v2.0/apps/" + appId + "?subscription-key=" + subKey + "&timezoneOffset=-360"; + + const lang: sdk.LanguageUnderstandingModel = sdk.LanguageUnderstandingModel.fromEndpoint(new URL(url)); + expect(lang instanceof sdk.LanguageUnderstandingModel).toEqual(true); + + // Now, be all sneaky and reach into the internal class to make sure the parsing worked correctly. + expect(lang instanceof LanguageUnderstandingModelImpl).toEqual(true); + + const langImpl: LanguageUnderstandingModelImpl = lang as LanguageUnderstandingModelImpl; + + expect(langImpl.appId).toEqual(appId); + expect(langImpl.subscriptionKey).toEqual(subKey); + expect(langImpl.region).toEqual(fakeRegion); +}); + +test("null URL", () => { + expect(() => sdk.LanguageUnderstandingModel.fromEndpoint(null)).toThrow(); +}); diff --git a/tests/RecognizerTests.ts b/tests/RecognizerTests.ts new file mode 100644 index 00000000..5e389fff --- /dev/null +++ b/tests/RecognizerTests.ts @@ -0,0 +1,59 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +import * as sdk from "../microsoft.cognitiveservices.speech.sdk"; +import { Settings } from "./Settings"; +import { WaveFileAudioInput } from "./WaveFileAudioInputStream"; + +beforeEach(() => { + // Override inputs, if necessary + Settings.LoadSettings(); +}); + +test("testRecognizer1", () => { + const s = sdk.SpeechConfig.fromSubscription(Settings.SpeechSubscriptionKey, Settings.SpeechRegion); + expect(s).not.toBeUndefined(); + + const f: File = WaveFileAudioInput.LoadFile(Settings.WaveFile); + const config: sdk.AudioConfig = sdk.AudioConfig.fromWavFileInput(f); + + const r = new sdk.IntentRecognizer(s, config); + expect(r).not.toBeUndefined(); + expect(r instanceof sdk.Recognizer); + + r.close(); + s.close(); +}); + +test("testRecognizer2", () => { + const s = sdk.SpeechConfig.fromSubscription(Settings.SpeechSubscriptionKey, Settings.SpeechRegion); + expect(s).not.toBeUndefined(); + + const f: File = WaveFileAudioInput.LoadFile(Settings.WaveFile); + const config: sdk.AudioConfig = sdk.AudioConfig.fromWavFileInput(f); + + const r = new sdk.SpeechRecognizer(s, config); + expect(r).not.toBeUndefined(); + expect(r instanceof sdk.Recognizer); + + r.close(); + s.close(); +}); +/* +// TODO does not work with microphone +test.skip("testRecognizer3", () => { + const s = sdk.SpeechFactory.fromSubscription(Settings.SpeechSubscriptionKey, Settings.SpeechRegion); + expect(s).not.toBeUndefined(); + + let targets: List = new List(); + targets.Add("en-US"); + + const r = s.createTranslationRecognizer("en-US", targets.ToArray()); + expect(r).not.toBeUndefined(); + + expect(r instanceof sdk.Recognizer); + + r.close(); + s.close(); +}); +*/ diff --git a/tests/Settings.ts b/tests/Settings.ts new file mode 100644 index 00000000..43d36a6a --- /dev/null +++ b/tests/Settings.ts @@ -0,0 +1,73 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +export class Settings { + + // subscription + public static SpeechSubscriptionKey: string = "<>"; + public static SpeechRegion: string = "<>"; + public static SpeechEndpoint: string; + + public static SpeechTestEndpointId: string = "<>"; + + // Endpoint and key for timeout testing. + // Endpoint should reduce standard speech timeout to value specified in SpeechServiceTimeoutSeconds + // If undefined, production timeout of 10 seconds will be used, but at the cost of greatly incrased test + // duration. + public static SpeechTimeoutEndpoint: string; + public static SpeechTimeoutKey: string; + public static SpeechServiceTimeoutSeconds: number = 60 * 10; // 10 minutes + + public static LuisSubscriptionKey: string = "<>"; + public static LuisRegion: string = "<>"; + public static LuisAppEndPointHref: string = "<>"; + + public static InputDir: string = "tests/input/audio/"; + + /* + * The intent behing this setting is that at test execution time the WaveFile below will contain speech + * that the LUIS app above will recognize as an intent with this ID. + * + * Since the default wave file asks "What's the weather like?", an intent with the Id of "Weather" seems reasonable. + */ + public static LuisValidIntentId: string = "HomeAutomation.TurnOn"; + public static LuisAppKey: string; + public static LuisWaveFileLanguage: string = "en-US"; + public static LuisWaveFile: string = Settings.InputDir + "TurnOnTheLamp.wav"; + public static LuisWavFileText: string = "Turn on the lamp."; + public static LuisWaveFileDuration: number = 11000000; + public static LuisWaveFileOffset: number = 4000000; + + // Currently the other bindings read the app key out of the shell environment setup by + // $\ci\set-test-variables.sh and not a secret passed by VSTS. So the question isn't + // should we set it in code, but where... + public static LuisAppId: string = "b687b851-56c5-4d31-816f-35a741a3f0be"; + + public static WaveFile: string = Settings.InputDir + "whatstheweatherlike.wav"; + public static WaveFileLanguage: string = "en-US"; + public static WaveFileDuration: number = 12900000; + public static WaveFileOffset: number = 1000000; + public static WaveFileText: string = "What's the weather like?"; + + private static IsSettingsInitialized: boolean = false; + public static SettingsClassLock: Settings; + + public static initialize(): void { + Settings.SettingsClassLock = new Settings(); + + Settings.LoadSettings(); + } + + public static LoadSettings = () => { + if (Settings.IsSettingsInitialized) { + return; + } + + if (undefined === Settings.LuisAppKey) { + Settings.LuisAppKey = Settings.LuisSubscriptionKey; + } + + Settings.IsSettingsInitialized = true; + } +} +Settings.initialize(); diff --git a/tests/SpeechConfigTests.ts b/tests/SpeechConfigTests.ts new file mode 100644 index 00000000..a2c4cc8c --- /dev/null +++ b/tests/SpeechConfigTests.ts @@ -0,0 +1,366 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +import { isString } from "util"; +import * as sdk from "../microsoft.cognitiveservices.speech.sdk"; +import { createNoDashGuid } from "../src/common/Guid"; +import { Settings } from "./Settings"; +import { WaveFileAudioInput } from "./WaveFileAudioInputStream"; + +beforeAll(() => { + // Override inputs, if necessary + Settings.LoadSettings(); +}); + +// Test cases are run linerally, the only other mechanism to demark them in the output is to put a console line in each case and +// report the name. +// tslint:disable-next-line:no-console +beforeEach(() => console.info("---------------------------------------Starting test case-----------------------------------")); + +test("Null Param Check, both.", () => { + expect(() => sdk.SpeechConfig.fromSubscription(null, null)).toThrowError(); +}); + +test("Null Param Check, Region", () => { + expect(() => sdk.SpeechConfig.fromSubscription(Settings.SpeechSubscriptionKey, null)).toThrowError(); +}); + +test("Null Param Check, Key", () => { + expect(() => sdk.SpeechConfig.fromSubscription(null, Settings.SpeechRegion)).toThrowError(); +}); + +test("Valid Params", () => { + const s: sdk.SpeechConfig = sdk.SpeechConfig.fromSubscription(Settings.SpeechSubscriptionKey, Settings.SpeechRegion); + expect(s).not.toBeUndefined(); + s.close(); +}); + +test("From Endpoint, but null", () => { + expect(() => sdk.SpeechConfig.fromEndpoint(null, null)).toThrowError(); +}); + +test("From Endpoint, endpoihnt numm null", () => { + expect(() => sdk.SpeechConfig.fromEndpoint(null, Settings.SpeechSubscriptionKey)).toThrowError(); +}); + +test("From Endpoint, key null", () => { + expect(() => sdk.SpeechConfig.fromEndpoint(new URL("http://www.example.com"), null)).toThrowError(); +}); + +test.skip("From endpoint, invalid key format.", () => { + expect(() => sdk.SpeechConfig.fromEndpoint(new URL("http://www.example.com"), "illegal-subscription")).toThrowError(); +}); + +// TODO use an endpoint that we control so the subscription key is not leaked! +test("From endpoing, valid Params", () => { + const s: sdk.SpeechConfig = sdk.SpeechConfig.fromEndpoint(new URL("http://www.example.com"), "Settings.SpeechSubscriptionKey"); + expect(s).not.toBeUndefined(); + s.close(); +}); + +test("TypedParametersAccessableViaPropBag", () => { + const s: sdk.SpeechConfig = sdk.SpeechConfig.fromSubscription(Settings.SpeechSubscriptionKey, Settings.SpeechRegion); + TestParam(() => s.authorizationToken, (val: string) => (s.authorizationToken = val), sdk.PropertyId.SpeechServiceAuthorization_Token, s); + TestParam(() => s.endpointId, (val: string) => (s.endpointId = val), sdk.PropertyId.SpeechServiceConnection_EndpointId, s); + TestParam(() => s.speechRecognitionLanguage, (val: string) => (s.speechRecognitionLanguage = val), sdk.PropertyId.SpeechServiceConnection_RecoLanguage, s); +}); + +const TestParam = (getAccess: () => string, setAccess: (val: string) => void, propEnum: sdk.PropertyId, config: sdk.SpeechConfig): void => { + const testString: string = createNoDashGuid(); + + setAccess(testString); + expect(config.getProperty(sdk.PropertyId[propEnum])).toEqual(testString); + expect(getAccess()).toEqual(testString); + + const testString2: string = createNoDashGuid(); + config.setProperty(sdk.PropertyId[propEnum], testString2); + expect(config.getProperty(sdk.PropertyId[propEnum])).toEqual(testString2); + expect(getAccess()).toEqual(testString2); +}; + +test("Unset param return default", () => { + const s: sdk.SpeechConfig = sdk.SpeechConfig.fromSubscription(Settings.SpeechSubscriptionKey, Settings.SpeechRegion); + + const name: string = createNoDashGuid(); + const value: string = createNoDashGuid(); + + expect(s.getProperty(name, value)).toEqual(value); + expect(s.getProperty(name)).toBeUndefined(); + + s.close(); +}); + +test("Create Recognizer", () => { + const s: sdk.SpeechConfig = sdk.SpeechConfig.fromSubscription(Settings.SpeechSubscriptionKey, Settings.SpeechRegion); + + const r: sdk.SpeechRecognizer = new sdk.SpeechRecognizer(s); + + expect(r).not.toBeUndefined(); + expect(r instanceof sdk.Recognizer); + + r.close(); + s.close(); +}); + +test("Proeprties are passed to recognizer", () => { + const s: sdk.SpeechConfig = sdk.SpeechConfig.fromSubscription(Settings.SpeechSubscriptionKey, Settings.SpeechRegion); + s.speechRecognitionLanguage = createNoDashGuid(); + s.authorizationToken = createNoDashGuid(); + s.endpointId = createNoDashGuid(); + + const r: sdk.SpeechRecognizer = new sdk.SpeechRecognizer(s); + expect(r).not.toBeUndefined(); + expect(r instanceof sdk.Recognizer); + expect(r.authorizationToken).toEqual(s.authorizationToken); + expect(r.endpointId).toEqual(s.endpointId); + expect(r.speechRecognitionLanguage).toEqual(s.speechRecognitionLanguage); + + r.close(); + s.close(); +}); + +test("Create SR from AudioConfig", () => { + const s: sdk.SpeechConfig = sdk.SpeechConfig.fromSubscription(Settings.SpeechSubscriptionKey, Settings.SpeechRegion); + + const f: File = WaveFileAudioInput.LoadFile(Settings.WaveFile); + const config: sdk.AudioConfig = sdk.AudioConfig.fromWavFileInput(f); + + const r: sdk.SpeechRecognizer = new sdk.SpeechRecognizer(s, config); + + expect(r).not.toBeUndefined(); + expect(r instanceof sdk.Recognizer); + + r.close(); + s.close(); +}); + +test("Null Language Throws", () => { + const s: sdk.SpeechConfig = sdk.SpeechConfig.fromSubscription(Settings.SpeechSubscriptionKey, Settings.SpeechRegion); + s.speechRecognitionLanguage = null; + + expect(() => new sdk.SpeechRecognizer(s)).toThrowError(); + + s.close(); +}); + +test("Create recognizer with language and audioConfig", () => { + const s: sdk.SpeechConfig = sdk.SpeechConfig.fromSubscription(Settings.SpeechSubscriptionKey, Settings.SpeechRegion); + s.speechRecognitionLanguage = "en-EN"; + + const f: File = WaveFileAudioInput.LoadFile(Settings.WaveFile); + const config: sdk.AudioConfig = sdk.AudioConfig.fromWavFileInput(f); + + const r: sdk.SpeechRecognizer = new sdk.SpeechRecognizer(s, config); + + expect(r).not.toBeUndefined(); + + s.close(); +}); + +test("Create Intent Recognizer", () => { + const s: sdk.SpeechConfig = sdk.SpeechConfig.fromSubscription(Settings.SpeechSubscriptionKey, Settings.SpeechRegion); + + const r: sdk.IntentRecognizer = new sdk.IntentRecognizer(s); + + s.close(); +}); + +test("testCreateIntentRecognizerLanguage1", () => { + const s: sdk.SpeechConfig = sdk.SpeechConfig.fromSubscription(Settings.SpeechSubscriptionKey, Settings.SpeechRegion); + s.speechRecognitionLanguage = null; + + expect(() => new sdk.IntentRecognizer(s)).toThrow(); + + s.close(); +}); + +test("Intent Recognizer Success", () => { + const s: sdk.SpeechConfig = sdk.SpeechConfig.fromSubscription(Settings.SpeechSubscriptionKey, Settings.SpeechRegion); + s.speechRecognitionLanguage = "en-US"; + + const r: sdk.IntentRecognizer = new sdk.IntentRecognizer(s); + + expect(r).not.toBeUndefined(); + + expect(r instanceof sdk.Recognizer); + + r.close(); + s.close(); +}); + +test("Intent Recognizer with Wave File.", () => { + const s: sdk.SpeechConfig = sdk.SpeechConfig.fromSubscription(Settings.SpeechSubscriptionKey, Settings.SpeechRegion); + s.speechRecognitionLanguage = "en-US"; + + const f: File = WaveFileAudioInput.LoadFile(Settings.WaveFile); + const config: sdk.AudioConfig = sdk.AudioConfig.fromWavFileInput(f); + + const r: sdk.IntentRecognizer = new sdk.IntentRecognizer(s, config); + + expect(r).not.toBeUndefined(); + + expect(r instanceof sdk.Recognizer); + + r.close(); + s.close(); +}); + +test("Intent Recognizer null language Throws", () => { + const s: sdk.SpeechTranslationConfig = sdk.SpeechTranslationConfig.fromSubscription(Settings.SpeechSubscriptionKey, Settings.SpeechRegion); + + expect(() => new sdk.TranslationRecognizer(s)).toThrow(); + + s.close(); +}); + +test("Translation Recognizer No Target Languages Throws", () => { + const s: sdk.SpeechTranslationConfig = sdk.SpeechTranslationConfig.fromSubscription(Settings.SpeechSubscriptionKey, Settings.SpeechRegion); + s.speechRecognitionLanguage = "en-EN"; + + expect(() => new sdk.TranslationRecognizer(s)).toThrow(); + + s.close(); +}); + +test("Translation Recognizer No Source Language Throws", () => { + const s: sdk.SpeechTranslationConfig = sdk.SpeechTranslationConfig.fromSubscription(Settings.SpeechSubscriptionKey, Settings.SpeechRegion); + s.addTargetLanguage("en-EN"); + + expect(() => new sdk.TranslationRecognizer(s)).toThrow(); + + s.close(); +}); + +test("Translation Recog success", () => { + const s: sdk.SpeechTranslationConfig = sdk.SpeechTranslationConfig.fromSubscription(Settings.SpeechSubscriptionKey, Settings.SpeechRegion); + s.speechRecognitionLanguage = "en-EN"; + s.addTargetLanguage("en-US"); + + const r: sdk.TranslationRecognizer = new sdk.TranslationRecognizer(s); + expect(r).not.toBeUndefined(); + + expect(r instanceof sdk.Recognizer); + + r.close(); + s.close(); +}); + +test("Translation Recognizer Null target languages throws", () => { + const s: sdk.SpeechTranslationConfig = sdk.SpeechTranslationConfig.fromSubscription(Settings.SpeechSubscriptionKey, Settings.SpeechRegion); + s.setProperty(sdk.PropertyId[sdk.PropertyId.SpeechServiceConnection_TranslationToLanguages], null); + s.speechRecognitionLanguage = "illegal"; + + expect(() => new sdk.TranslationRecognizer(s)).toThrow(); + + s.close(); +}); + +test("Test Translation Recognizer emty target list throws", () => { + const s: sdk.SpeechTranslationConfig = sdk.SpeechTranslationConfig.fromSubscription(Settings.SpeechSubscriptionKey, Settings.SpeechRegion); + s.speechRecognitionLanguage = "en-EN"; + s.setProperty(sdk.PropertyId[sdk.PropertyId.SpeechServiceConnection_TranslationToLanguages], ""); + + expect(() => new sdk.TranslationRecognizer(s)).toThrow(); + s.close(); +}); + +test("Translation Null voice value throws", () => { + const s: sdk.SpeechTranslationConfig = sdk.SpeechTranslationConfig.fromSubscription(Settings.SpeechSubscriptionKey, Settings.SpeechRegion); + s.setProperty(sdk.PropertyId[sdk.PropertyId.SpeechServiceConnection_TranslationToLanguages], "illegal"); + s.setProperty(sdk.PropertyId[sdk.PropertyId.SpeechServiceConnection_TranslationVoice], null); + s.speechRecognitionLanguage = "en-EN"; + + expect(() => new sdk.TranslationRecognizer(s)).toThrow(); + + s.close(); +}); + +test("Translition Recognizer success.", () => { + const s: sdk.SpeechTranslationConfig = sdk.SpeechTranslationConfig.fromSubscription(Settings.SpeechSubscriptionKey, Settings.SpeechRegion); + s.setProperty(sdk.PropertyId[sdk.PropertyId.SpeechServiceConnection_TranslationToLanguages], "en-US"); + s.speechRecognitionLanguage = "en-EN"; + + const r: sdk.TranslationRecognizer = new sdk.TranslationRecognizer(s); + expect(r).not.toBeUndefined(); + + expect(r instanceof sdk.Recognizer); + + r.close(); + s.close(); +}); + +test("Translation Recog Nul via prop set for targets", () => { + const s: sdk.SpeechTranslationConfig = sdk.SpeechTranslationConfig.fromSubscription(Settings.SpeechSubscriptionKey, Settings.SpeechRegion); + s.setProperty(sdk.PropertyId[sdk.PropertyId.SpeechServiceConnection_TranslationToLanguages], null); + + expect(() => { s.speechRecognitionLanguage = null; }).toThrow(); + + s.close(); +}); + +test("Translation Recog Null via propset voice and targets", () => { + const s: sdk.SpeechTranslationConfig = sdk.SpeechTranslationConfig.fromSubscription(Settings.SpeechSubscriptionKey, Settings.SpeechRegion); + s.setProperty(sdk.PropertyId[sdk.PropertyId.SpeechServiceConnection_TranslationToLanguages], null); + s.setProperty(sdk.PropertyId[sdk.PropertyId.SpeechServiceConnection_TranslationVoice], null); + + expect(() => new sdk.TranslationRecognizer(s)).toThrow(); + + s.close(); +}); + +test("Translation Recog Null via propset voice and targets with Language", () => { + const s: sdk.SpeechTranslationConfig = sdk.SpeechTranslationConfig.fromSubscription(Settings.SpeechSubscriptionKey, Settings.SpeechRegion); + s.setProperty(sdk.PropertyId[sdk.PropertyId.SpeechServiceConnection_TranslationToLanguages], null); + s.speechRecognitionLanguage = "en-US"; + s.setProperty(sdk.PropertyId[sdk.PropertyId.SpeechServiceConnection_TranslationVoice], null); + + expect(() => new sdk.TranslationRecognizer(s)).toThrow(); + + s.close(); +}); + +test("Translation Recog with empty targets via prop", () => { + const s: sdk.SpeechTranslationConfig = sdk.SpeechTranslationConfig.fromSubscription(Settings.SpeechSubscriptionKey, Settings.SpeechRegion); + s.setProperty(sdk.PropertyId[sdk.PropertyId.SpeechServiceConnection_TranslationToLanguages], ""); + s.speechRecognitionLanguage = "en-US"; + s.setProperty(sdk.PropertyId[sdk.PropertyId.SpeechServiceConnection_TranslationVoice], null); + + expect(() => new sdk.TranslationRecognizer(s)).toThrow(); + + s.close(); +}); + +test("Translation Recog, Null voice via prop throws", () => { + const s: sdk.SpeechTranslationConfig = sdk.SpeechTranslationConfig.fromSubscription(Settings.SpeechSubscriptionKey, Settings.SpeechRegion); + s.setProperty(sdk.PropertyId[sdk.PropertyId.SpeechServiceConnection_TranslationToLanguages], "en-US"); + s.speechRecognitionLanguage = "en-US"; + s.setProperty(sdk.PropertyId[sdk.PropertyId.SpeechServiceConnection_TranslationVoice], null); + + expect(() => new sdk.TranslationRecognizer(s)).toThrow(); + + s.close(); +}); + +test("Translation Recog success", () => { + const s: sdk.SpeechTranslationConfig = sdk.SpeechTranslationConfig.fromSubscription(Settings.SpeechSubscriptionKey, Settings.SpeechRegion); + s.setProperty(sdk.PropertyId[sdk.PropertyId.SpeechServiceConnection_TranslationToLanguages], "en-US"); + s.speechRecognitionLanguage = "en-US"; + s.setProperty(sdk.PropertyId[sdk.PropertyId.SpeechServiceConnection_TranslationVoice], "en-US"); + + const f: File = WaveFileAudioInput.LoadFile(Settings.WaveFile); + const config: sdk.AudioConfig = sdk.AudioConfig.fromWavFileInput(f); + + const r: sdk.TranslationRecognizer = new sdk.TranslationRecognizer(s); + expect(r).not.toBeUndefined(); + + expect(r instanceof sdk.Recognizer); + + r.close(); + s.close(); +}); + +test("testClose", () => { + const s: sdk.SpeechConfig = sdk.SpeechConfig.fromSubscription(Settings.SpeechSubscriptionKey, Settings.SpeechRegion); + + s.close(); +}); diff --git a/tests/SpeechRecognizerTests.ts b/tests/SpeechRecognizerTests.ts new file mode 100644 index 00000000..21d62db1 --- /dev/null +++ b/tests/SpeechRecognizerTests.ts @@ -0,0 +1,1825 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. +import * as sdk from "../microsoft.cognitiveservices.speech.sdk"; +import { ConsoleLoggingListener, WebsocketMessageAdapter } from "../src/common.browser/Exports"; +import { ServiceRecognizerBase } from "../src/common.speech/Exports"; +import { QueryParameterNames } from "../src/common.speech/QueryParameterNames"; +import { ConnectionStartEvent } from "../src/common/Exports"; +import { Events, EventType, PlatformEvent } from "../src/common/Exports"; + +import { Settings } from "./Settings"; +import { WaveFileAudioInput } from "./WaveFileAudioInputStream"; + +import * as fs from "fs"; +import { setTimeout } from "timers"; +import { ByteBufferAudioFile } from "./ByteBufferAudioFile"; +import WaitForCondition from "./Utilities"; + +const FIRST_EVENT_ID: number = 1; +const Recognizing: string = "Recognizing"; +const Recognized: string = "Recognized"; +const Session: string = "Session"; +const Canceled: string = "Canceled"; + +let objsToClose: any[]; + +beforeAll(() => { + // override inputs, if necessary + Settings.LoadSettings(); + Events.instance.attachListener(new ConsoleLoggingListener(EventType.Debug)); +}); + +// Test cases are run linerally, the only other mechanism to demark them in the output is to put a console line in each case and +// report the name. +beforeEach(() => { + objsToClose = []; + // tslint:disable-next-line:no-console + console.info("---------------------------------------Starting test case-----------------------------------"); + // tslint:disable-next-line:no-console + console.info("Start Time: " + new Date(Date.now()).toLocaleString()); +}); + +afterEach(() => { + // tslint:disable-next-line:no-console + console.info("End Time: " + new Date(Date.now()).toLocaleString()); + objsToClose.forEach((value: any, index: number, array: any[]) => { + if (typeof value.close === "function") { + value.close(); + } + }); +}); + +const BuildRecognizerFromWaveFile: (speechConfig?: sdk.SpeechConfig) => sdk.SpeechRecognizer = (speechConfig?: sdk.SpeechConfig): sdk.SpeechRecognizer => { + + let s: sdk.SpeechConfig = speechConfig; + if (s === undefined) { + s = BuildSpeechConfig(); + // Since we're not going to return it, mark it for closure. + objsToClose.push(s); + } + + const f: File = WaveFileAudioInput.LoadFile(Settings.WaveFile); + const config: sdk.AudioConfig = sdk.AudioConfig.fromWavFileInput(f); + + const language: string = Settings.WaveFileLanguage; + if (s.speechRecognitionLanguage === undefined) { + s.speechRecognitionLanguage = language; + } + + const r: sdk.SpeechRecognizer = new sdk.SpeechRecognizer(s, config); + expect(r).not.toBeUndefined(); + + return r; +}; + +const BuildSpeechConfig: () => sdk.SpeechConfig = (): sdk.SpeechConfig => { + + let s: sdk.SpeechConfig; + if (undefined === Settings.SpeechEndpoint) { + s = sdk.SpeechConfig.fromSubscription(Settings.SpeechSubscriptionKey, Settings.SpeechRegion); + } else { + s = sdk.SpeechConfig.fromEndpoint(new URL(Settings.SpeechEndpoint), Settings.SpeechSubscriptionKey); + } + + expect(s).not.toBeUndefined(); + return s; +}; + +test("testSpeechRecognizer1", () => { + // tslint:disable-next-line:no-console + console.info("Name: testSpeechRecognizer1"); + const speechConfig: sdk.SpeechConfig = sdk.SpeechConfig.fromSubscription(Settings.SpeechSubscriptionKey, Settings.SpeechRegion); + expect(speechConfig).not.toBeUndefined(); + + const f: File = WaveFileAudioInput.LoadFile(Settings.WaveFile); + const config: sdk.AudioConfig = sdk.AudioConfig.fromWavFileInput(f); + + const r: sdk.SpeechRecognizer = new sdk.SpeechRecognizer(speechConfig, config); + objsToClose.push(r); + + expect(r).not.toBeUndefined(); + + expect(r instanceof sdk.Recognizer); +}); + +test("testGetLanguage1", () => { + // tslint:disable-next-line:no-console + console.info("Name: testGetLanguage1"); + const r: sdk.SpeechRecognizer = BuildRecognizerFromWaveFile(); + objsToClose.push(r); + + expect(r.speechRecognitionLanguage).not.toBeNull(); +}); + +test("testGetLanguage2", () => { + // tslint:disable-next-line:no-console + console.info("Name: testGetLanguage2"); + const s: sdk.SpeechConfig = BuildSpeechConfig(); + objsToClose.push(s); + + const language: string = "de-DE"; + s.speechRecognitionLanguage = language; + + const r: sdk.SpeechRecognizer = BuildRecognizerFromWaveFile(s); + objsToClose.push(r); + + expect(r.speechRecognitionLanguage).not.toBeNull(); + expect(language === r.speechRecognitionLanguage); +}); + +test("testGetOutputFormatDefault", () => { + // tslint:disable-next-line:no-console + console.info("Name: testGetOutputFormatDefault"); + const r: sdk.SpeechRecognizer = BuildRecognizerFromWaveFile(); + objsToClose.push(r); + + expect(r.outputFormat === sdk.OutputFormat.Simple); +}); + +test("testGetParameters", () => { + // tslint:disable-next-line:no-console + console.info("Name: testGetParameters"); + const r: sdk.SpeechRecognizer = BuildRecognizerFromWaveFile(); + objsToClose.push(r); + + expect(r.properties).not.toBeUndefined(); + // expect(r.language == r.properties.getProperty(RecognizerParameterNames.SpeechRecognitionLanguage)); + // expect(r.deploymentId == r.properties.getProperty(RecognizerParameterNames.SpeechMspeechConfigImpl// TODO: is this really the correct mapping? + expect(r.speechRecognitionLanguage).not.toBeUndefined(); + expect(r.endpointId === r.properties.getProperty(sdk.PropertyId.SpeechServiceConnection_EndpointId, null)); // todo: is this really the correct mapping? +}); + +describe.each([true, false])("Service based tests", (forceNodeWebSocket: boolean) => { + + beforeAll(() => { + WebsocketMessageAdapter.forceNpmWebSocket = forceNodeWebSocket; + }); + + afterAll(() => { + WebsocketMessageAdapter.forceNpmWebSocket = false; + }); + + test("testGetOutputFormatDetailed", (done: jest.DoneCallback) => { + // tslint:disable-next-line:no-console + console.info("Name: testGetOutputFormatDetailed"); + + const s: sdk.SpeechConfig = BuildSpeechConfig(); + objsToClose.push(s); + + s.outputFormat = sdk.OutputFormat.Detailed; + + const r: sdk.SpeechRecognizer = BuildRecognizerFromWaveFile(s); + objsToClose.push(r); + + expect(r.outputFormat === sdk.OutputFormat.Detailed); + + r.recognizeOnceAsync((result: sdk.SpeechRecognitionResult) => { + expect(result).not.toBeUndefined(); + expect(result.text).toEqual(Settings.WaveFileText); + + done(); + }, (error: string) => { + done.fail(error); + }); + }); + + describe("Counts Telemetry", () => { + afterAll(() => { + ServiceRecognizerBase.telemetryData = undefined; + }); + + test("RecognizeOnce", (done: jest.DoneCallback) => { + // tslint:disable-next-line:no-console + console.info("Name: RecognizeOnce"); + + const r: sdk.SpeechRecognizer = BuildRecognizerFromWaveFile(); + objsToClose.push(r); + + let telemetryEvents: number = 0; + let sessionId: string; + + r.sessionStarted = (r: sdk.Recognizer, e: sdk.SessionEventArgs): void => { + sessionId = e.sessionId; + }; + + ServiceRecognizerBase.telemetryData = (json: string): void => { + // Only record telemetry events from this session. + if (json !== undefined && + sessionId !== undefined && + json.indexOf(sessionId) > 0) { + telemetryEvents++; + } + }; + + r.recognizeOnceAsync( + (p2: sdk.SpeechRecognitionResult) => { + try { + const res: sdk.SpeechRecognitionResult = p2; + expect(res).not.toBeUndefined(); + expect(res.text).toEqual("What's the weather like?"); + expect(res.reason).toEqual(sdk.ResultReason.RecognizedSpeech); + expect(telemetryEvents).toEqual(1); + done(); + } catch (error) { + done.fail(error); + } + + }, + (error: string) => { + done.fail(error); + }); + }); + }); + + test("Event Tests (RecognizeOnce)", (done: jest.DoneCallback) => { + // tslint:disable-next-line:no-console + console.info("Name: Event Tests (RecognizeOnce)"); + const SpeechStartDetectedEvent = "SpeechStartDetectedEvent"; + const SpeechEndDetectedEvent = "SpeechEndDetectedEvent"; + const SessionStartedEvent = "SessionStartedEvent"; + const SessionStoppedEvent = "SessionStoppedEvent"; + const r: sdk.SpeechRecognizer = BuildRecognizerFromWaveFile(); + objsToClose.push(r); + + const eventsMap: { [id: string]: number; } = {}; + let eventIdentifier: number = 1; + + r.recognized = (o: sdk.Recognizer, e: sdk.SpeechRecognitionEventArgs) => { + eventsMap[Recognized] = eventIdentifier++; + }; + + r.recognizing = (o: sdk.Recognizer, e: sdk.SpeechRecognitionEventArgs) => { + const now: number = eventIdentifier++; + eventsMap[Recognizing + "-" + Date.now().toPrecision(4)] = now; + eventsMap[Recognizing] = now; + }; + + r.canceled = (o: sdk.Recognizer, e: sdk.SpeechRecognitionCanceledEventArgs) => { + eventsMap[Canceled] = eventIdentifier++; + }; + + // todo eventType should be renamed and be a function getEventType() + r.speechStartDetected = (o: sdk.Recognizer, e: sdk.RecognitionEventArgs) => { + const now: number = eventIdentifier++; + // tslint:disable-next-line:no-string-literal + eventsMap[SpeechStartDetectedEvent] = now; + }; + r.speechEndDetected = (o: sdk.Recognizer, e: sdk.RecognitionEventArgs) => { + const now: number = eventIdentifier++; + eventsMap[SpeechEndDetectedEvent] = now; + }; + + r.sessionStarted = (o: sdk.Recognizer, e: sdk.SessionEventArgs) => { + const now: number = eventIdentifier++; + eventsMap[Session + SessionStartedEvent] = now; + eventsMap[Session + SessionStartedEvent + "-" + Date.now().toPrecision(4)] = now; + }; + r.sessionStopped = (o: sdk.Recognizer, e: sdk.SessionEventArgs) => { + const now: number = eventIdentifier++; + eventsMap[Session + SessionStoppedEvent] = now; + eventsMap[Session + SessionStoppedEvent + "-" + Date.now().toPrecision(4)] = now; + }; + + // note: TODO session stopped event not necessarily raised before async operation returns! + // this makes this test flaky + + r.recognizeOnceAsync( + (res: sdk.SpeechRecognitionResult) => { + expect(res).not.toBeUndefined(); + expect(res.text).toEqual("What's the weather like?"); + expect(res.reason).toEqual(sdk.ResultReason.RecognizedSpeech); + + // session events are first and last event + const LAST_RECORDED_EVENT_ID: number = --eventIdentifier; + + expect(LAST_RECORDED_EVENT_ID).toBeGreaterThan(FIRST_EVENT_ID); + + expect(Session + SessionStartedEvent in eventsMap).toEqual(true); + expect(eventsMap[Session + SessionStartedEvent]).toEqual(FIRST_EVENT_ID); + + if (Session + SessionStoppedEvent in eventsMap) { + expect(LAST_RECORDED_EVENT_ID).toEqual(eventsMap[Session + SessionStoppedEvent]); + } + // end events come after start events. + if (Session + SessionStoppedEvent in eventsMap) { + expect(eventsMap[Session + SessionStartedEvent]) + .toBeLessThan(eventsMap[Session + SessionStoppedEvent]); + } + + expect(eventsMap[SpeechStartDetectedEvent]) + .toBeLessThan(eventsMap[SpeechEndDetectedEvent]); + expect((FIRST_EVENT_ID + 1)).toEqual(eventsMap[SpeechStartDetectedEvent]); + + // make sure, first end of speech, then final result + expect((LAST_RECORDED_EVENT_ID - 1)).toEqual(eventsMap[SpeechEndDetectedEvent]); + + expect((LAST_RECORDED_EVENT_ID)).toEqual(eventsMap[Recognized]); + + // recognition events come after session start but before session end events + expect(eventsMap[Session + SessionStartedEvent]) + .toBeLessThan(eventsMap[SpeechStartDetectedEvent]); + + if (Session + SessionStoppedEvent in eventsMap) { + expect(eventsMap[SpeechEndDetectedEvent]) + .toBeLessThan(eventsMap[Session + SessionStoppedEvent]); + } + + // there is no partial result reported after the final result + // (and check that we have intermediate and final results recorded) + if (Recognizing in eventsMap) { + expect(eventsMap[Recognizing]) + .toBeGreaterThan(eventsMap[SpeechStartDetectedEvent]); + } + + // speech should stop before getting the final result. + expect(eventsMap[Recognized]).toBeGreaterThan(eventsMap[SpeechEndDetectedEvent]); + + expect(eventsMap[Recognizing]).toBeLessThan(eventsMap[Recognized]); + + // make sure events we don't expect, don't get raised + expect(Canceled in eventsMap).toBeFalsy(); + + done(); + }, (error: string) => { + done.fail(error); + }); + + }); + + test("Event Tests (Continuous)", (done: jest.DoneCallback) => { + // tslint:disable-next-line:no-console + console.info("Name: Event Tests (Continuous)"); + const SpeechStartDetectedEvent = "SpeechStartDetectedEvent"; + const SpeechEndDetectedEvent = "SpeechEndDetectedEvent"; + const SessionStartedEvent = "SessionStartedEvent"; + const SessionStoppedEvent = "SessionStoppedEvent"; + const r: sdk.SpeechRecognizer = BuildRecognizerFromWaveFile(); + objsToClose.push(r); + + let sessionStopped: boolean = false; + + const eventsMap: { [id: string]: number; } = {}; + let eventIdentifier: number = 1; + + r.recognized = (o: sdk.Recognizer, e: sdk.SpeechRecognitionEventArgs) => { + eventsMap[Recognized] = eventIdentifier++; + }; + + r.recognizing = (o: sdk.Recognizer, e: sdk.SpeechRecognitionEventArgs) => { + const now: number = eventIdentifier++; + eventsMap[Recognizing + "-" + Date.now().toPrecision(4)] = now; + eventsMap[Recognizing] = now; + }; + + r.canceled = (o: sdk.Recognizer, e: sdk.SpeechRecognitionCanceledEventArgs) => { + try { + expect(e.errorDetails).toBeUndefined(); + expect(sdk.CancellationErrorCode[e.errorCode]).toEqual(sdk.CancellationErrorCode[sdk.CancellationErrorCode.NoError]); + expect(sdk.CancellationReason[e.reason]).toEqual(sdk.CancellationReason[sdk.CancellationReason.EndOfStream]); + eventsMap[Canceled] = eventIdentifier++; + } catch (error) { + done.fail(error); + } + }; + + // todo eventType should be renamed and be a function getEventType() + r.speechStartDetected = (o: sdk.Recognizer, e: sdk.RecognitionEventArgs) => { + const now: number = eventIdentifier++; + // tslint:disable-next-line:no-string-literal + eventsMap[SpeechStartDetectedEvent] = now; + }; + r.speechEndDetected = (o: sdk.Recognizer, e: sdk.RecognitionEventArgs) => { + const now: number = eventIdentifier++; + eventsMap[SpeechEndDetectedEvent] = now; + }; + + r.sessionStarted = (o: sdk.Recognizer, e: sdk.SessionEventArgs) => { + const now: number = eventIdentifier++; + eventsMap[Session + SessionStartedEvent] = now; + eventsMap[Session + SessionStartedEvent + "-" + Date.now().toPrecision(4)] = now; + }; + + r.sessionStopped = (o: sdk.Recognizer, e: sdk.SessionEventArgs) => { + const now: number = eventIdentifier++; + eventsMap[Session + SessionStoppedEvent] = now; + eventsMap[Session + SessionStoppedEvent + "-" + Date.now().toPrecision(4)] = now; + sessionStopped = true; + }; + + r.startContinuousRecognitionAsync(); + + WaitForCondition(() => sessionStopped, () => { + try { + // session events are first and last event + const LAST_RECORDED_EVENT_ID: number = --eventIdentifier; + expect(LAST_RECORDED_EVENT_ID).toBeGreaterThan(FIRST_EVENT_ID); + + expect(Session + SessionStartedEvent in eventsMap).toEqual(true); + + expect(eventsMap[Session + SessionStartedEvent]).toEqual(FIRST_EVENT_ID); + + expect(Session + SessionStoppedEvent in eventsMap).toEqual(true); + expect(LAST_RECORDED_EVENT_ID).toEqual(eventsMap[Session + SessionStoppedEvent]); + + // end events come after start events. + if (Session + SessionStoppedEvent in eventsMap) { + expect(eventsMap[Session + SessionStartedEvent]) + .toBeLessThan(eventsMap[Session + SessionStoppedEvent]); + } + + expect(eventsMap[SpeechStartDetectedEvent]) + .toBeLessThan(eventsMap[SpeechEndDetectedEvent]); + expect((FIRST_EVENT_ID + 1)).toEqual(eventsMap[SpeechStartDetectedEvent]); + + // make sure, first end of speech, then final result + expect((LAST_RECORDED_EVENT_ID - 1)).toEqual(eventsMap[Canceled]); + expect((LAST_RECORDED_EVENT_ID - 2)).toEqual(eventsMap[SpeechEndDetectedEvent]); + expect((LAST_RECORDED_EVENT_ID - 3)).toEqual(eventsMap[Recognized]); + + // recognition events come after session start but before session end events + expect(eventsMap[Session + SessionStartedEvent]) + .toBeLessThan(eventsMap[SpeechStartDetectedEvent]); + + if (Session + SessionStoppedEvent in eventsMap) { + expect(eventsMap[SpeechEndDetectedEvent]) + .toBeLessThan(eventsMap[Session + SessionStoppedEvent]); + } + + // there is no partial result reported after the final result + // (and check that we have intermediate and final results recorded) + if (Recognizing in eventsMap) { + expect(eventsMap[Recognizing]) + .toBeGreaterThan(eventsMap[SpeechStartDetectedEvent]); + } + + // speech should not stop before getting the final result. + expect(eventsMap[Recognized]).toBeLessThan(eventsMap[SpeechEndDetectedEvent]); + + expect(eventsMap[Recognizing]).toBeLessThan(eventsMap[Recognized]); + + // make sure we got a cancel event. + expect(Canceled in eventsMap).toEqual(true); + + done(); + } catch (error) { + done.fail(error); + } + }); + }, 20000); + + describe("Disables Telemetry", () => { + + // Re-enable telemetry + afterEach(() => sdk.Recognizer.enableTelemetry(true)); + + test("testStopContinuousRecognitionAsyncWithoutTelemetry", (done: jest.DoneCallback) => { + // tslint:disable-next-line:no-console + console.info("Name: testStopContinuousRecognitionAsyncWithoutTelemetry"); + // start with telemetry disabled + const r: sdk.SpeechRecognizer = BuildRecognizerFromWaveFile(); + objsToClose.push(r); + + let eventDone: boolean = false; + let canceled: boolean = false; + let telemetryEvents: number = 0; + + // disable telemetry data + sdk.Recognizer.enableTelemetry(false); + + ServiceRecognizerBase.telemetryData = (json: string): void => { + telemetryEvents++; + }; + + r.recognized = (o: sdk.Recognizer, e: sdk.SpeechRecognitionEventArgs) => { + try { + eventDone = true; + expect(sdk.ResultReason[e.result.reason]).toEqual(sdk.ResultReason[sdk.ResultReason.RecognizedSpeech]); + expect(e.result.text).toEqual("What's the weather like?"); + } catch (error) { + done.fail(error); + } + }; + + r.canceled = (o: sdk.Recognizer, e: sdk.SpeechRecognitionCanceledEventArgs): void => { + try { + canceled = true; + expect(e.errorDetails).toBeUndefined(); + expect(e.reason).toEqual(sdk.CancellationReason.EndOfStream); + } catch (error) { + done.fail(error); + } + }; + + r.startContinuousRecognitionAsync( + () => WaitForCondition(() => (eventDone && canceled), () => { + r.stopContinuousRecognitionAsync( + () => { + // since we disabled, there should be no telemetry + // event run through our handler + expect(telemetryEvents).toEqual(0); + done(); + }, + (err: string) => { + done.fail(err); + }); + }), + (err: string) => { + done.fail(err); + }); + }); + }); + + test("testStopContinuousRecognitionAsyncWithTelemetry", (done: jest.DoneCallback) => { + // tslint:disable-next-line:no-console + console.info("Name: testStopContinuousRecognitionAsyncWithTelemetry"); + // Now, the same test, but with telemetry enabled. + const r: sdk.SpeechRecognizer = BuildRecognizerFromWaveFile(); + objsToClose.push(r); + + let eventDone: boolean = false; + let canceled: boolean = false; + let telemetryEvents: number = 0; + + // enable telemetry data + sdk.Recognizer.enableTelemetry(true); + + ServiceRecognizerBase.telemetryData = (json: string): void => { + telemetryEvents++; + }; + + r.recognized = (o: sdk.Recognizer, e: sdk.SpeechRecognitionEventArgs) => { + try { + eventDone = true; + expect(sdk.ResultReason[e.result.reason]).toEqual(sdk.ResultReason[sdk.ResultReason.RecognizedSpeech]); + expect(e.result.text).toEqual("What's the weather like?"); + } catch (error) { + done.fail(error); + } + }; + + r.canceled = (o: sdk.Recognizer, e: sdk.SpeechRecognitionCanceledEventArgs): void => { + try { + canceled = true; + expect(e.errorDetails).toBeUndefined(); + expect(e.reason).toEqual(sdk.CancellationReason.EndOfStream); + } catch (error) { + done.fail(error); + } + }; + + r.startContinuousRecognitionAsync( + () => WaitForCondition(() => (eventDone && canceled), () => { + r.stopContinuousRecognitionAsync( + () => { + // Three? One for the phrase that was recognized. + // Once for the end of stream. + // Once when closed. + expect(telemetryEvents).toEqual(3); + done(); + }, + (err: string) => { + done.fail(err); + }); + }), + (err: string) => { + done.fail(err); + }); + }); + + test("Close with no recognition", () => { + // tslint:disable-next-line:no-console + console.info("Name: Close with no recognition"); + const r: sdk.SpeechRecognizer = BuildRecognizerFromWaveFile(); + objsToClose.push(r); + }); + + test("Config is copied on construction", () => { + // tslint:disable-next-line:no-console + console.info("Name: Config is copied on construction"); + + const s: sdk.SpeechConfig = BuildSpeechConfig(); + objsToClose.push(s); + + s.speechRecognitionLanguage = "en-US"; + + const ranVal: string = Math.random().toString(); + + s.setProperty("RandomProperty", ranVal); + s.setProperty(sdk.PropertyId[sdk.PropertyId.SpeechServiceConnection_TranslationVoice], "Microsoft Server Speech Text to Speech Voice (en-US, ZiraRUS)"); + + const r: sdk.SpeechRecognizer = BuildRecognizerFromWaveFile(s); + objsToClose.push(r); + + expect(r.speechRecognitionLanguage).toEqual("en-US"); + expect(r.properties.getProperty("RandomProperty")).toEqual(ranVal); + expect(r.properties.getProperty(sdk.PropertyId[sdk.PropertyId.SpeechServiceConnection_TranslationVoice])).toEqual("Microsoft Server Speech Text to Speech Voice (en-US, ZiraRUS)"); + + // Change them. + s.speechRecognitionLanguage = "de-DE"; + s.setProperty("RandomProperty", Math.random.toString()); + s.setProperty(sdk.PropertyId[sdk.PropertyId.SpeechServiceConnection_TranslationVoice], "Microsoft Server Speech Text to Speech Voice (de-DE, Hedda)"); + + // Validate no change. + expect(r.speechRecognitionLanguage).toEqual("en-US"); + expect(r.properties.getProperty("RandomProperty")).toEqual(ranVal); + expect(r.properties.getProperty(sdk.PropertyId[sdk.PropertyId.SpeechServiceConnection_TranslationVoice])).toEqual("Microsoft Server Speech Text to Speech Voice (en-US, ZiraRUS)"); + + }); + + test("PushStream4KNoDelay", (done: jest.DoneCallback) => { + // tslint:disable-next-line:no-console + console.info("Name: PushStream4KNoDelay"); + const s: sdk.SpeechConfig = BuildSpeechConfig(); + objsToClose.push(s); + + const f: ArrayBuffer = WaveFileAudioInput.LoadArrayFromFile(Settings.WaveFile); + const p: sdk.PushAudioInputStream = sdk.AudioInputStream.createPushStream(); + const config: sdk.AudioConfig = sdk.AudioConfig.fromStreamInput(p); + let i: number; + + const sendSize: number = 4096; + + for (i = sendSize - 1; i < f.byteLength; i += sendSize) { + p.write(f.slice(i - (sendSize - 1), i + 1)); + } + + p.write(f.slice(i - (sendSize - 1), f.byteLength)); + p.close(); + + const r: sdk.SpeechRecognizer = new sdk.SpeechRecognizer(s, config); + objsToClose.push(r); + + expect(r).not.toBeUndefined(); + expect(r instanceof sdk.Recognizer); + + r.recognizeOnceAsync( + (p2: sdk.SpeechRecognitionResult) => { + const res: sdk.SpeechRecognitionResult = p2; + + expect(res).not.toBeUndefined(); + expect(sdk.ResultReason[res.reason]).toEqual(sdk.ResultReason[sdk.ResultReason.RecognizedSpeech]); + expect(res.text).toEqual("What's the weather like?"); + done(); + }, + (error: string) => { + done.fail(error); + }); + }); + + test("PushStream4KPostRecognizePush", (done: jest.DoneCallback) => { + // tslint:disable-next-line:no-console + console.info("Name: PushStream4KPostRecognizePush"); + const s: sdk.SpeechConfig = BuildSpeechConfig(); + objsToClose.push(s); + + const f: ArrayBuffer = WaveFileAudioInput.LoadArrayFromFile(Settings.WaveFile); + const p: sdk.PushAudioInputStream = sdk.AudioInputStream.createPushStream(); + const config: sdk.AudioConfig = sdk.AudioConfig.fromStreamInput(p); + let i: number; + + const r: sdk.SpeechRecognizer = new sdk.SpeechRecognizer(s, config); + objsToClose.push(r); + expect(r).not.toBeUndefined(); + expect(r instanceof sdk.Recognizer); + + r.recognizeOnceAsync( + (p2: sdk.SpeechRecognitionResult) => { + const res: sdk.SpeechRecognitionResult = p2; + + expect(res).not.toBeUndefined(); + expect(sdk.ResultReason[res.reason]).toEqual(sdk.ResultReason[sdk.ResultReason.RecognizedSpeech]); + expect(res.text).toEqual("What's the weather like?"); + + done(); + }, + (error: string) => { + done.fail(error); + }); + + const sendSize: number = 4096; + + for (i = sendSize - 1; i < f.byteLength; i += sendSize) { + p.write(f.slice(i - (sendSize - 1), i)); + } + + p.write(f.slice(i - (sendSize - 1), f.byteLength - 1)); + p.close(); + + }); + + test("PullStreamFullFill", (done: jest.DoneCallback) => { + // tslint:disable-next-line:no-console + console.info("Name: PullStreamFullFill"); + const s: sdk.SpeechConfig = BuildSpeechConfig(); + objsToClose.push(s); + + const fileBuffer: ArrayBuffer = WaveFileAudioInput.LoadArrayFromFile(Settings.WaveFile); + + let bytesSent: number = 0; + let p: sdk.PullAudioInputStream; + + p = sdk.AudioInputStream.createPullStream( + { + close: () => { return; }, + read: (buffer: ArrayBuffer): number => { + const copyArray: Uint8Array = new Uint8Array(buffer); + const start: number = bytesSent; + const end: number = buffer.byteLength > (fileBuffer.byteLength - bytesSent) ? (fileBuffer.byteLength - 1) : (bytesSent + buffer.byteLength - 1); + copyArray.set(new Uint8Array(fileBuffer.slice(start, end))); + bytesSent += (end - start) + 1; + + if (bytesSent < buffer.byteLength) { + setTimeout(() => p.close(), 1000); + } + + return (end - start) + 1; + }, + }); + + const config: sdk.AudioConfig = sdk.AudioConfig.fromStreamInput(p); + + const r: sdk.SpeechRecognizer = new sdk.SpeechRecognizer(s, config); + objsToClose.push(r); + + expect(r).not.toBeUndefined(); + expect(r instanceof sdk.Recognizer); + + r.recognizeOnceAsync( + (p2: sdk.SpeechRecognitionResult) => { + const res: sdk.SpeechRecognitionResult = p2; + + expect(res).not.toBeUndefined(); + expect(sdk.ResultReason[res.reason]).toEqual(sdk.ResultReason[sdk.ResultReason.RecognizedSpeech]); + expect(res.text).toEqual("What's the weather like?"); + + done(); + }, + (error: string) => { + done.fail(error); + }); + }); + + test("PullStreamHalfFill", (done: jest.DoneCallback) => { + // tslint:disable-next-line:no-console + console.info("Name: PullStreamHalfFill"); + const s: sdk.SpeechConfig = BuildSpeechConfig(); + objsToClose.push(s); + + const fileBuffer: ArrayBuffer = WaveFileAudioInput.LoadArrayFromFile(Settings.WaveFile); + + let bytesSent: number = 0; + let p: sdk.PullAudioInputStream; + + p = sdk.AudioInputStream.createPullStream( + { + close: () => { return; }, + read: (buffer: ArrayBuffer): number => { + const copyArray: Uint8Array = new Uint8Array(buffer); + const start: number = bytesSent; + const fillSize: number = Math.round(buffer.byteLength / 2); + const end: number = fillSize > (fileBuffer.byteLength - bytesSent) ? (fileBuffer.byteLength - 1) : (bytesSent + fillSize - 1); + copyArray.set(new Uint8Array(fileBuffer.slice(start, end))); + bytesSent += (end - start) + 1; + + if (bytesSent < buffer.byteLength) { + setTimeout(() => p.close(), 1000); + } + + return (end - start) + 1; + }, + }); + + const config: sdk.AudioConfig = sdk.AudioConfig.fromStreamInput(p); + + const r: sdk.SpeechRecognizer = new sdk.SpeechRecognizer(s, config); + expect(r).not.toBeUndefined(); + expect(r instanceof sdk.Recognizer); + objsToClose.push(r); + + r.recognizeOnceAsync( + (p2: sdk.SpeechRecognitionResult) => { + const res: sdk.SpeechRecognitionResult = p2; + + expect(res).not.toBeUndefined(); + expect(sdk.ResultReason[res.reason]).toEqual(sdk.ResultReason[sdk.ResultReason.RecognizedSpeech]); + expect(res.text).toEqual("What's the weather like?"); + + done(); + }, + (error: string) => { + done.fail(error); + }); + }); + + test("InitialSilenceTimeout (pull)", (done: jest.DoneCallback) => { + // tslint:disable-next-line:no-console + console.info("Name: InitialSilenceTimeout (pull)"); + let p: sdk.PullAudioInputStream; + let bytesSent: number = 0; + + // To make sure we don't send a ton of extra data. + // 5s * 16K * 2 * 1.25; + // For reference, before the throttling was implemented, we sent 6-10x the required data. + const expectedBytesSent: number = 5 * 16000 * 2 * 1.25; + + p = sdk.AudioInputStream.createPullStream( + { + close: () => { return; }, + read: (buffer: ArrayBuffer): number => { + bytesSent += buffer.byteLength; + return buffer.byteLength; + }, + }); + + const config: sdk.AudioConfig = sdk.AudioConfig.fromStreamInput(p); + + testInitialSilienceTimeout(config, done, () => expect(bytesSent).toBeLessThan(expectedBytesSent)); + }, 15000); + + test("InitialSilenceTimeout (push)", (done: jest.DoneCallback) => { + // tslint:disable-next-line:no-console + console.info("Name: InitialSilenceTimeout (push)"); + const p: sdk.PushAudioInputStream = sdk.AudioInputStream.createPushStream(); + const bigFileBuffer: Uint8Array = new Uint8Array(1024 * 1024); + const config: sdk.AudioConfig = sdk.AudioConfig.fromStreamInput(p); + + p.write(bigFileBuffer.buffer); + p.close(); + + testInitialSilienceTimeout(config, done); + }, 15000); + + test("InitialSilenceTimeout (File)", (done: jest.DoneCallback) => { + // tslint:disable-next-line:no-console + console.info("Name: InitialSilenceTimeout (File)"); + + const bigFileBuffer: Uint8Array = new Uint8Array(1024 * 1024); + const bigFile: File = ByteBufferAudioFile.Load(bigFileBuffer.buffer); + + const config: sdk.AudioConfig = sdk.AudioConfig.fromWavFileInput(bigFile); + + testInitialSilienceTimeout(config, done); + }, 15000); + + const testInitialSilienceTimeout = (config: sdk.AudioConfig, done: jest.DoneCallback, addedChecks?: () => void): void => { + const s: sdk.SpeechConfig = BuildSpeechConfig(); + objsToClose.push(s); + + // To validate the data isn't sent too fast. + const startTime: number = Date.now(); + + const r: sdk.SpeechRecognizer = new sdk.SpeechRecognizer(s, config); + objsToClose.push(r); + + expect(r).not.toBeUndefined(); + expect(r instanceof sdk.Recognizer); + + let numReports: number = 0; + + r.canceled = (o: sdk.Recognizer, e: sdk.SpeechRecognitionCanceledEventArgs) => { + done.fail(e.errorDetails); + }; + + r.recognized = (o: sdk.Recognizer, e: sdk.SpeechRecognitionEventArgs) => { + try { + const res: sdk.SpeechRecognitionResult = e.result; + expect(res).not.toBeUndefined(); + expect(sdk.ResultReason.NoMatch).toEqual(res.reason); + expect(res.text).toBeUndefined(); + + const nmd: sdk.NoMatchDetails = sdk.NoMatchDetails.fromResult(res); + expect(nmd.reason).toEqual(sdk.NoMatchReason.InitialSilenceTimeout); + } catch (error) { + done.fail(error); + } finally { + numReports++; + } + + }; + + r.recognizeOnceAsync( + (p2: sdk.SpeechRecognitionResult) => { + const res: sdk.SpeechRecognitionResult = p2; + numReports++; + + expect(res).not.toBeUndefined(); + expect(sdk.ResultReason.NoMatch).toEqual(res.reason); + expect(res.errorDetails).toBeUndefined(); + expect(res.text).toBeUndefined(); + + const nmd: sdk.NoMatchDetails = sdk.NoMatchDetails.fromResult(res); + expect(nmd.reason).toEqual(sdk.NoMatchReason.InitialSilenceTimeout); + expect(Date.now()).toBeGreaterThanOrEqual(startTime + ((res.offset / 1e+4) / 2)); + + }, + (error: string) => { + fail(error); + }); + + WaitForCondition(() => (numReports === 2), () => { + try { + if (!!addedChecks) { + addedChecks(); + } + done(); + } catch (error) { + done.fail(error); + } + }); + }; + + test.skip("InitialBabbleTimeout", (done: jest.DoneCallback) => { + // tslint:disable-next-line:no-console + console.info("Name: InitialBabbleTimeout"); + + const s: sdk.SpeechConfig = sdk.SpeechConfig.fromSubscription(Settings.SpeechSubscriptionKey, Settings.SpeechRegion); + expect(s).not.toBeUndefined(); + + s.speechRecognitionLanguage = "es-MX"; + + const f: File = WaveFileAudioInput.LoadFile(Settings.WaveFile); + const config: sdk.AudioConfig = sdk.AudioConfig.fromWavFileInput(f); + + const r: sdk.SpeechRecognizer = new sdk.SpeechRecognizer(s, config); + expect(r).not.toBeUndefined(); + expect(r instanceof sdk.Recognizer); + + r.recognizeOnceAsync( + (p2: sdk.SpeechRecognitionResult) => { + const res: sdk.SpeechRecognitionResult = p2; + expect(res).not.toBeUndefined(); + expect("What's the weather like?").toEqual(res.text); + expect(res.reason).toEqual(sdk.ResultReason.RecognizedSpeech); + + r.close(); + s.close(); + done(); + + }, + (error: string) => { + r.close(); + s.close(); + done.fail(error); + }); + }); + + test("emptyFile", (done: jest.DoneCallback) => { + // tslint:disable-next-line:no-console + console.info("Name: emptyFile"); + // Server Responses: + // turn.start {"context": { "serviceTag": "" }} + // speech.endDetected { } + // speech.phrase { "RecognitionStatus": "Error", "Offset": 0, "Duration": 0 } + + const s: sdk.SpeechConfig = BuildSpeechConfig(); + objsToClose.push(s); + + const blob: Blob[] = []; + const f: File = new File(blob, "file.wav"); + + const config: sdk.AudioConfig = sdk.AudioConfig.fromWavFileInput(f); + + const r: sdk.SpeechRecognizer = new sdk.SpeechRecognizer(s, config); + objsToClose.push(r); + expect(r).not.toBeUndefined(); + expect(r instanceof sdk.Recognizer); + let oneCalled: boolean = false; + + r.canceled = (o: sdk.Recognizer, e: sdk.SpeechRecognitionCanceledEventArgs): void => { + try { + expect(sdk.CancellationReason[e.reason]).toEqual(sdk.CancellationReason[sdk.CancellationReason.Error]); + + if (true === oneCalled) { + done(); + } else { + oneCalled = true; + } + } catch (error) { + done.fail(error); + } + }; + + r.recognizeOnceAsync( + (p2: sdk.SpeechRecognitionResult) => { + expect(p2.reason).toEqual(sdk.ResultReason.Canceled); + const cancelDetails: sdk.CancellationDetails = sdk.CancellationDetails.fromResult(p2); + expect(sdk.CancellationReason[cancelDetails.reason]).toEqual(sdk.CancellationReason[sdk.CancellationReason.Error]); + + if (true === oneCalled) { + done(); + } else { + oneCalled = true; + } + }, + (error: string) => { + done.fail(error); + }); + }); + + test("PullStreamSendHalfTheFile", (done: jest.DoneCallback) => { + // tslint:disable-next-line:no-console + console.info("Name: PullStreamSendHalfTheFile"); + const s: sdk.SpeechConfig = BuildSpeechConfig(); + objsToClose.push(s); + + const fileBuffer: ArrayBuffer = WaveFileAudioInput.LoadArrayFromFile(Settings.WaveFile); + + let bytesSent: number = 0; + let p: sdk.PullAudioInputStream; + + p = sdk.AudioInputStream.createPullStream( + { + close: () => { return; }, + read: (buffer: ArrayBuffer): number => { + const copyArray: Uint8Array = new Uint8Array(buffer); + const start: number = bytesSent; + const end: number = buffer.byteLength > (fileBuffer.byteLength - bytesSent) ? (fileBuffer.byteLength - 1) : (bytesSent + buffer.byteLength - 1); + copyArray.set(new Uint8Array(fileBuffer.slice(start, end))); + bytesSent += (end - start) + 1; + + if (bytesSent > (fileBuffer.byteLength / 2)) { + p.close(); + } + + return (end - start) + 1; + }, + }); + const config: sdk.AudioConfig = sdk.AudioConfig.fromStreamInput(p); + + const r: sdk.SpeechRecognizer = new sdk.SpeechRecognizer(s, config); + objsToClose.push(r); + expect(r).not.toBeUndefined(); + expect(r instanceof sdk.Recognizer); + + r.recognizeOnceAsync( + (p2: sdk.SpeechRecognitionResult) => { + const res: sdk.SpeechRecognitionResult = p2; + + expect(res).not.toBeUndefined(); + expect(sdk.ResultReason[res.reason]).toEqual(sdk.ResultReason[sdk.ResultReason.RecognizedSpeech]); + expect(res.text).toEqual("What's the weather?"); + + done(); + }, + (error: string) => { + done.fail(error); + }); + }); + + test("burst of silence", (done: jest.DoneCallback) => { + // tslint:disable-next-line:no-console + console.info("Name: burst of silence"); + const s: sdk.SpeechConfig = BuildSpeechConfig(); + objsToClose.push(s); + + const f: ArrayBuffer = WaveFileAudioInput.LoadArrayFromFile(Settings.WaveFile); + const p: sdk.PushAudioInputStream = sdk.AudioInputStream.createPushStream(); + const config: sdk.AudioConfig = sdk.AudioConfig.fromStreamInput(p); + + const emptyBuffer: Uint8Array = new Uint8Array(1 * 1024); + p.write(emptyBuffer.buffer); + p.close(); + + const r: sdk.SpeechRecognizer = new sdk.SpeechRecognizer(s, config); + objsToClose.push(r); + expect(r).not.toBeUndefined(); + expect(r instanceof sdk.Recognizer); + + r.canceled = (o: sdk.Recognizer, e: sdk.SpeechRecognitionCanceledEventArgs) => { + try { + expect(e.errorDetails).toBeUndefined(); + expect(sdk.CancellationErrorCode[e.errorCode]).toEqual(sdk.CancellationErrorCode[sdk.CancellationErrorCode.NoError]); + expect(sdk.CancellationReason[e.reason]).toEqual(sdk.CancellationReason[sdk.CancellationReason.EndOfStream]); + } catch (error) { + done.fail(error); + } + }; + r.recognizeOnceAsync( + (p2: sdk.SpeechRecognitionResult) => { + const res: sdk.SpeechRecognitionResult = p2; + + expect(res).not.toBeUndefined(); + expect(res.reason).toEqual(sdk.ResultReason.NoMatch); + const nmd: sdk.NoMatchDetails = sdk.NoMatchDetails.fromResult(res); + expect(nmd.reason).toEqual(sdk.NoMatchReason.InitialSilenceTimeout); + + done(); + }, + (error: string) => { + done.fail(error); + }); + }); + + test("RecognizeOnceAsync is async", (done: jest.DoneCallback) => { + // tslint:disable-next-line:no-console + console.info("Name: ecognizeOnceAsync is async"); + const s: sdk.SpeechConfig = BuildSpeechConfig(); + objsToClose.push(s); + + s.speechRecognitionLanguage = "en-US"; + + const f: File = WaveFileAudioInput.LoadFile(Settings.WaveFile); + const config: sdk.AudioConfig = sdk.AudioConfig.fromWavFileInput(f); + + const r: sdk.SpeechRecognizer = new sdk.SpeechRecognizer(s, config); + objsToClose.push(r); + expect(r).not.toBeUndefined(); + expect(r instanceof sdk.Recognizer); + + let postCall: boolean = false; + let resultSeen: boolean = false; + r.recognized = (o: sdk.Recognizer, e: sdk.SpeechRecognitionEventArgs) => { + WaitForCondition(() => postCall, () => { + resultSeen = true; + try { + expect(e.result.errorDetails).toBeUndefined(); + expect(e.result.reason).toEqual(sdk.ResultReason.RecognizedSpeech); + expect(e.result.text).toEqual(Settings.WaveFileText); + done(); + } catch (error) { + done.fail(error); + } + }); + }; + + r.recognizeOnceAsync(); + + expect(resultSeen).toEqual(false); + postCall = true; + }); + + test("InitialSilenceTimeout Continous", (done: jest.DoneCallback) => { + // tslint:disable-next-line:no-console + console.info("Name: InitialSilenceTimeout Continous"); + const s: sdk.SpeechConfig = BuildSpeechConfig(); + objsToClose.push(s); + + let p: sdk.PullAudioInputStream; + + p = sdk.AudioInputStream.createPullStream( + { + close: () => { return; }, + read: (buffer: ArrayBuffer): number => { + return buffer.byteLength; + }, + }); + + const config: sdk.AudioConfig = sdk.AudioConfig.fromStreamInput(p); + + const r: sdk.SpeechRecognizer = new sdk.SpeechRecognizer(s, config); + objsToClose.push(r); + expect(r).not.toBeUndefined(); + expect(r instanceof sdk.Recognizer); + + r.canceled = (o: sdk.Recognizer, e: sdk.SpeechRecognitionCanceledEventArgs) => { + // Since the pull stream above will always return an empty array, there should be + // no other reason besides an error for cancel to hit. + done.fail(e.errorDetails); + }; + + let passed: boolean = false; + + r.recognized = (o: sdk.Recognizer, e: sdk.SpeechRecognitionEventArgs) => { + + const res: sdk.SpeechRecognitionResult = e.result; + expect(res).not.toBeUndefined(); + expect(sdk.ResultReason.NoMatch).toEqual(res.reason); + expect(res.text).toBeUndefined(); + + const nmd: sdk.NoMatchDetails = sdk.NoMatchDetails.fromResult(res); + expect(nmd.reason).toEqual(sdk.NoMatchReason.InitialSilenceTimeout); + passed = true; + }; + + /* tslint:disable:no-empty */ + r.startContinuousRecognitionAsync(() => { + }, + (error: string) => { + done.fail(error); + }); + + WaitForCondition(() => passed, () => { + r.stopContinuousRecognitionAsync(() => { + done(); + }, (error: string) => done.fail(error)); + }); + + }, 30000); + + test("Audio Config is optional", () => { + // tslint:disable-next-line:no-console + console.info("Name: Audio Config is optional"); + const s: sdk.SpeechConfig = BuildSpeechConfig(); + objsToClose.push(s); + + const r: sdk.SpeechRecognizer = new sdk.SpeechRecognizer(s); + objsToClose.push(r); + expect(r instanceof sdk.Recognizer).toEqual(true); + }); + + test("Default mic is used when audio config is not specified.", () => { + // tslint:disable-next-line:no-console + console.info("Name: Default mic is used when audio config is not specified."); + const s: sdk.SpeechConfig = BuildSpeechConfig(); + objsToClose.push(s); + + const r: sdk.SpeechRecognizer = new sdk.SpeechRecognizer(s); + objsToClose.push(r); + + expect(r instanceof sdk.Recognizer).toEqual(true); + // Node.js doesn't have a microphone natively. So we'll take the specific message that indicates that microphone init failed as evidence it was attempted. + r.recognizeOnceAsync(() => fail("RecognizeOnceAsync returned success when it should have failed"), + (error: string): void => { + expect(error).toEqual("Error: Browser does not support Web Audio API (AudioContext is not available)."); + }); + + r.startContinuousRecognitionAsync(() => fail("startContinuousRecognitionAsync returned success when it should have failed"), + (error: string): void => { + expect(error).toEqual("Error: Browser does not support Web Audio API (AudioContext is not available)."); + }); + }); + + test("Using disposed recognizer invokes error callbacks.", () => { + // tslint:disable-next-line:no-console + console.info("Name: Using disposed recognizer invokes error callbacks."); + const s: sdk.SpeechConfig = BuildSpeechConfig(); + objsToClose.push(s); + + const r: sdk.SpeechRecognizer = new sdk.SpeechRecognizer(s); + expect(r instanceof sdk.Recognizer).toEqual(true); + + r.close(); + + r.recognizeOnceAsync(() => fail("RecognizeOnceAsync on closed recognizer called success callback"), + (error: string): void => { + expect(error).toEqual("Error: the object is already disposed"); + }); + + r.startContinuousRecognitionAsync(() => fail("startContinuousRecognitionAsync on closed recognizer called success callback"), + (error: string): void => { + expect(error).toEqual("Error: the object is already disposed"); + }); + + r.stopContinuousRecognitionAsync(() => fail("stopContinuousRecognitionAsync on closed recognizer called success callback"), + (error: string): void => { + expect(error).toEqual("Error: the object is already disposed"); + }); + }); + + test.skip("Endpoint URL Test", (done: jest.DoneCallback) => { + let uri: string; + + Events.instance.attachListener({ + onEvent: (event: PlatformEvent) => { + if (event instanceof ConnectionStartEvent) { + const connectionEvent: ConnectionStartEvent = event as ConnectionStartEvent; + uri = connectionEvent.uri; + } + }, + }); + + const s: sdk.SpeechConfig = BuildSpeechConfig(); + objsToClose.push(s); + + s.endpointId = Settings.SpeechTestEndpointId; + + const r: sdk.SpeechRecognizer = BuildRecognizerFromWaveFile(s); + objsToClose.push(r); + + r.recognizeOnceAsync( + (p2: sdk.SpeechRecognitionResult) => { + try { + const res: sdk.SpeechRecognitionResult = p2; + expect(res).not.toBeUndefined(); + expect(res.errorDetails).toBeUndefined(); + expect(sdk.ResultReason[res.reason]).toEqual(sdk.ResultReason[sdk.ResultReason.RecognizedSpeech]); + expect("What's the weather like?").toEqual(res.text); + expect(uri).not.toBeUndefined(); + expect(uri.search(QueryParameterNames.DeploymentIdParamName + "=" + Settings.SpeechTestEndpointId)).not.toEqual(-1); + expect(uri.search(QueryParameterNames.LanguageParamName)).toEqual(-1); + + done(); + } catch (error) { + done.fail(error); + } + }, + (error: string) => { + done.fail(error); + }); + }); + + test("Endpoint URL With Parameter Test", (done: jest.DoneCallback) => { + // tslint:disable-next-line:no-console + console.info("Name: Endpoint URL With Parameter Test"); + let uri: string; + + Events.instance.attachListener({ + onEvent: (event: PlatformEvent) => { + if (event instanceof ConnectionStartEvent) { + const connectionEvent: ConnectionStartEvent = event as ConnectionStartEvent; + uri = connectionEvent.uri; + } + }, + }); + + const s: sdk.SpeechConfig = sdk.SpeechConfig.fromEndpoint(new URL("wss://fake.host.name?somequeryParam=Value"), "fakekey"); + objsToClose.push(s); + + const r: sdk.SpeechRecognizer = BuildRecognizerFromWaveFile(s); + objsToClose.push(r); + + r.recognizeOnceAsync( + (p2: sdk.SpeechRecognitionResult) => { + try { + expect(uri).not.toBeUndefined(); + // Make sure there's only a single ? in the URL. + expect(uri.indexOf("?")).toEqual(uri.lastIndexOf("?")); + + expect(p2.errorDetails).not.toBeUndefined(); + expect(sdk.ResultReason[p2.reason]).toEqual(sdk.ResultReason[sdk.ResultReason.Canceled]); + + const cancelDetails: sdk.CancellationDetails = sdk.CancellationDetails.fromResult(p2); + expect(sdk.CancellationReason[cancelDetails.reason]).toEqual(sdk.CancellationReason[sdk.CancellationReason.Error]); + expect(sdk.CancellationErrorCode[cancelDetails.ErrorCode]).toEqual(sdk.CancellationErrorCode[sdk.CancellationErrorCode.ConnectionFailure]); + done(); + } catch (error) { + done.fail(error); + } + }); + }); + + test("Connection Errors Propogate Async", (done: jest.DoneCallback) => { + // tslint:disable-next-line:no-console + console.info("Name: Connection Errors Propogate Async"); + const s: sdk.SpeechConfig = sdk.SpeechConfig.fromSubscription("badKey", Settings.SpeechRegion); + objsToClose.push(s); + + const r: sdk.SpeechRecognizer = BuildRecognizerFromWaveFile(s); + objsToClose.push(r); + + r.canceled = (o: sdk.Recognizer, e: sdk.SpeechRecognitionCanceledEventArgs) => { + try { + expect(sdk.CancellationReason[e.reason]).toEqual(sdk.CancellationReason[sdk.CancellationReason.Error]); + expect(sdk.CancellationErrorCode[e.errorCode]).toEqual(sdk.CancellationErrorCode[sdk.CancellationErrorCode.ConnectionFailure]); + done(); + } catch (error) { + done.fail(error); + } + }; + + r.startContinuousRecognitionAsync(); + + }); + + test("Connection Errors Propogate Sync", (done: jest.DoneCallback) => { + // tslint:disable-next-line:no-console + console.info("Name: Connection Errors Propogate Sync"); + const s: sdk.SpeechConfig = sdk.SpeechConfig.fromSubscription("badKey", Settings.SpeechRegion); + objsToClose.push(s); + + const r: sdk.SpeechRecognizer = BuildRecognizerFromWaveFile(s); + objsToClose.push(r); + + let doneCount: number = 0; + r.canceled = (o: sdk.Recognizer, e: sdk.SpeechRecognitionCanceledEventArgs) => { + try { + expect(sdk.CancellationReason[e.reason]).toEqual(sdk.CancellationReason[sdk.CancellationReason.Error]); + expect(sdk.CancellationErrorCode[e.errorCode]).toEqual(sdk.CancellationErrorCode[sdk.CancellationErrorCode.ConnectionFailure]); + expect(e.errorDetails).toContain("1006"); + doneCount++; + } catch (error) { + done.fail(error); + } + }; + + r.recognizeOnceAsync((result: sdk.SpeechRecognitionResult) => { + try { + const e: sdk.CancellationDetails = sdk.CancellationDetails.fromResult(result); + expect(sdk.CancellationReason[e.reason]).toEqual(sdk.CancellationReason[sdk.CancellationReason.Error]); + expect(sdk.CancellationErrorCode[e.ErrorCode]).toEqual(sdk.CancellationErrorCode[sdk.CancellationErrorCode.ConnectionFailure]); + expect(e.errorDetails).toContain("1006"); + doneCount++; + } catch (error) { + done.fail(error); + } + }); + + WaitForCondition(() => (doneCount === 2), done); + + }); + + test("RecognizeOnce Bad Language", (done: jest.DoneCallback) => { + // tslint:disable-next-line:no-console + console.info("Name: RecognizeOnce Bad Language"); + const s: sdk.SpeechConfig = BuildSpeechConfig(); + objsToClose.push(s); + s.speechRecognitionLanguage = "BadLanguage"; + + const r: sdk.SpeechRecognizer = BuildRecognizerFromWaveFile(s); + objsToClose.push(r); + let doneCount: number = 0; + + r.canceled = (o: sdk.Recognizer, e: sdk.SpeechRecognitionCanceledEventArgs) => { + try { + expect(sdk.CancellationReason[e.reason]).toEqual(sdk.CancellationReason[sdk.CancellationReason.Error]); + expect(sdk.CancellationErrorCode[e.errorCode]).toEqual(sdk.CancellationErrorCode[sdk.CancellationErrorCode.ConnectionFailure]); + expect(e.errorDetails).toContain("1006"); + doneCount++; + } catch (error) { + done.fail(error); + } + }; + + r.recognizeOnceAsync((result: sdk.SpeechRecognitionResult) => { + try { + const e: sdk.CancellationDetails = sdk.CancellationDetails.fromResult(result); + expect(sdk.CancellationReason[e.reason]).toEqual(sdk.CancellationReason[sdk.CancellationReason.Error]); + expect(sdk.CancellationErrorCode[e.ErrorCode]).toEqual(sdk.CancellationErrorCode[sdk.CancellationErrorCode.ConnectionFailure]); + expect(e.errorDetails).toContain("1006"); + doneCount++; + } catch (error) { + done.fail(error); + } + WaitForCondition(() => (doneCount === 2), done); + }); + }); + + test("Silence After Speech", (done: jest.DoneCallback) => { + // tslint:disable-next-line:no-console + console.info("Name: Silence After Speech"); + // Pump valid speech and then silence until at least one speech end cycle hits. + const p: sdk.PushAudioInputStream = sdk.AudioInputStream.createPushStream(); + const bigFileBuffer: Uint8Array = new Uint8Array(32 * 1024 * 30); // ~30 seconds. + const config: sdk.AudioConfig = sdk.AudioConfig.fromStreamInput(p); + const s: sdk.SpeechConfig = BuildSpeechConfig(); + objsToClose.push(s); + + p.write(WaveFileAudioInput.LoadArrayFromFile(Settings.WaveFile)); + p.write(bigFileBuffer.buffer); + p.close(); + + const r: sdk.SpeechRecognizer = new sdk.SpeechRecognizer(s, config); + objsToClose.push(r); + + let speechRecognized: boolean = false; + let noMatchCount: number = 0; + let speechEnded: number = 0; + let canceled: boolean = false; + let inTurn: boolean = false; + + r.recognized = (o: sdk.Recognizer, e: sdk.SpeechRecognitionEventArgs) => { + try { + if (e.result.reason === sdk.ResultReason.RecognizedSpeech) { + expect(speechRecognized).toEqual(false); + speechRecognized = true; + expect(sdk.ResultReason[e.result.reason]).toEqual(sdk.ResultReason[sdk.ResultReason.RecognizedSpeech]); + expect(e.result.text).toEqual("What's the weather like?"); + } else if (e.result.reason === sdk.ResultReason.NoMatch) { + expect(speechRecognized).toEqual(true); + noMatchCount++; + } + } catch (error) { + done.fail(error); + } + }; + + r.canceled = (o: sdk.Recognizer, e: sdk.SpeechRecognitionCanceledEventArgs): void => { + try { + expect(e.errorDetails).toBeUndefined(); + expect(e.reason).toEqual(sdk.CancellationReason.EndOfStream); + canceled = true; + } catch (error) { + done.fail(error); + } + }; + + r.sessionStarted = ((s: sdk.Recognizer, e: sdk.SessionEventArgs): void => { + inTurn = true; + }); + + r.sessionStopped = ((s: sdk.Recognizer, e: sdk.SessionEventArgs): void => { + inTurn = false; + }); + + r.speechEndDetected = (o: sdk.Recognizer, e: sdk.RecognitionEventArgs): void => { + speechEnded++; + }; + + r.startContinuousRecognitionAsync(() => { + WaitForCondition(() => (canceled && !inTurn), () => { + r.stopContinuousRecognitionAsync(() => { + try { + expect(speechEnded).toEqual(noMatchCount); + expect(noMatchCount).toEqual(2); + done(); + } catch (error) { + done.fail(error); + } + }, (error: string) => { + done.fail(error); + }); + }); + }, + (err: string) => { + done.fail(err); + }); + }, 30000); + + test("Silence Then Speech", (done: jest.DoneCallback) => { + // tslint:disable-next-line:no-console + console.info("Name: Silence Then Speech"); + // Pump valid speech and then silence until at least one speech end cycle hits. + const p: sdk.PushAudioInputStream = sdk.AudioInputStream.createPushStream(); + const bigFileBuffer: Uint8Array = new Uint8Array(32 * 1024 * 30); // ~30 seconds. + const config: sdk.AudioConfig = sdk.AudioConfig.fromStreamInput(p); + const s: sdk.SpeechConfig = BuildSpeechConfig(); + objsToClose.push(s); + + p.write(bigFileBuffer.buffer); + p.write(WaveFileAudioInput.LoadArrayFromFile(Settings.WaveFile)); + p.close(); + + const r: sdk.SpeechRecognizer = new sdk.SpeechRecognizer(s, config); + objsToClose.push(r); + + let speechRecognized: boolean = false; + let noMatchCount: number = 0; + let speechEnded: number = 0; + let canceled: boolean = false; + let inTurn: boolean = false; + + r.recognized = (o: sdk.Recognizer, e: sdk.SpeechRecognitionEventArgs) => { + try { + if (e.result.reason === sdk.ResultReason.RecognizedSpeech) { + expect(speechRecognized).toEqual(false); + expect(noMatchCount).toBeGreaterThanOrEqual(1); + speechRecognized = true; + expect(sdk.ResultReason[e.result.reason]).toEqual(sdk.ResultReason[sdk.ResultReason.RecognizedSpeech]); + expect(e.result.text).toEqual("What's the weather like?"); + } else if (e.result.reason === sdk.ResultReason.NoMatch) { + expect(speechRecognized).toEqual(false); + noMatchCount++; + } + } catch (error) { + done.fail(error); + } + }; + + r.canceled = (o: sdk.Recognizer, e: sdk.SpeechRecognitionCanceledEventArgs): void => { + try { + expect(e.errorDetails).toBeUndefined(); + expect(e.reason).toEqual(sdk.CancellationReason.EndOfStream); + canceled = true; + } catch (error) { + done.fail(error); + } + }; + + r.sessionStarted = ((s: sdk.Recognizer, e: sdk.SessionEventArgs): void => { + inTurn = true; + }); + + r.sessionStopped = ((s: sdk.Recognizer, e: sdk.SessionEventArgs): void => { + inTurn = false; + }); + + r.speechEndDetected = (o: sdk.Recognizer, e: sdk.RecognitionEventArgs): void => { + speechEnded++; + }; + + r.startContinuousRecognitionAsync(() => { + WaitForCondition(() => (canceled && !inTurn), () => { + r.stopContinuousRecognitionAsync(() => { + try { + expect(speechEnded).toEqual(noMatchCount + 1); + expect(noMatchCount).toEqual(2); + done(); + } catch (error) { + done.fail(error); + } + }, (error: string) => { + done.fail(error); + }); + }); + }, + (err: string) => { + done.fail(err); + }); + }, 35000); + + // Tests client reconnect after speech timeouts. + test.skip("Reconnect After timeout", (done: jest.DoneCallback) => { + // tslint:disable-next-line:no-console + console.info("Name: Reconnect After timeout"); + // Pump valid speech and then silence until at least one speech end cycle hits. + const fileBuffer: ArrayBuffer = WaveFileAudioInput.LoadArrayFromFile(Settings.WaveFile); + + let p: sdk.PullAudioInputStream; + const bigFileBuffer: Uint8Array = new Uint8Array(32 * 1024 * 30); // ~30 seconds. + let s: sdk.SpeechConfig; + if (undefined === Settings.SpeechTimeoutEndpoint || undefined === Settings.SpeechTimeoutKey) { + // tslint:disable-next-line:no-console + console.warn("Running timeout test against production, this will be very slow..."); + s = BuildSpeechConfig(); + } else { + s = sdk.SpeechConfig.fromEndpoint(new URL(Settings.SpeechTimeoutEndpoint), Settings.SpeechTimeoutKey); + } + objsToClose.push(s); + + let pumpSilence: boolean = false; + let bytesSent: number = 0; + const targetLoops: number = 250; + + // Pump the audio from the wave file specified with 1 second silence between iterations indefinetly. + p = sdk.AudioInputStream.createPullStream( + { + close: () => { return; }, + read: (buffer: ArrayBuffer): number => { + if (pumpSilence) { + bytesSent += buffer.byteLength; + if (bytesSent >= 8000) { + bytesSent = 0; + pumpSilence = false; + } + return buffer.byteLength; + } else { + const copyArray: Uint8Array = new Uint8Array(buffer); + const start: number = bytesSent; + const end: number = buffer.byteLength > (fileBuffer.byteLength - bytesSent) ? (fileBuffer.byteLength - 1) : (bytesSent + buffer.byteLength - 1); + copyArray.set(new Uint8Array(fileBuffer.slice(start, end))); + const readyToSend: number = (end - start) + 1; + bytesSent += readyToSend; + + if (readyToSend < buffer.byteLength) { + bytesSent = 0; + pumpSilence = true; + } + + return readyToSend; + } + + }, + }); + + const config: sdk.AudioConfig = sdk.AudioConfig.fromStreamInput(p); + + const r: sdk.SpeechRecognizer = new sdk.SpeechRecognizer(s, config); + objsToClose.push(r); + + let speechEnded: number = 0; + let lastOffset: number = 0; + let recogCount: number = 0; + let canceled: boolean = false; + let inTurn: boolean = false; + + r.recognized = (o: sdk.Recognizer, e: sdk.SpeechRecognitionEventArgs) => { + try { + expect(sdk.ResultReason[e.result.reason]).toEqual(sdk.ResultReason[sdk.ResultReason.RecognizedSpeech]); + expect(e.offset).toBeGreaterThanOrEqual(lastOffset); + lastOffset = e.offset; + + // If there is silence exactly at the moment of disconnect, an extra speech.phrase with text ="" is returned just before the + // connection is disconnected. + if ("" !== e.result.text) { + expect(e.result.text).toEqual(Settings.WaveFileText); + } + if (recogCount++ > targetLoops) { + p.close(); + } + + } catch (error) { + done.fail(error); + } + }; + + r.canceled = (o: sdk.Recognizer, e: sdk.SpeechRecognitionCanceledEventArgs): void => { + try { + expect(e.errorDetails).toBeUndefined(); + expect(sdk.CancellationReason[e.reason]).toEqual(sdk.CancellationReason[sdk.CancellationReason.EndOfStream]); + canceled = true; + } catch (error) { + done.fail(error); + } + }; + + r.sessionStarted = ((s: sdk.Recognizer, e: sdk.SessionEventArgs): void => { + inTurn = true; + }); + + r.sessionStopped = ((s: sdk.Recognizer, e: sdk.SessionEventArgs): void => { + inTurn = false; + }); + + r.speechEndDetected = (o: sdk.Recognizer, e: sdk.RecognitionEventArgs): void => { + speechEnded++; + }; + + r.startContinuousRecognitionAsync(() => { + WaitForCondition(() => (canceled && !inTurn), () => { + r.stopContinuousRecognitionAsync(() => { + try { + expect(speechEnded).toEqual(1); + done(); + } catch (error) { + done.fail(error); + } + }, (error: string) => { + done.fail(error); + }); + }); + }, + (err: string) => { + done.fail(err); + }); + }, 35000); +}); + +test("Push Stream Async", (done: jest.DoneCallback) => { + // tslint:disable-next-line:no-console + console.info("Name: Push Stream Async"); + + const s: sdk.SpeechConfig = BuildSpeechConfig(); + objsToClose.push(s); + + const p: sdk.PushAudioInputStream = sdk.AudioInputStream.createPushStream(); + const config: sdk.AudioConfig = sdk.AudioConfig.fromStreamInput(p); + + fs.createReadStream(Settings.WaveFile).on("data", (buffer: Buffer) => { + p.write(buffer.buffer); + }).on("end", () => { + p.close(); + }); + + const r: sdk.SpeechRecognizer = new sdk.SpeechRecognizer(s, config); + objsToClose.push(r); + + expect(r).not.toBeUndefined(); + expect(r instanceof sdk.Recognizer); + + r.canceled = (r: sdk.Recognizer, e: sdk.SpeechRecognitionCanceledEventArgs) => { + done.fail(e.errorDetails); + }; + + r.recognizeOnceAsync( + (p2: sdk.SpeechRecognitionResult) => { + const res: sdk.SpeechRecognitionResult = p2; + + expect(res).not.toBeUndefined(); + expect(sdk.ResultReason[res.reason]).toEqual(sdk.ResultReason[sdk.ResultReason.RecognizedSpeech]); + expect(res.text).toEqual("What's the weather like?"); + done(); + }, + (error: string) => { + done.fail(error); + }); +}); + +test("Bad DataType for PushStreams results in error", (done: jest.DoneCallback) => { + // tslint:disable-next-line:no-console + console.info("Name: Bad DataType for PushStreams results in error"); + + const s: sdk.SpeechConfig = BuildSpeechConfig(); + objsToClose.push(s); + + const p: sdk.PushAudioInputStream = sdk.AudioInputStream.createPushStream(); + const config: sdk.AudioConfig = sdk.AudioConfig.fromStreamInput(p); + + // Wrong data type for ReadStreams + fs.createReadStream(Settings.WaveFile).on("data", (buffer: ArrayBuffer) => { + p.write(buffer); + }).on("end", () => { + p.close(); + }); + + const r: sdk.SpeechRecognizer = new sdk.SpeechRecognizer(s, config); + objsToClose.push(r); + + expect(r).not.toBeUndefined(); + expect(r instanceof sdk.Recognizer); + + r.canceled = (r: sdk.Recognizer, e: sdk.SpeechRecognitionCanceledEventArgs) => { + try { + expect(e.errorDetails).not.toBeUndefined(); + expect(sdk.CancellationReason[e.reason]).toEqual(sdk.CancellationReason[sdk.CancellationReason.Error]); + expect(sdk.CancellationErrorCode[e.errorCode]).toEqual(sdk.CancellationErrorCode[sdk.CancellationErrorCode.RuntimeError]); + } catch (error) { + done.fail(error); + } + }; + + r.recognizeOnceAsync( + (p2: sdk.SpeechRecognitionResult) => { + const res: sdk.SpeechRecognitionResult = p2; + try { + expect(res).not.toBeUndefined(); + expect(res.errorDetails).not.toBeUndefined(); + expect(sdk.ResultReason[res.reason]).toEqual(sdk.ResultReason[sdk.ResultReason.Canceled]); + done(); + } catch (error) { + done.fail(error); + } + }, + (error: string) => { + done.fail(error); + }); +}); diff --git a/tests/TranslationRecognizerTests.ts b/tests/TranslationRecognizerTests.ts new file mode 100644 index 00000000..0c62fe4b --- /dev/null +++ b/tests/TranslationRecognizerTests.ts @@ -0,0 +1,1394 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +import * as sdk from "../microsoft.cognitiveservices.speech.sdk"; +import { ConsoleLoggingListener, WebsocketMessageAdapter } from "../src/common.browser/Exports"; +import { Events, EventType, ObjectDisposedError } from "../src/common/Exports"; + +import { ByteBufferAudioFile } from "./ByteBufferAudioFile"; +import { Settings } from "./Settings"; +import { default as WaitForCondition } from "./Utilities"; +import { WaveFileAudioInput } from "./WaveFileAudioInputStream"; + +import * as fs from "fs"; + +let objsToClose: any[]; + +beforeAll(() => { + // Override inputs, if necessary + Settings.LoadSettings(); + Events.instance.attachListener(new ConsoleLoggingListener(EventType.Debug)); +}); + +// Test cases are run linerally, still looking for a way to get the test name to print that doesn't mean changing each test. +beforeEach(() => { + objsToClose = []; + // tslint:disable-next-line:no-console + console.info("---------------------------------------Starting test case-----------------------------------"); + // tslint:disable-next-line:no-console + console.info("Sart Time: " + new Date(Date.now()).toLocaleString()); +}); + +afterEach(() => { + // tslint:disable-next-line:no-console + console.info("End Time: " + new Date(Date.now()).toLocaleString()); + objsToClose.forEach((value: any, index: number, array: any[]) => { + if (typeof value.close === "function") { + value.close(); + } + }); +}); + +const BuildRecognizerFromWaveFile: (speechConfig?: sdk.SpeechTranslationConfig) => sdk.TranslationRecognizer = (speechConfig?: sdk.SpeechTranslationConfig): sdk.TranslationRecognizer => { + + let s: sdk.SpeechTranslationConfig = speechConfig; + if (s === undefined) { + s = BuildSpeechConfig(); + // Since we're not going to return it, mark it for closure. + objsToClose.push(s); + } + + const f: File = WaveFileAudioInput.LoadFile(Settings.WaveFile); + const config: sdk.AudioConfig = sdk.AudioConfig.fromWavFileInput(f); + + const language: string = Settings.WaveFileLanguage; + if (s.getProperty(sdk.PropertyId[sdk.PropertyId.SpeechServiceConnection_RecoLanguage]) === undefined) { + s.speechRecognitionLanguage = language; + } + s.addTargetLanguage("de-DE"); + + const r: sdk.TranslationRecognizer = new sdk.TranslationRecognizer(s, config); + expect(r).not.toBeUndefined(); + + return r; +}; + +const BuildSpeechConfig: () => sdk.SpeechTranslationConfig = (): sdk.SpeechTranslationConfig => { + const s: sdk.SpeechTranslationConfig = sdk.SpeechTranslationConfig.fromSubscription(Settings.SpeechSubscriptionKey, Settings.SpeechRegion); + expect(s).not.toBeUndefined(); + return s; +}; + +const FIRST_EVENT_ID: number = 1; +const Recognizing: string = "Recognizing"; +const Recognized: string = "Recognized"; +const Session: string = "Session"; +const Canceled: string = "Canceled"; + +let eventIdentifier: number; + +test("TranslationRecognizerMicrophone", () => { + // tslint:disable-next-line:no-console + console.info("Name: TranslationRecognizerMicrophone"); + + const s: sdk.SpeechTranslationConfig = sdk.SpeechTranslationConfig.fromSubscription(Settings.SpeechSubscriptionKey, Settings.SpeechRegion); + objsToClose.push(s); + expect(s).not.toBeUndefined(); + s.addTargetLanguage("en-US"); + s.speechRecognitionLanguage = "en-US"; + + const r: sdk.TranslationRecognizer = new sdk.TranslationRecognizer(s); + objsToClose.push(r); + expect(r).not.toBeUndefined(); + expect(r instanceof sdk.Recognizer).toEqual(true); +}); + +test("TranslationRecognizerWavFile", () => { + // tslint:disable-next-line:no-console + console.info("Name: TranslationRecognizerWavFile"); + const r: sdk.TranslationRecognizer = BuildRecognizerFromWaveFile(); + objsToClose.push(r); +}); + +test("GetSourceLanguage", () => { + // tslint:disable-next-line:no-console + console.info("Name: GetSourceLanguage"); + const r: sdk.TranslationRecognizer = BuildRecognizerFromWaveFile(); + objsToClose.push(r); + expect(r.speechRecognitionLanguage).not.toBeUndefined(); + expect(r.speechRecognitionLanguage).not.toBeNull(); + expect(r.speechRecognitionLanguage).toEqual(r.properties.getProperty(sdk.PropertyId[sdk.PropertyId.SpeechServiceConnection_RecoLanguage])); +}); + +test("GetTargetLanguages", () => { + // tslint:disable-next-line:no-console + console.info("Name: GetTargetLanguages"); + const r: sdk.TranslationRecognizer = BuildRecognizerFromWaveFile(); + objsToClose.push(r); + + expect(r.targetLanguages).not.toBeUndefined(); + expect(r.targetLanguages).not.toBeNull(); + expect(r.targetLanguages.length).toEqual(1); + expect(r.targetLanguages[0]).toEqual(r.properties.getProperty(sdk.PropertyId[sdk.PropertyId.SpeechServiceConnection_TranslationToLanguages])); +}); + +test.skip("GetOutputVoiceNameNoSetting", () => { + // tslint:disable-next-line:no-console + console.info("Name: GetOutputVoiceNameNoSetting"); + const r: sdk.TranslationRecognizer = BuildRecognizerFromWaveFile(); + objsToClose.push(r); + expect(r.voiceName).not.toBeUndefined(); +}); + +test("GetOutputVoiceName", () => { + // tslint:disable-next-line:no-console + console.info("Name: GetOutputVoiceName"); + const s: sdk.SpeechTranslationConfig = BuildSpeechConfig(); + objsToClose.push(s); + + const voice: string = "Microsoft Server Speech Text to Speech Voice (de-DE, Hedda)"; + s.voiceName = voice; + + const r: sdk.TranslationRecognizer = BuildRecognizerFromWaveFile(s); + objsToClose.push(r); + + expect(r.voiceName).toEqual(voice); +}); + +test("GetParameters", () => { + // tslint:disable-next-line:no-console + console.info("Name: GetParameters"); + const r: sdk.TranslationRecognizer = BuildRecognizerFromWaveFile(); + objsToClose.push(r); + + expect(r.properties).not.toBeUndefined(); + expect(r.speechRecognitionLanguage).toEqual(r.properties.getProperty(sdk.PropertyId.SpeechServiceConnection_RecoLanguage, "")); + + // TODO this cannot be true, right? comparing an array with a string parameter??? + expect(r.targetLanguages.length).toEqual(1); + expect(r.targetLanguages[0]).toEqual(r.properties.getProperty(sdk.PropertyId.SpeechServiceConnection_TranslationToLanguages)); +}); + +describe.each([true, false])("Service based tests", (forceNodeWebSocket: boolean) => { + + beforeEach(() => { + // tslint:disable-next-line:no-console + console.info("forceNodeWebSocket: " + forceNodeWebSocket); + WebsocketMessageAdapter.forceNpmWebSocket = forceNodeWebSocket; + }); + afterAll(() => { + WebsocketMessageAdapter.forceNpmWebSocket = false; + }); + + test("RecognizeOnceAsync1", (done: jest.DoneCallback) => { + // tslint:disable-next-line:no-console + console.info("Name: RecognizeOnceAsync1"); + const r: sdk.TranslationRecognizer = BuildRecognizerFromWaveFile(); + objsToClose.push(r); + + r.recognizeOnceAsync( + (res: sdk.TranslationRecognitionResult) => { + expect(res).not.toBeUndefined(); + expect(res.errorDetails).toBeUndefined(); + expect(sdk.ResultReason[res.reason]).toEqual(sdk.ResultReason[sdk.ResultReason.TranslatedSpeech]); + expect(res.translations.get("de", undefined) !== undefined).toEqual(true); + expect("Wie ist das Wetter?").toEqual(res.translations.get("de", "")); + expect(res.text).toEqual("What's the weather like?"); + + done(); + }, + (error: string) => { + done.fail(error); + }); + }); + + test("Translate Multiple Targets", (done: jest.DoneCallback) => { + // tslint:disable-next-line:no-console + console.info("Name: Translate Multiple Targets"); + const s: sdk.SpeechTranslationConfig = BuildSpeechConfig(); + objsToClose.push(s); + s.addTargetLanguage("en-US"); + + const r: sdk.TranslationRecognizer = BuildRecognizerFromWaveFile(s); + objsToClose.push(r); + + r.recognizeOnceAsync( + (res: sdk.TranslationRecognitionResult) => { + expect(res).not.toBeUndefined(); + expect(res.errorDetails).toBeUndefined(); + expect(sdk.ResultReason[res.reason]).toEqual(sdk.ResultReason[sdk.ResultReason.TranslatedSpeech]); + expect("Wie ist das Wetter?").toEqual(res.translations.get("de", "")); + expect("What's the weather like?").toEqual(res.translations.get("en", "")); + done(); + }, + (error: string) => { + done.fail(error); + }); + }); + + test("Validate Event Ordering", (done: jest.DoneCallback) => { + // tslint:disable-next-line:no-console + console.info("Name: Validate Event Ordering"); + const SpeechStartDetectedEvent = "SpeechStartDetectedEvent"; + const SpeechEndDetectedEvent = "SpeechEndDetectedEvent"; + const SessionStartedEvent = "SessionStartedEvent"; + const SessionStoppedEvent = "SessionStoppedEvent"; + + const r: sdk.TranslationRecognizer = BuildRecognizerFromWaveFile(); + objsToClose.push(r); + + const eventsMap: { [id: string]: number; } = {}; + eventIdentifier = 1; + + r.recognized = (o: sdk.Recognizer, e: sdk.TranslationRecognitionEventArgs) => { + eventsMap[Recognized] = eventIdentifier++; + }; + + r.recognizing = (o: sdk.Recognizer, e: sdk.TranslationRecognitionEventArgs) => { + const now: number = eventIdentifier++; + eventsMap[Recognizing + "-" + Date.now().toPrecision(4)] = now; + eventsMap[Recognizing] = now; + }; + + r.canceled = (o: sdk.Recognizer, e: sdk.TranslationRecognitionCanceledEventArgs) => { + eventsMap[Canceled] = eventIdentifier++; + }; + + // TODO eventType should be renamed and be a function getEventType() + r.speechStartDetected = (o: sdk.Recognizer, e: sdk.RecognitionEventArgs) => { + const now: number = eventIdentifier++; + eventsMap[SpeechStartDetectedEvent + "-" + Date.now().toPrecision(4)] = now; + eventsMap[SpeechStartDetectedEvent] = now; + }; + r.speechEndDetected = (o: sdk.Recognizer, e: sdk.RecognitionEventArgs) => { + const now: number = eventIdentifier++; + eventsMap[SpeechEndDetectedEvent + "-" + Date.now().toPrecision(4)] = now; + eventsMap[SpeechEndDetectedEvent] = now; + }; + + r.sessionStarted = (o: sdk.Recognizer, e: sdk.SessionEventArgs) => { + const now: number = eventIdentifier++; + eventsMap[Session + ":" + SessionStartedEvent + "-" + Date.now().toPrecision(4)] = now; + eventsMap[Session + ":" + SessionStartedEvent] = now; + }; + r.sessionStopped = (o: sdk.Recognizer, e: sdk.SessionEventArgs) => { + const now: number = eventIdentifier++; + eventsMap[Session + ":" + SessionStoppedEvent + "-" + Date.now().toPrecision(4)] = now; + eventsMap[Session + ":" + SessionStoppedEvent] = now; + }; + + // TODO there is no guarantee that SessionStoppedEvent comes before the recognizeOnceAsync() call returns?! + // this is why below SessionStoppedEvent checks are conditional + r.recognizeOnceAsync((res: sdk.TranslationRecognitionResult) => { + expect(res).not.toBeUndefined(); + expect(res.errorDetails).toBeUndefined(); + expect(sdk.ResultReason[res.reason]).toEqual(sdk.ResultReason[sdk.ResultReason.TranslatedSpeech]); + + expect(res.translations.get("de", "No Translation")).toEqual("Wie ist das Wetter?"); + + // session events are first and last event + const LAST_RECORDED_EVENT_ID: number = eventIdentifier; + expect(LAST_RECORDED_EVENT_ID).toBeGreaterThan(FIRST_EVENT_ID); + expect(FIRST_EVENT_ID).toEqual(eventsMap[Session + ":" + SessionStartedEvent]); + + if (Session + ":" + SessionStoppedEvent in eventsMap) { + expect(LAST_RECORDED_EVENT_ID).toEqual(eventsMap[Session + ":" + SessionStoppedEvent]); + } + + // end events come after start events. + if (Session + ":" + SessionStoppedEvent in eventsMap) { + expect(eventsMap[Session + ":" + SessionStartedEvent]).toBeLessThan(eventsMap[Session + ":" + SessionStoppedEvent]); + } + + expect((FIRST_EVENT_ID + 1)).toEqual(eventsMap[SpeechStartDetectedEvent]); + + // recognition events come after session start but before session end events + expect(eventsMap[Session + ":" + SessionStartedEvent]).toBeLessThan(eventsMap[SpeechStartDetectedEvent]); + + if (Session + ":" + SessionStoppedEvent in eventsMap) { + expect(eventsMap[SpeechEndDetectedEvent]).toBeLessThan(eventsMap[Session + ":" + SessionStoppedEvent]); + } + + // there is no partial result reported after the final result + // (and check that we have intermediate and final results recorded) + expect(eventsMap[Recognizing]).toBeLessThan(eventsMap[Recognized]); + + // make sure events we don't expect, don't get raised + expect(Canceled in eventsMap).toEqual(false); + done(); + + }, (error: string) => { + done.fail(error); + }); + }); + + test("StartContinuousRecognitionAsync", (done: jest.DoneCallback) => { + // tslint:disable-next-line:no-console + console.info("Name: StartContinuousRecognitionAsync"); + const r: sdk.TranslationRecognizer = BuildRecognizerFromWaveFile(); + objsToClose.push(r); + + r.startContinuousRecognitionAsync(() => { + + // Just long enough to start the connection, but not as long as recognition takes. + const end: number = Date.now() + 1000; + + WaitForCondition(() => { + return end <= Date.now(); + }, () => { + r.stopContinuousRecognitionAsync(() => { + done(); + }, (error: string) => done.fail(error)); + }); + }, (error: string) => done.fail(error)); + }); + + test("StopContinuousRecognitionAsync", (done: jest.DoneCallback) => { + // tslint:disable-next-line:no-console + console.info("Name: StopContinuousRecognitionAsync"); + const r: sdk.TranslationRecognizer = BuildRecognizerFromWaveFile(); + objsToClose.push(r); + + r.canceled = (o: sdk.Recognizer, e: sdk.TranslationRecognitionCanceledEventArgs): void => { + try { + expect(e.errorDetails).toBeUndefined(); + expect(e.reason).not.toEqual(sdk.CancellationReason.Error); + } catch (error) { + done.fail(error); + } + }; + r.startContinuousRecognitionAsync(() => { + const end: number = Date.now() + 1000; + + WaitForCondition(() => { + return end <= Date.now(); + }, () => { + r.stopContinuousRecognitionAsync(() => done(), (error: string) => done.fail(error)); + }); + }, (error: string) => done.fail(error)); + }); + + test("StartStopContinuousRecognitionAsync", (done: jest.DoneCallback) => { + // tslint:disable-next-line:no-console + console.info("Name: StartStopContinuousRecognitionAsync"); + const r: sdk.TranslationRecognizer = BuildRecognizerFromWaveFile(); + objsToClose.push(r); + + const rEvents: { [id: string]: string; } = {}; + + r.recognized = ((o: sdk.Recognizer, e: sdk.TranslationRecognitionEventArgs) => { + const result: string = e.result.translations.get("de", ""); + rEvents["Result@" + Date.now()] = result; + }); + + r.startContinuousRecognitionAsync(); + + WaitForCondition((): boolean => { + return Object.keys(rEvents).length > 0; + }, () => { + expect(rEvents[Object.keys(rEvents)[0]]).toEqual("Wie ist das Wetter?"); + + r.stopContinuousRecognitionAsync(() => done(), (error: string) => done.fail(error)); + }); + }); + + test("TranslateVoiceRoundTrip", (done: jest.DoneCallback) => { + // tslint:disable-next-line:no-console + console.info("Name: TranslateVoiceRoundTrip"); + const s: sdk.SpeechTranslationConfig = BuildSpeechConfig(); + objsToClose.push(s); + + s.voiceName = "Microsoft Server Speech Text to Speech Voice (de-DE, Hedda)"; + + const r: sdk.TranslationRecognizer = BuildRecognizerFromWaveFile(s); + objsToClose.push(r); + + let synthCount: number = 0; + let synthFragmentCount: number = 0; + + const rEvents: { [id: number]: ArrayBuffer; } = {}; + + r.synthesizing = ((o: sdk.Recognizer, e: sdk.TranslationSynthesisEventArgs) => { + switch (e.result.reason) { + case sdk.ResultReason.Canceled: + done.fail(sdk.ResultReason[e.result.reason]); + break; + case sdk.ResultReason.SynthesizingAudio: + const result: ArrayBuffer = e.result.audio; + rEvents[synthFragmentCount++] = result; + break; + case sdk.ResultReason.SynthesizingAudioCompleted: + synthCount++; + break; + } + }); + + let canceled: boolean = false; + let inTurn: boolean = false; + + r.canceled = ((o: sdk.Recognizer, e: sdk.TranslationRecognitionCanceledEventArgs) => { + try { + switch (e.reason) { + case sdk.CancellationReason.Error: + done.fail(e.errorDetails); + break; + case sdk.CancellationReason.EndOfStream: + expect(synthCount).toEqual(1); + canceled = true; + break; + } + } catch (error) { + done.fail(error); + } + }); + + r.sessionStarted = ((s: sdk.Recognizer, e: sdk.SessionEventArgs): void => { + inTurn = true; + }); + + r.sessionStopped = ((s: sdk.Recognizer, e: sdk.SessionEventArgs): void => { + inTurn = false; + }); + + r.startContinuousRecognitionAsync(); + + WaitForCondition((): boolean => (canceled && !inTurn), + () => { + r.stopContinuousRecognitionAsync(() => { + let byteCount: number = 0; + + for (let i: number = 0; i < synthFragmentCount; i++) { + byteCount += rEvents[i].byteLength; + } + + const result: Uint8Array = new Uint8Array(byteCount); + + byteCount = 0; + for (let i: number = 0; i < synthFragmentCount; i++) { + result.set(new Uint8Array(rEvents[i]), byteCount); + byteCount += rEvents[i].byteLength; + } + + const inputStream: File = ByteBufferAudioFile.Load(result); + const config: sdk.AudioConfig = sdk.AudioConfig.fromWavFileInput(inputStream); + const speechConfig: sdk.SpeechConfig = sdk.SpeechConfig.fromSubscription(Settings.SpeechSubscriptionKey, Settings.SpeechRegion); + objsToClose.push(speechConfig); + speechConfig.speechRecognitionLanguage = "de-DE"; + + const r2: sdk.SpeechRecognizer = new sdk.SpeechRecognizer(speechConfig, config); + objsToClose.push(r2); + + r2.recognizeOnceAsync((speech: sdk.SpeechRecognitionResult) => { + expect(speech.errorDetails).toBeUndefined(); + expect(speech.reason).toEqual(sdk.ResultReason.RecognizedSpeech); + expect(speech.text).toEqual("Wie ist das Wetter?"); + done(); + }, (error: string) => done.fail(error)); + }, (error: string) => done.fail(error)); + }); + }, 10000); + + test("TranslateVoiceInvalidVoice", (done: jest.DoneCallback) => { + // tslint:disable-next-line:no-console + console.info("Name: TranslateVoiceInvalidVoice"); + const s: sdk.SpeechTranslationConfig = BuildSpeechConfig(); + objsToClose.push(s); + + s.voiceName = "Microsoft Server Speech Text to Speech Voice (BadVoice)"; + + const r: sdk.TranslationRecognizer = BuildRecognizerFromWaveFile(s); + objsToClose.push(r); + + r.synthesizing = ((o: sdk.Recognizer, e: sdk.TranslationSynthesisEventArgs) => { + try { + expect(sdk.ResultReason[e.result.reason]).toEqual(sdk.ResultReason[sdk.ResultReason.Canceled]); + } catch (error) { + done.fail(error); + } + + }); + + let stopReco: boolean = false; + let pass: boolean = false; + + r.canceled = ((o: sdk.Recognizer, e: sdk.TranslationRecognitionCanceledEventArgs) => { + try { + stopReco = true; + if (!pass) { + expect(e.errorDetails).toEqual("Synthesis service failed with code: - Could not identify the voice 'Microsoft Server Speech Text to Speech Voice (BadVoice)' for the text to speech service "); + } else { + expect(sdk.CancellationReason[e.reason]).toEqual(sdk.CancellationReason[sdk.CancellationReason.EndOfStream]); + } + + pass = true; + } catch (error) { + done.fail(error); + } + }); + + r.startContinuousRecognitionAsync(); + + WaitForCondition(() => stopReco, () => { + r.stopContinuousRecognitionAsync(() => { + if (pass) { + done(); + } + }); + }); + }); + + test("TranslateVoiceUSToGerman", (done: jest.DoneCallback) => { + // tslint:disable-next-line:no-console + console.info("Name: TranslateVoiceUSToGerman"); + const s: sdk.SpeechTranslationConfig = BuildSpeechConfig(); + objsToClose.push(s); + + s.voiceName = "Microsoft Server Speech Text to Speech Voice (de-DE, Hedda)"; + + const r: sdk.TranslationRecognizer = BuildRecognizerFromWaveFile(s); + objsToClose.push(r); + + let synthCount: number = 0; + let synthFragmentCount: number = 0; + + const rEvents: { [id: number]: ArrayBuffer; } = {}; + + r.synthesizing = ((o: sdk.Recognizer, e: sdk.TranslationSynthesisEventArgs) => { + try { + switch (e.result.reason) { + case sdk.ResultReason.Canceled: + done.fail(sdk.ResultReason[e.result.reason]); + break; + case sdk.ResultReason.SynthesizingAudio: + const result: ArrayBuffer = e.result.audio; + rEvents[synthFragmentCount++] = result; + break; + case sdk.ResultReason.SynthesizingAudioCompleted: + synthCount++; + break; + } + } catch (error) { + done.fail(error); + } + }); + + let canceled: boolean = false; + let inTurn: boolean = false; + + r.canceled = ((o: sdk.Recognizer, e: sdk.TranslationRecognitionCanceledEventArgs) => { + try { + switch (e.reason) { + case sdk.CancellationReason.Error: + done.fail(e.errorDetails); + break; + case sdk.CancellationReason.EndOfStream: + expect(synthCount).toEqual(1); + canceled = true; + break; + } + } catch (error) { + done.fail(error); + } + }); + + r.sessionStarted = ((s: sdk.Recognizer, e: sdk.SessionEventArgs): void => { + inTurn = true; + }); + + r.sessionStopped = ((s: sdk.Recognizer, e: sdk.SessionEventArgs): void => { + inTurn = false; + }); + + r.recognizing = (o: sdk.Recognizer, e: sdk.TranslationRecognitionEventArgs): void => { + try { + expect(e.result.reason).toEqual(sdk.ResultReason.TranslatingSpeech); + } catch (error) { + done.fail(error); + } + }; + + r.startContinuousRecognitionAsync(); + + // wait until we get at least on final result + WaitForCondition((): boolean => (canceled && !inTurn), + () => { + r.stopContinuousRecognitionAsync(() => { + const p: sdk.PushAudioInputStream = sdk.AudioInputStream.createPushStream(); + + for (let i: number = 0; i < synthFragmentCount; i++) { + p.write(rEvents[i]); + } + p.close(); + + const config: sdk.AudioConfig = sdk.AudioConfig.fromStreamInput(p); + const s2: sdk.SpeechConfig = sdk.SpeechConfig.fromSubscription(Settings.SpeechSubscriptionKey, Settings.SpeechRegion); + objsToClose.push(s2); + s2.speechRecognitionLanguage = "de-DE"; + + const r2: sdk.SpeechRecognizer = new sdk.SpeechRecognizer(s2, config); + objsToClose.push(r2); + + r2.recognizeOnceAsync((speech: sdk.SpeechRecognitionResult) => { + expect(speech.errorDetails).toBeUndefined(); + expect(speech.reason).toEqual(sdk.ResultReason.RecognizedSpeech); + expect(speech.text).toEqual("Wie ist das Wetter?"); + done(); + }, (error: string) => { + done.fail(error); + }); + }, (error: string) => { + done.fail(error); + }); + }); + }, 10000); + + test.skip("MultiPhrase", (done: jest.DoneCallback) => { + // tslint:disable-next-line:no-console + console.info("Name: MultiPhrase"); + const s: sdk.SpeechTranslationConfig = BuildSpeechConfig(); + objsToClose.push(s); + + s.voiceName = "Microsoft Server Speech Text to Speech Voice (de-DE, Hedda)"; + s.addTargetLanguage("de-DE"); + s.speechRecognitionLanguage = Settings.WaveFileLanguage; + + const f: ArrayBuffer = WaveFileAudioInput.LoadArrayFromFile(Settings.WaveFile); + const p: sdk.PushAudioInputStream = sdk.AudioInputStream.createPushStream(); + const config: sdk.AudioConfig = sdk.AudioConfig.fromStreamInput(p); + const numPhrases: number = 3; + + for (let i: number = 0; i < 3; i++) { + p.write(f); + } + + p.close(); + + const r: sdk.TranslationRecognizer = new sdk.TranslationRecognizer(s, config); + expect(r).not.toBeUndefined(); + expect(r instanceof sdk.Recognizer).toEqual(true); + objsToClose.push(r); + + let synthCount: number = 0; + let synthFragmentCount: number = 0; + + const rEvents: { [id: number]: ArrayBuffer; } = {}; + + r.synthesizing = ((o: sdk.Recognizer, e: sdk.TranslationSynthesisEventArgs) => { + try { + switch (e.result.reason) { + case sdk.ResultReason.Canceled: + done.fail(sdk.ResultReason[e.result.reason]); + break; + case sdk.ResultReason.SynthesizingAudio: + const result: ArrayBuffer = e.result.audio; + rEvents[synthFragmentCount++] = result; + break; + case sdk.ResultReason.SynthesizingAudioCompleted: + synthCount++; + break; + } + } catch (error) { + done.fail(error); + } + }); + + let canceled: boolean = false; + let inTurn: boolean = false; + + r.canceled = ((o: sdk.Recognizer, e: sdk.TranslationRecognitionCanceledEventArgs) => { + try { + switch (e.reason) { + case sdk.CancellationReason.Error: + done.fail(e.errorDetails); + break; + case sdk.CancellationReason.EndOfStream: + expect(synthCount).toEqual(numPhrases); + canceled = true; + break; + } + } catch (error) { + done.fail(error); + } + }); + + r.sessionStarted = ((s: sdk.Recognizer, e: sdk.SessionEventArgs): void => { + inTurn = true; + }); + + r.sessionStopped = ((s: sdk.Recognizer, e: sdk.SessionEventArgs): void => { + inTurn = false; + }); + + r.startContinuousRecognitionAsync(); + + WaitForCondition((): boolean => (canceled && !inTurn), + () => { + r.stopContinuousRecognitionAsync(() => { + const p: sdk.PushAudioInputStream = sdk.AudioInputStream.createPushStream(); + + for (let i: number = 0; i < synthFragmentCount; i++) { + p.write(rEvents[i]); + } + p.close(); + + const config: sdk.AudioConfig = sdk.AudioConfig.fromStreamInput(p); + const s2: sdk.SpeechConfig = sdk.SpeechConfig.fromSubscription(Settings.SpeechSubscriptionKey, Settings.SpeechRegion); + objsToClose.push(s2); + s2.speechRecognitionLanguage = "de-DE"; + + const r2: sdk.SpeechRecognizer = new sdk.SpeechRecognizer(s2, config); + objsToClose.push(r2); + + let numEvents: number = 0; + canceled = false; + + r2.sessionStarted = ((s: sdk.Recognizer, e: sdk.SessionEventArgs): void => { + inTurn = true; + }); + + r2.sessionStopped = ((s: sdk.Recognizer, e: sdk.SessionEventArgs): void => { + inTurn = false; + }); + + r2.recognized = (o: sdk.Recognizer, e: sdk.SpeechRecognitionEventArgs) => { + try { + expect(e.result.text).toEqual("Wie ist das Wetter?"); + numEvents++; + } catch (error) { + done.fail(error); + } + }; + + r2.canceled = (o: sdk.Recognizer, e: sdk.SpeechRecognitionCanceledEventArgs) => { + switch (e.reason) { + case sdk.CancellationReason.EndOfStream: + canceled = true; + break; + case sdk.CancellationReason.Error: + done.fail(e.errorDetails); + break; + } + }; + + r2.startContinuousRecognitionAsync(() => { + WaitForCondition(() => (canceled && !inTurn), + () => { + r2.stopContinuousRecognitionAsync(() => { + expect(numEvents).toEqual(numPhrases); + done(); + }, (error: string) => { + done.fail(error); + }); + }); + }, + (error: string) => { + done.fail(error); + }); + + }, (error: string) => { + done.fail(error); + }); + }); + }, 45000); + + test("Config is copied on construction", () => { + // tslint:disable-next-line:no-console + console.info("Name: Config is copied on construction"); + const s: sdk.SpeechTranslationConfig = sdk.SpeechTranslationConfig.fromSubscription(Settings.SpeechSubscriptionKey, Settings.SpeechRegion); + expect(s).not.toBeUndefined(); + s.speechRecognitionLanguage = "en-US"; + s.addTargetLanguage("en-US"); + + const ranVal: string = Math.random().toString(); + s.setProperty("RandomProperty", ranVal); + s.voiceName = "Microsoft Server Speech Text to Speech Voice (en-US, ZiraRUS)"; + + const f: File = WaveFileAudioInput.LoadFile(Settings.WaveFile); + const config: sdk.AudioConfig = sdk.AudioConfig.fromWavFileInput(f); + + const r: sdk.TranslationRecognizer = new sdk.TranslationRecognizer(s, config); + expect(r).not.toBeUndefined(); + expect(r instanceof sdk.Recognizer); + + expect(r.speechRecognitionLanguage).toEqual("en-US"); + expect(r.properties.getProperty("RandomProperty")).toEqual(ranVal); + expect(r.properties.getProperty(sdk.PropertyId.SpeechServiceConnection_TranslationVoice)).toEqual("Microsoft Server Speech Text to Speech Voice (en-US, ZiraRUS)"); + + // Change them. + s.speechRecognitionLanguage = "de-DE"; + s.setProperty("RandomProperty", Math.random.toString()); + s.voiceName = "Microsoft Server Speech Text to Speech Voice (de-DE, Hedda)"; + + // Validate no change. + expect(r.speechRecognitionLanguage).toEqual("en-US"); + expect(r.properties.getProperty("RandomProperty")).toEqual(ranVal); + expect(r.properties.getProperty(sdk.PropertyId.SpeechServiceConnection_TranslationVoice)).toEqual("Microsoft Server Speech Text to Speech Voice (en-US, ZiraRUS)"); + + }); + + test("InitialSilenceTimeout (pull)", (done: jest.DoneCallback) => { + // tslint:disable-next-line:no-console + console.info("Name: InitialSilenceTimeout (pull)"); + let p: sdk.PullAudioInputStream; + let bytesSent: number = 0; + + // To make sure we don't send a ton of extra data. + // 5s * 16K * 2 * 1.25; + // For reference, before the throttling was implemented, we sent 6-10x the required data. + const expectedBytesSent: number = 15 * 16000 * 2 * 1.25; + + p = sdk.AudioInputStream.createPullStream( + { + close: () => { return; }, + read: (buffer: ArrayBuffer): number => { + bytesSent += buffer.byteLength; + return buffer.byteLength; + }, + }); + + const config: sdk.AudioConfig = sdk.AudioConfig.fromStreamInput(p); + + testInitialSilienceTimeout(config, done, () => expect(bytesSent).toBeLessThan(expectedBytesSent)); + }, 15000); + + test("InitialSilenceTimeout (push)", (done: jest.DoneCallback) => { + // tslint:disable-next-line:no-console + console.info("Name: InitialSilenceTimeout (push)"); + const p: sdk.PushAudioInputStream = sdk.AudioInputStream.createPushStream(); + const bigFileBuffer: Uint8Array = new Uint8Array(1024 * 1024); + const config: sdk.AudioConfig = sdk.AudioConfig.fromStreamInput(p); + + p.write(bigFileBuffer.buffer); + p.close(); + + testInitialSilienceTimeout(config, done); + }, 15000); + + test("InitialSilenceTimeout (File)", (done: jest.DoneCallback) => { + // tslint:disable-next-line:no-console + console.info("Name: InitialSilenceTimeout (File)"); + const bigFileBuffer: Uint8Array = new Uint8Array(1024 * 1024); + const bigFile: File = ByteBufferAudioFile.Load(bigFileBuffer.buffer); + + const config: sdk.AudioConfig = sdk.AudioConfig.fromWavFileInput(bigFile); + + testInitialSilienceTimeout(config, done); + }, 15000); + + const testInitialSilienceTimeout = (config: sdk.AudioConfig, done: jest.DoneCallback, addedChecks?: () => void): void => { + const s: sdk.SpeechTranslationConfig = BuildSpeechConfig(); + objsToClose.push(s); + + s.addTargetLanguage("de-DE"); + s.speechRecognitionLanguage = "en-US"; + + // To validate the data isn't sent too fast. + const startTime: number = Date.now(); + + const r: sdk.TranslationRecognizer = new sdk.TranslationRecognizer(s, config); + objsToClose.push(r); + + expect(r).not.toBeUndefined(); + expect(r instanceof sdk.Recognizer); + + let numReports: number = 0; + + r.canceled = (o: sdk.Recognizer, e: sdk.TranslationRecognitionCanceledEventArgs) => { + done.fail(e.errorDetails); + }; + + r.recognized = (o: sdk.Recognizer, e: sdk.TranslationRecognitionEventArgs) => { + try { + const res: sdk.SpeechRecognitionResult = e.result; + expect(res).not.toBeUndefined(); + expect(sdk.ResultReason.NoMatch).toEqual(res.reason); + expect(res.text).toBeUndefined(); + + const nmd: sdk.NoMatchDetails = sdk.NoMatchDetails.fromResult(res); + expect(nmd.reason).toEqual(sdk.NoMatchReason.InitialSilenceTimeout); + } catch (error) { + done.fail(error); + } finally { + numReports++; + } + + }; + + r.recognizeOnceAsync( + (p2: sdk.TranslationRecognitionResult) => { + const res: sdk.TranslationRecognitionResult = p2; + numReports++; + + expect(res).not.toBeUndefined(); + expect(sdk.ResultReason.NoMatch).toEqual(res.reason); + expect(res.errorDetails).toBeUndefined(); + expect(res.text).toBeUndefined(); + + const nmd: sdk.NoMatchDetails = sdk.NoMatchDetails.fromResult(res); + expect(nmd.reason).toEqual(sdk.NoMatchReason.InitialSilenceTimeout); + expect(Date.now()).toBeGreaterThanOrEqual(startTime + ((res.offset / 1e+4) / 2)); + + }, + (error: string) => { + fail(error); + }); + + WaitForCondition(() => (numReports === 2), () => { + try { + if (!!addedChecks) { + addedChecks(); + } + done(); + } catch (error) { + done.fail(error); + } + }); + }; + + test.skip("emptyFile", (done: jest.DoneCallback) => { + // tslint:disable-next-line:no-console + console.info("Name: emptyFile"); + const s: sdk.SpeechTranslationConfig = BuildSpeechConfig(); + objsToClose.push(s); + + const blob: Blob[] = []; + const f: File = new File(blob, "file.wav"); + + const config: sdk.AudioConfig = sdk.AudioConfig.fromWavFileInput(f); + + const r: sdk.TranslationRecognizer = new sdk.TranslationRecognizer(s, config); + objsToClose.push(r); + expect(r).not.toBeUndefined(); + expect(r instanceof sdk.Recognizer); + let oneCalled: boolean = false; + + r.canceled = (o: sdk.Recognizer, e: sdk.TranslationRecognitionCanceledEventArgs): void => { + try { + expect(e.reason).toEqual(sdk.CancellationReason.Error); + const cancelDetails: sdk.CancellationDetails = sdk.CancellationDetails.fromResult(e.result); + expect(cancelDetails.reason).toEqual(sdk.CancellationReason.Error); + + if (true === oneCalled) { + done(); + } else { + oneCalled = true; + } + } catch (error) { + done.fail(error); + } + }; + + r.recognizeOnceAsync( + (p2: sdk.SpeechRecognitionResult) => { + if (true === oneCalled) { + done(); + } else { + oneCalled = true; + } + + }, + (error: string) => { + done.fail(error); + }); + }); + + test("Translate Bad Language", (done: jest.DoneCallback) => { + // tslint:disable-next-line:no-console + console.info("Name: Translate Bad Language"); + const s: sdk.SpeechTranslationConfig = BuildSpeechConfig(); + objsToClose.push(s); + + s.addTargetLanguage("bo-GU"); + + const r: sdk.TranslationRecognizer = BuildRecognizerFromWaveFile(s); + objsToClose.push(r); + + expect(r).not.toBeUndefined(); + + expect(r instanceof sdk.Recognizer).toEqual(true); + + r.synthesizing = ((o: sdk.Recognizer, e: sdk.TranslationSynthesisEventArgs) => { + try { + if (e.result.reason === sdk.ResultReason.Canceled) { + done.fail(sdk.ResultReason[e.result.reason]); + } + } catch (error) { + done.fail(error); + } + }); + + r.recognizeOnceAsync( + (res: sdk.TranslationRecognitionResult) => { + expect(res).not.toBeUndefined(); + expect(res.errorDetails).toContain("400036"); + expect(sdk.ResultReason[res.reason]).toEqual(sdk.ResultReason[sdk.ResultReason.RecognizedSpeech]); + expect(res.translations).toBeUndefined(); + expect(res.text).toEqual("What's the weather like?"); + done(); + }, + (error: string) => { + done.fail(error); + }); + }); + + test("Audio Config is optional", () => { + // tslint:disable-next-line:no-console + console.info("Name: Audio Config is optional"); + const s: sdk.SpeechTranslationConfig = BuildSpeechConfig(); + objsToClose.push(s); + s.addTargetLanguage("de-DE"); + s.speechRecognitionLanguage = Settings.WaveFileLanguage; + + const r: sdk.TranslationRecognizer = new sdk.TranslationRecognizer(s); + objsToClose.push(r); + + expect(r instanceof sdk.Recognizer).toEqual(true); + + }); + + test("Default mic is used when audio config is not specified.", () => { + // tslint:disable-next-line:no-console + console.info("Name: Default mic is used when audio config is not specified."); + const s: sdk.SpeechTranslationConfig = sdk.SpeechTranslationConfig.fromSubscription(Settings.SpeechSubscriptionKey, Settings.SpeechRegion); + expect(s).not.toBeUndefined(); + s.speechRecognitionLanguage = "en-US"; + s.addTargetLanguage("en-US"); + + const r: sdk.TranslationRecognizer = new sdk.TranslationRecognizer(s); + expect(r instanceof sdk.Recognizer).toEqual(true); + // Node.js doesn't have a microphone natively. So we'll take the specific message that indicates that microphone init failed as evidence it was attempted. + r.recognizeOnceAsync(() => fail("RecognizeOnceAsync returned success when it should have failed"), + (error: string): void => { + expect(error).not.toBeUndefined(); + expect(error).toEqual("Error: Browser does not support Web Audio API (AudioContext is not available)."); + }); + + r.startContinuousRecognitionAsync(() => fail("startContinuousRecognitionAsync returned success when it should have failed"), + (error: string): void => { + expect(error).not.toBeUndefined(); + expect(error).toEqual("Error: Browser does not support Web Audio API (AudioContext is not available)."); + }); + }); + + test("Connection Errors Propogate Async", (done: jest.DoneCallback) => { + // tslint:disable-next-line:no-console + console.info("Name: Connection Errors Propogate Async"); + const s: sdk.SpeechTranslationConfig = sdk.SpeechTranslationConfig.fromSubscription("badKey", Settings.SpeechRegion); + objsToClose.push(s); + s.addTargetLanguage("en-US"); + + const r: sdk.TranslationRecognizer = BuildRecognizerFromWaveFile(s); + objsToClose.push(r); + + let pass: boolean = false; + r.canceled = (o: sdk.Recognizer, e: sdk.TranslationRecognitionCanceledEventArgs) => { + try { + expect(sdk.CancellationReason[e.reason]).toEqual(sdk.CancellationReason[sdk.CancellationReason.Error]); + expect(sdk.CancellationErrorCode[e.errorCode]).toEqual(sdk.CancellationErrorCode[sdk.CancellationErrorCode.ConnectionFailure]); + pass = true; + } catch (error) { + done.fail(error); + } + }; + + r.startContinuousRecognitionAsync(); + + WaitForCondition(() => pass, () => r.stopContinuousRecognitionAsync(() => done(), (error: string) => done.fail(error))); + }); + + test("Connection Errors Propogate Sync", (done: jest.DoneCallback) => { + // tslint:disable-next-line:no-console + console.info("Name: Connection Errors Propogate Sync"); + const s: sdk.SpeechTranslationConfig = sdk.SpeechTranslationConfig.fromSubscription("badKey", Settings.SpeechRegion); + objsToClose.push(s); + s.addTargetLanguage("en-US"); + + const r: sdk.TranslationRecognizer = BuildRecognizerFromWaveFile(s); + objsToClose.push(r); + + let doneCount: number = 0; + r.canceled = (o: sdk.Recognizer, e: sdk.TranslationRecognitionCanceledEventArgs) => { + try { + expect(sdk.CancellationReason[e.reason]).toEqual(sdk.CancellationReason[sdk.CancellationReason.Error]); + expect(sdk.CancellationErrorCode[e.errorCode]).toEqual(sdk.CancellationErrorCode[sdk.CancellationErrorCode.ConnectionFailure]); + expect(e.errorDetails).toContain("1006"); + doneCount++; + } catch (error) { + done.fail(error); + } + }; + + r.recognizeOnceAsync((result: sdk.TranslationRecognitionResult) => { + try { + const e: sdk.CancellationDetails = sdk.CancellationDetails.fromResult(result); + expect(sdk.CancellationReason[e.reason]).toEqual(sdk.CancellationReason[sdk.CancellationReason.Error]); + expect(sdk.CancellationErrorCode[e.ErrorCode]).toEqual(sdk.CancellationErrorCode[sdk.CancellationErrorCode.ConnectionFailure]); + expect(e.errorDetails).toContain("1006"); + doneCount++; + } catch (error) { + done.fail(error); + } + + WaitForCondition(() => (doneCount === 2), done); + + }); + }); + + test("RecognizeOnce Bad Language", (done: jest.DoneCallback) => { + // tslint:disable-next-line:no-console + console.info("Name: RecognizeOnce Bad Language"); + const s: sdk.SpeechTranslationConfig = BuildSpeechConfig(); + objsToClose.push(s); + s.speechRecognitionLanguage = "BadLanguage"; + s.addTargetLanguage("en-US"); + + const r: sdk.TranslationRecognizer = BuildRecognizerFromWaveFile(s); + objsToClose.push(r); + let doneCount: number = 0; + + r.canceled = (o: sdk.Recognizer, e: sdk.TranslationRecognitionCanceledEventArgs) => { + try { + expect(sdk.CancellationReason[e.reason]).toEqual(sdk.CancellationReason[sdk.CancellationReason.Error]); + expect(sdk.CancellationErrorCode[e.errorCode]).toEqual(sdk.CancellationErrorCode[sdk.CancellationErrorCode.ConnectionFailure]); + expect(e.errorDetails).toContain("1006"); + doneCount++; + } catch (error) { + done.fail(error); + } + }; + + r.recognizeOnceAsync((result: sdk.TranslationRecognitionResult) => { + try { + const e: sdk.CancellationDetails = sdk.CancellationDetails.fromResult(result); + expect(sdk.CancellationReason[e.reason]).toEqual(sdk.CancellationReason[sdk.CancellationReason.Error]); + expect(sdk.CancellationErrorCode[e.ErrorCode]).toEqual(sdk.CancellationErrorCode[sdk.CancellationErrorCode.ConnectionFailure]); + expect(e.errorDetails).toContain("1006"); + doneCount++; + } catch (error) { + done.fail(error); + } + }); + + WaitForCondition(() => (doneCount === 2), done); + }); + + test("Silence After Speech", (done: jest.DoneCallback) => { + // tslint:disable-next-line:no-console + console.info("Name: Silence After Speech"); + // Pump valid speech and then silence until at least one speech end cycle hits. + const p: sdk.PushAudioInputStream = sdk.AudioInputStream.createPushStream(); + const bigFileBuffer: Uint8Array = new Uint8Array(32 * 1024 * 30); // ~30 seconds. + const config: sdk.AudioConfig = sdk.AudioConfig.fromStreamInput(p); + const s: sdk.SpeechTranslationConfig = BuildSpeechConfig(); + s.addTargetLanguage("de-DE"); + s.speechRecognitionLanguage = "en-US"; + objsToClose.push(s); + + p.write(WaveFileAudioInput.LoadArrayFromFile(Settings.WaveFile)); + p.write(bigFileBuffer.buffer); + p.close(); + + const r: sdk.TranslationRecognizer = new sdk.TranslationRecognizer(s, config); + objsToClose.push(r); + + let speechRecognized: boolean = false; + let noMatchCount: number = 0; + let speechEnded: number = 0; + + r.recognized = (o: sdk.Recognizer, e: sdk.TranslationRecognitionEventArgs) => { + try { + if (e.result.reason === sdk.ResultReason.TranslatedSpeech) { + expect(speechRecognized).toEqual(false); + speechRecognized = true; + expect(sdk.ResultReason[e.result.reason]).toEqual(sdk.ResultReason[sdk.ResultReason.TranslatedSpeech]); + expect(e.result.text).toEqual("What's the weather like?"); + } else if (e.result.reason === sdk.ResultReason.NoMatch) { + expect(speechRecognized).toEqual(true); + noMatchCount++; + } + } catch (error) { + done.fail(error); + } + }; + + let canceled: boolean = false; + let inTurn: boolean = false; + + r.sessionStarted = ((s: sdk.Recognizer, e: sdk.SessionEventArgs): void => { + inTurn = true; + }); + + r.sessionStopped = ((s: sdk.Recognizer, e: sdk.SessionEventArgs): void => { + inTurn = false; + }); + + r.canceled = (o: sdk.Recognizer, e: sdk.TranslationRecognitionCanceledEventArgs): void => { + try { + expect(e.errorDetails).toBeUndefined(); + expect(e.reason).toEqual(sdk.CancellationReason.EndOfStream); + canceled = true; + } catch (error) { + done.fail(error); + } + }; + + r.speechEndDetected = (o: sdk.Recognizer, e: sdk.RecognitionEventArgs): void => { + speechEnded++; + }; + + r.startContinuousRecognitionAsync(() => { + WaitForCondition(() => (canceled && !inTurn), () => { + r.stopContinuousRecognitionAsync(() => { + try { + expect(speechEnded).toEqual(noMatchCount); + expect(noMatchCount).toEqual(2); + done(); + } catch (error) { + done.fail(error); + } + }, (error: string) => { + done.fail(error); + }); + }); + }, + (err: string) => { + done.fail(err); + }); + }, 35000); + + test("Silence Then Speech", (done: jest.DoneCallback) => { + // tslint:disable-next-line:no-console + console.info("Name: Silence Then Speech"); + // Pump valid speech and then silence until at least one speech end cycle hits. + const p: sdk.PushAudioInputStream = sdk.AudioInputStream.createPushStream(); + const bigFileBuffer: Uint8Array = new Uint8Array(32 * 1024 * 30); // ~30 seconds. + const config: sdk.AudioConfig = sdk.AudioConfig.fromStreamInput(p); + const s: sdk.SpeechTranslationConfig = BuildSpeechConfig(); + objsToClose.push(s); + s.speechRecognitionLanguage = "en-US"; + s.addTargetLanguage("de-DE"); + + p.write(bigFileBuffer.buffer); + p.write(WaveFileAudioInput.LoadArrayFromFile(Settings.WaveFile)); + p.close(); + + const r: sdk.TranslationRecognizer = new sdk.TranslationRecognizer(s, config); + objsToClose.push(r); + + let speechRecognized: boolean = false; + let noMatchCount: number = 0; + let speechEnded: number = 0; + + let canceled: boolean = false; + let inTurn: boolean = false; + + r.canceled = (o: sdk.Recognizer, e: sdk.TranslationRecognitionCanceledEventArgs): void => { + try { + switch (e.reason) { + case sdk.CancellationReason.Error: + done.fail(e.errorDetails); + break; + case sdk.CancellationReason.EndOfStream: + canceled = true; + break; + } + } catch (error) { + done.fail(error); + } + }; + + r.sessionStarted = ((s: sdk.Recognizer, e: sdk.SessionEventArgs): void => { + inTurn = true; + }); + + r.sessionStopped = ((s: sdk.Recognizer, e: sdk.SessionEventArgs): void => { + inTurn = false; + }); + + r.speechEndDetected = (o: sdk.Recognizer, e: sdk.RecognitionEventArgs): void => { + speechEnded++; + }; + + r.recognized = (o: sdk.Recognizer, e: sdk.TranslationRecognitionEventArgs) => { + try { + const res: sdk.TranslationRecognitionResult = e.result; + expect(res).not.toBeUndefined(); + if (res.reason === sdk.ResultReason.TranslatedSpeech) { + expect(speechRecognized).toEqual(false); + expect(noMatchCount).toBeGreaterThanOrEqual(1); + speechRecognized = true; + expect(sdk.ResultReason[res.reason]).toEqual(sdk.ResultReason[sdk.ResultReason.TranslatedSpeech]); + expect(res.text).toEqual("What's the weather like?"); + } else if (res.reason === sdk.ResultReason.NoMatch) { + expect(speechRecognized).toEqual(false); + noMatchCount++; + } + } catch (error) { + done.fail(error); + } + }; + + r.startContinuousRecognitionAsync(() => { + WaitForCondition(() => (canceled && !inTurn), () => { + r.stopContinuousRecognitionAsync(() => { + try { + expect(speechEnded).toEqual(noMatchCount + 1); + expect(noMatchCount).toEqual(2); + done(); + } catch (error) { + done.fail(error); + } + }, (error: string) => { + done.fail(error); + }); + }); + }, + (err: string) => { + done.fail(err); + }); + }, 35000); + + test("Bad DataType for PushStreams results in error", (done: jest.DoneCallback) => { + // tslint:disable-next-line:no-console + console.info("Name: Bad DataType for PushStreams results in error"); + + const s: sdk.SpeechTranslationConfig = BuildSpeechConfig(); + objsToClose.push(s); + + s.addTargetLanguage("en-US"); + s.speechRecognitionLanguage = "en-US"; + + const p: sdk.PushAudioInputStream = sdk.AudioInputStream.createPushStream(); + const config: sdk.AudioConfig = sdk.AudioConfig.fromStreamInput(p); + + // Wrong data type for ReadStreams + fs.createReadStream(Settings.WaveFile).on("data", (buffer: ArrayBuffer) => { + p.write(buffer); + }).on("end", () => { + p.close(); + }); + + const r: sdk.TranslationRecognizer = new sdk.TranslationRecognizer(s, config); + objsToClose.push(r); + + expect(r).not.toBeUndefined(); + expect(r instanceof sdk.Recognizer); + + r.canceled = (r: sdk.Recognizer, e: sdk.TranslationRecognitionCanceledEventArgs) => { + try { + expect(e.errorDetails).not.toBeUndefined(); + expect(e.errorDetails).toContain("ArrayBuffer"); + expect(sdk.CancellationReason[e.reason]).toEqual(sdk.CancellationReason[sdk.CancellationReason.Error]); + expect(sdk.CancellationErrorCode[e.errorCode]).toEqual(sdk.CancellationErrorCode[sdk.CancellationErrorCode.RuntimeError]); + } catch (error) { + done.fail(error); + } + }; + + r.recognizeOnceAsync( + (p2: sdk.TranslationRecognitionResult) => { + const res: sdk.TranslationRecognitionResult = p2; + try { + expect(res).not.toBeUndefined(); + expect(res.errorDetails).not.toBeUndefined(); + expect(sdk.ResultReason[res.reason]).toEqual(sdk.ResultReason[sdk.ResultReason.Canceled]); + done(); + } catch (error) { + done.fail(error); + } + }, + (error: string) => { + done.fail(error); + }); + }); +}); diff --git a/tests/Utilities.ts b/tests/Utilities.ts new file mode 100644 index 00000000..5325d115 --- /dev/null +++ b/tests/Utilities.ts @@ -0,0 +1,11 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. +const WaitForCondition = (condition: () => boolean, after: () => void): void => { + if (condition() === true) { + after(); + } else { + setTimeout(() => WaitForCondition(condition, after), 500); + } +}; + +export default WaitForCondition; diff --git a/tests/WaveFileAudioInputStream.ts b/tests/WaveFileAudioInputStream.ts new file mode 100644 index 00000000..94225da9 --- /dev/null +++ b/tests/WaveFileAudioInputStream.ts @@ -0,0 +1,26 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +import * as fs from "fs"; + +export class WaveFileAudioInput { + + public static LoadFile(filename: string): File { + // obtain and open the line. + const fileContents: Buffer = fs.readFileSync(filename); + + const arrayBuffer: ArrayBuffer = Uint8Array.from(fileContents).buffer; + const parts: ArrayBuffer[] = [arrayBuffer]; + const file: File = new File(parts, filename); + + return (file); + } + + public static LoadArrayFromFile(filename: string): ArrayBuffer { + const fileContents: Buffer = fs.readFileSync(filename); + + const ret = Uint8Array.from(fileContents); + + return ret.buffer; + } +} diff --git a/tests/input/audio/TurnOnTheLamp.wav b/tests/input/audio/TurnOnTheLamp.wav new file mode 100644 index 00000000..60edc705 Binary files /dev/null and b/tests/input/audio/TurnOnTheLamp.wav differ diff --git a/tests/input/audio/whatstheweatherlike.wav b/tests/input/audio/whatstheweatherlike.wav new file mode 100644 index 00000000..5c92cbc2 Binary files /dev/null and b/tests/input/audio/whatstheweatherlike.wav differ diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 00000000..afc86a08 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,11 @@ +{ + "compilerOptions": { + "module": "commonjs", + "preserveConstEnums": true, + "sourceMap": true, + "target": "ES5", + "declaration": true, + "noImplicitAny": true, + "removeComments": false + } +} \ No newline at end of file diff --git a/tslint.json b/tslint.json new file mode 100644 index 00000000..e25906af --- /dev/null +++ b/tslint.json @@ -0,0 +1,51 @@ +{ + /* + Using recommended community rules + https://github.com/palantir/tslint/blob/master/src/configs/recommended.ts + */ + "extends": "tslint:recommended", + "rules": { + + "max-line-length": [false], + + "member-ordering":[ + true, { + "order": [ + "private-static-field", + "private-instance-field", + "constructor", + "public-static-method", + "public-instance-method", + "protected-static-method", + "protected-instance-method", + "private-static-method", + "private-instance-method" + ] + } + ], + + "no-reference": true, + "no-namespace": true, + "no-bitwise": false, + "no-shadowed-variable": false, + + "only-arrow-functions": [ + true + ], + + /* Enabling strictest possible type checking on every thing we write */ + "typedef": [ + true, + "arrow-parameter", + "call-signature", + "member-variable-declaration", + "parameter", + "property-declaration" + ] + }, + "linterOptions": { + "exclude": [ + "secrets/*.ts" + ] + } +} \ No newline at end of file