├── .devops ├── azure-pipelines-ci.yml ├── azure-pipelines-pr.yml ├── azure-pipelines-release.yml ├── azure-pipelines-test-dependent-projects.yml └── azure-pipelines-test.yml ├── .gitattributes ├── .github ├── CODEOWNERS └── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── .gitignore ├── .prettierignore ├── .prettierrc.json ├── .scripts ├── checkConstantsVersion.ts ├── checkEverything.ts ├── checkForOnlyCalls.ts ├── checkPackageJsonVersion.ts ├── latest.ts ├── local.ts ├── publish.js ├── testDependentProjects.ts └── tsconfig.json ├── .travis.yml ├── .typings ├── karma.d.ts ├── rollup-plugin-commonjs.d.ts ├── rollup-plugin-json.d.ts ├── rollup-plugin-multi-entry.d.ts ├── rollup-plugin-node-resolve.d.ts ├── rollup-plugin-sourcemaps.d.ts └── rollup-plugin-visualizer.d.ts ├── .vscode └── launch.json ├── Changelog.md ├── LICENSE ├── README.md ├── SECURITY.md ├── ThirdPartyNotices.txt ├── api-extractor.json ├── docs └── architectureOverview.md ├── dom-shim.d.ts ├── index.html ├── karma.conf.ts ├── lib ├── browserFetchHttpClient.ts ├── credentials │ ├── apiKeyCredentials.ts │ ├── azureIdentityTokenCredentialAdapter.ts │ ├── basicAuthenticationCredentials.ts │ ├── credentials.ts │ ├── domainCredentials.ts │ ├── serviceClientCredentials.ts │ ├── tokenCredentials.ts │ ├── tokenResponse.ts │ └── topicCredentials.ts ├── defaultHttpClient.browser.ts ├── defaultHttpClient.ts ├── dom.d.ts ├── fetchHttpClient.ts ├── httpClient.ts ├── httpHeaders.ts ├── httpOperationResponse.ts ├── httpPipelineLogLevel.ts ├── httpPipelineLogger.ts ├── msRest.ts ├── nodeFetchHttpClient.ts ├── operationArguments.ts ├── operationParameter.ts ├── operationResponse.ts ├── operationSpec.ts ├── policies │ ├── agentPolicy.browser.ts │ ├── agentPolicy.ts │ ├── deserializationPolicy.ts │ ├── exponentialRetryPolicy.ts │ ├── generateClientRequestIdPolicy.ts │ ├── logPolicy.ts │ ├── msRestUserAgentPolicy.browser.ts │ ├── msRestUserAgentPolicy.ts │ ├── proxyPolicy.browser.ts │ ├── proxyPolicy.ts │ ├── redirectPolicy.ts │ ├── requestPolicy.ts │ ├── rpRegistrationPolicy.ts │ ├── signingPolicy.ts │ ├── systemErrorRetryPolicy.ts │ ├── throttlingRetryPolicy.ts │ └── userAgentPolicy.ts ├── proxyAgent.ts ├── queryCollectionFormat.ts ├── restError.ts ├── serializer.ts ├── serviceClient.ts ├── url.ts ├── util │ ├── base64.browser.ts │ ├── base64.ts │ ├── constants.ts │ ├── utils.ts │ ├── xml.browser.ts │ └── xml.ts ├── webResource.ts └── xhrHttpClient.ts ├── mocha.config.json ├── package.json ├── review └── ms-rest-js.api.md ├── rollup.config.ts ├── samples ├── index.html └── node-sample.ts ├── test ├── credentialTests.ts ├── data │ └── TestClient │ │ └── lib │ │ ├── models │ │ └── mappers.ts │ │ └── testClient.ts ├── defaultHttpClientTests.ts ├── logFilterTests.ts ├── mocha.opts ├── mockHttp.ts ├── msAssert.ts ├── msRestUserAgentPolicyTests.ts ├── operationParameterTests.ts ├── policies │ ├── agentPolicyTests.ts │ ├── deserializationPolicyTests.ts │ ├── proxyPolicyTests.ts │ ├── redirectPolicyTests.ts │ ├── systemErrorRetryPolicyTests.spec.ts │ └── throttlingRetryPolicyTests.ts ├── proxyAgent.node.ts ├── redirectLimitTests.ts ├── resources │ ├── example-index.html │ ├── httpbin-index.html │ └── test.txt ├── serializationTests.ts ├── serviceClientTests.ts ├── urlTests.ts ├── xhrTests.browser.ts └── xmlTests.ts ├── tsconfig.es.json ├── tsconfig.json ├── tslint.json ├── webpack.testconfig.ts └── xunit.xml /.devops/azure-pipelines-ci.yml: -------------------------------------------------------------------------------- 1 | trigger: 2 | - master 3 | 4 | pr: none 5 | 6 | jobs: 7 | - job: Build 8 | pool: 9 | vmImage: 'ubuntu-22.04' 10 | steps: 11 | - task: Npm@1 12 | displayName: 'npm install' 13 | inputs: 14 | command: custom 15 | verbose: false 16 | customCommand: install 17 | - task: Npm@1 18 | displayName: 'npm run build' 19 | inputs: 20 | command: custom 21 | verbose: false 22 | customCommand: run build 23 | - job: Pack 24 | pool: 25 | vmImage: 'ubuntu-22.04' 26 | steps: 27 | - task: NodeTool@0 28 | displayName: 'Install Node 11.x' 29 | inputs: 30 | versionSpec: 11.x 31 | - task: Npm@1 32 | displayName: 'npm pack' 33 | inputs: 34 | command: custom 35 | verbose: false 36 | customCommand: pack 37 | - task: CopyFiles@2 38 | displayName: 'Copy Files to: drop' 39 | inputs: 40 | Contents: '*.tgz' 41 | TargetFolder: drop 42 | - task: PublishBuildArtifacts@1 43 | inputs: 44 | pathtoPublish: $(Build.SourcesDirectory)/drop 45 | - task: PublishBuildArtifacts@1 46 | inputs: 47 | pathtoPublish: $(Build.SourcesDirectory)/dist 48 | artifactName: dist 49 | - template: azure-pipelines-test.yml 50 | parameters: 51 | nodeVersion: '10' 52 | - template: azure-pipelines-test.yml 53 | parameters: 54 | nodeVersion: '12' 55 | -------------------------------------------------------------------------------- /.devops/azure-pipelines-pr.yml: -------------------------------------------------------------------------------- 1 | jobs: 2 | - job: Build 3 | pool: 4 | vmImage: 'ubuntu-22.04' 5 | steps: 6 | - task: Npm@1 7 | displayName: 'npm install' 8 | inputs: 9 | command: custom 10 | verbose: false 11 | customCommand: install 12 | - task: Npm@1 13 | displayName: 'npm run build' 14 | inputs: 15 | command: custom 16 | verbose: false 17 | customCommand: run build 18 | - job: Check_Everything 19 | pool: 20 | vmImage: 'ubuntu-22.04' 21 | steps: 22 | - task: Npm@1 23 | displayName: 'npm install' 24 | inputs: 25 | command: custom 26 | verbose: false 27 | customCommand: install 28 | - task: Npm@1 29 | displayName: 'npm run check:everything -- --azure-devops' 30 | inputs: 31 | command: custom 32 | verbose: false 33 | customCommand: run check:everything -- --azure-devops 34 | - template: azure-pipelines-test.yml 35 | parameters: 36 | nodeVersion: '10' 37 | - template: azure-pipelines-test.yml 38 | parameters: 39 | nodeVersion: '12' 40 | -------------------------------------------------------------------------------- /.devops/azure-pipelines-release.yml: -------------------------------------------------------------------------------- 1 | trigger: none 2 | pr: none 3 | 4 | jobs: 5 | - job: Release 6 | pool: 7 | vmImage: 'ubuntu-22.04' 8 | steps: 9 | - task: NodeTool@0 10 | inputs: 11 | versionSpec: '14.x' 12 | - script: | 13 | npm install 14 | npm run build 15 | displayName: Build 16 | 17 | - script: npm run test 18 | displayName: Test 19 | 20 | - script: | 21 | npm install -g npm 22 | npm config set //registry.npmjs.org/:_authToken=$(azure-sdk-npm-token) 23 | npm publish --access public 24 | displayName: Publish 25 | -------------------------------------------------------------------------------- /.devops/azure-pipelines-test-dependent-projects.yml: -------------------------------------------------------------------------------- 1 | variables: 2 | artifactName: ms-rest-js 3 | msRestJsPackageName: azure-ms-rest-js.tgz 4 | msRestAzureArtifactName: ms-rest-azure-js 5 | msRestAzureJsPackageName: azure-ms-rest-azure-js.tgz 6 | tempDirectory: $(Pipeline.Workspace)/.tmp 7 | vmImage: 'ubuntu-22.04' 8 | 9 | jobs: 10 | - job: prepare_ms_rest_js 11 | displayName: 'Pack and upload ms-rest-js' 12 | pool: 13 | vmImage: $(vmImage) 14 | steps: 15 | - script: 'npm pack' 16 | displayName: 'npm pack' 17 | - script: 'mv azure-ms-rest-js-*.tgz $(msRestJsPackageName)' 18 | displayName: 'rename artifact' 19 | - task: PublishPipelineArtifact@0 20 | inputs: 21 | artifactName: $(artifactName) 22 | targetPath: $(msRestJsPackageName) 23 | 24 | - job: test_ms_rest_azure_js 25 | displayName: 'Test ms-rest-azure-js with PR ms-rest-js' 26 | dependsOn: prepare_ms_rest_js 27 | pool: 28 | vmImage: $(vmImage) 29 | variables: 30 | repoDir: '$(tempDirectory)/ms-rest-azure-js' 31 | steps: 32 | - task: DownloadPipelineArtifact@0 33 | inputs: 34 | artifactName: $(artifactName) 35 | targetPath: $(System.DefaultWorkingDirectory) 36 | - script: 'mkdir -p $(tempDirectory)' 37 | displayName: 'mkdir -p $(tempDirectory)' 38 | - script: 'git clone https://github.com/Azure/ms-rest-azure-js.git ms-rest-azure-js --depth 1' 39 | workingDirectory: $(tempDirectory) 40 | displayName: "clone ms-rest-azure-js" 41 | - script: 'npm install --no-save $(Build.SourcesDirectory)/$(msRestJsPackageName)' 42 | workingDirectory: $(repoDir) 43 | displayName: 'npm install @azure/ms-rest-js' 44 | - script: 'npm pack' 45 | workingDirectory: $(repoDir) 46 | displayName: 'npm pack' 47 | - script: 'npm run test' 48 | workingDirectory: $(repoDir) 49 | displayName: "npm run test" 50 | - script: 'mv azure-ms-rest-azure-js-*.tgz $(msRestAzureJsPackageName)' 51 | workingDirectory: $(repoDir) 52 | displayName: 'rename artifact' 53 | - task: PublishPipelineArtifact@0 54 | inputs: 55 | artifactName: $(msRestAzureArtifactName) 56 | targetPath: '$(repoDir)/$(msRestAzureJsPackageName)' 57 | 58 | - job: test_autorest_typescript 59 | displayName: 'Test autorest.typescript with PR ms-rest-js' 60 | dependsOn: [prepare_ms_rest_js, test_ms_rest_azure_js] 61 | pool: 62 | vmImage: $(vmImage) 63 | variables: 64 | repoDir: '$(tempDirectory)/autorest.typescript' 65 | steps: 66 | # Autorest appears to use a version of gulp that doesn't yet work with node 12.x: https://stackoverflow.com/questions/55921442/how-to-fix-referenceerror-primordials-is-not-defined-in-node 67 | - task: NodeTool@0 68 | inputs: 69 | versionSpec: 10.x 70 | displayName: Use Node.js 10 71 | - task: DownloadPipelineArtifact@0 72 | inputs: 73 | artifactName: $(artifactName) 74 | targetPath: $(System.DefaultWorkingDirectory) 75 | - task: UseDotNet@2 76 | displayName: 'Use .NET Core sdk v2' 77 | inputs: 78 | packageType: sdk 79 | version: 2.x 80 | installationPath: $(Agent.ToolsDirectory)/dotnet 81 | - task: DownloadPipelineArtifact@0 82 | inputs: 83 | artifactName: $(msRestAzureArtifactName) 84 | targetPath: $(System.DefaultWorkingDirectory) 85 | - script: 'mkdir -p $(tempDirectory)' 86 | displayName: 'mkdir -p $(tempDirectory)' 87 | - script: 'git clone --single-branch -b v4x https://github.com/Azure/autorest.typescript.git autorest.typescript --recursive --depth 1' 88 | workingDirectory: $(tempDirectory) 89 | displayName: "clone autorest.typescript" 90 | - script: 'npm install $(System.DefaultWorkingDirectory)/$(msRestJsPackageName)' 91 | workingDirectory: $(repoDir) 92 | displayName: 'npm install @azure/ms-rest-js' 93 | - script: 'npm install $(System.DefaultWorkingDirectory)/$(msRestAzureJsPackageName)' 94 | workingDirectory: $(repoDir) 95 | displayName: 'npm install @azure/ms-rest-azure-js' 96 | - script: 'cat package.json' 97 | workingDirectory: $(repoDir) 98 | displayName: "debug" 99 | - script: 'npm install --verbose' 100 | workingDirectory: $(repoDir) 101 | displayName: "npm install" 102 | - script: 'gulp regenerate' 103 | workingDirectory: $(repoDir) 104 | displayName: 'gulp regenerate' 105 | - script: 'gulp test' 106 | workingDirectory: $(repoDir) 107 | displayName: 'gulp test' 108 | -------------------------------------------------------------------------------- /.devops/azure-pipelines-test.yml: -------------------------------------------------------------------------------- 1 | parameters: 2 | nodeVersion: '' 3 | 4 | jobs: 5 | - job: 'testOn${{parameters.nodeVersion}}' 6 | pool: 7 | vmImage: 'ubuntu-22.04' 8 | dependsOn: Build 9 | condition: succeeded() 10 | steps: 11 | - script: 'sudo apt-get update' 12 | displayName: 'sudo apt-get update' 13 | - script: 'sudo apt install chromium-browser' 14 | displayName: 'sudo apt install chromium-browser' 15 | - task: NodeTool@0 16 | displayName: 'Install Node ${{parameters.nodeVersion}}' 17 | inputs: 18 | versionSpec: '${{parameters.nodeVersion}}.x' 19 | - task: Npm@1 20 | displayName: 'npm install' 21 | inputs: 22 | verbose: false 23 | - task: Npm@1 24 | displayName: 'npm test' 25 | inputs: 26 | command: custom 27 | verbose: false 28 | customCommand: test 29 | - task: PublishTestResults@2 30 | inputs: 31 | testResultsFiles: '$(System.DefaultWorkingDirectory)/test-results.xml' 32 | testRunTitle: 'Test results for JavaScript' 33 | - task: PublishCodeCoverageResults@1 34 | inputs: 35 | codeCoverageTool: Cobertura 36 | summaryFileLocation: '$(System.DefaultWorkingDirectory)/coverage/cobertura-coverage.xml' 37 | reportDirectory: '$(System.DefaultWorkingDirectory)/coverage/' 38 | failIfCoverageEmpty: true 39 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.bmp binary 2 | *.dll binary 3 | *.gif binary 4 | *.jpg binary 5 | *.png binary 6 | *.snk binary 7 | *.exe binary 8 | *.wmv binary 9 | *.mp4 binary 10 | *.ismv binary 11 | *.isma binary 12 | 13 | *.ascx text 14 | *.cmd text 15 | *.config text 16 | *.cs text diff=csharp 17 | *.csproj text merge=union 18 | *.edmx text 19 | 20 | *.htm text 21 | *.html text 22 | 23 | *.json text eol=lf 24 | *.ts text eol=lf 25 | *.js text eol=lf 26 | 27 | *.msbuild text 28 | *.nuspec text 29 | 30 | *.resx text 31 | *.ruleset text 32 | *.StyleCop text 33 | *.targets text 34 | *.txt text 35 | *.xml text 36 | 37 | *.sln text eol=crlf merge=union -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | # Default owners 2 | * @jeremymeng @xirzec @deyaaeldeen 3 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | 5 | --- 6 | 7 | **Package Version**: _______ 8 | - [ ] **nodejs** 9 | - **nodejs version**: ______ 10 | - **os name/version**: _____ 11 | - [ ] **browser** 12 | - **name/version**: ____ 13 | **Describe the bug** 14 | A clear and concise description of what the bug is. 15 | 16 | **To Reproduce** 17 | Steps to reproduce the behavior: 18 | 1. 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Additional context** 27 | Add any other context about the problem here. 28 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | 5 | --- 6 | 7 | **Is your feature request related to a problem? Please describe.** 8 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 9 | 10 | **Describe the solution you'd like** 11 | A clear and concise description of what you want to happen. 12 | 13 | **Describe alternatives you've considered** 14 | A clear and concise description of any alternative solutions or features you've considered. 15 | 16 | **Additional context** 17 | Add any other context or screenshots about the feature request here. 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | #### linux gitignore 2 | *~ 3 | 4 | # KDE directory preferences 5 | .directory 6 | 7 | # Linux trash folder which might appear on any partition or disk 8 | .Trash-* 9 | 10 | /obj/* 11 | /node_modules 12 | .ntvs_analysis.dat 13 | .ntvs_analysis.* 14 | npm-debug.log 15 | tmp/* 16 | packages/* 17 | *.njsperf 18 | .vs/ 19 | bin/* 20 | /.vscode/* 21 | ValidationTool.njsproj 22 | ValidationTool.sln 23 | .vscode/launch.json 24 | 25 | #### win gitignore 26 | 27 | # Windows image file caches 28 | Thumbs.db 29 | ehthumbs.db 30 | 31 | # Folder config file 32 | Desktop.ini 33 | 34 | # Recycle Bin used on file shares 35 | $RECYCLE.BIN/ 36 | 37 | # Windows Installer files 38 | *.cab 39 | *.msi 40 | *.msm 41 | *.msp 42 | 43 | # Windows shortcuts 44 | *.lnk 45 | 46 | #### osx gitignore 47 | 48 | .DS_Store 49 | .AppleDouble 50 | .LSOverride 51 | 52 | # Icon must end with two \r 53 | Icon 54 | 55 | # Thumbnails 56 | ._* 57 | 58 | # Files that might appear in the root of a volume 59 | .DocumentRevisions-V100 60 | .fseventsd 61 | .Spotlight-V100 62 | .TemporaryItems 63 | .Trashes 64 | .VolumeIcon.icns 65 | 66 | # Directories potentially created on remote AFP share 67 | .AppleDB 68 | .AppleDesktop 69 | Network Trash Folder 70 | Temporary Items 71 | .apdisk 72 | 73 | node_modules 74 | 75 | #### JetBrains 76 | .idea 77 | 78 | # ignore code gen virtual env folder 79 | SdkCodeGen 80 | 81 | output/* 82 | package-lock.json 83 | 84 | # Typescript output 85 | dist/ 86 | *.d.ts 87 | *.d.ts.map 88 | *.js 89 | *.js.map 90 | test-results.xml 91 | .nyc_output/ 92 | coverage/ 93 | 94 | # Rollup 95 | !rollup.config.js 96 | *stats.html 97 | 98 | *.log 99 | 100 | # Tests 101 | coverage 102 | .nyc_output 103 | temp/ 104 | tsdoc-metadata.json 105 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | typings/ 2 | **/*.d.ts 3 | -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "arrowParens": "always", 3 | "bracketSpacing": true, 4 | "endOfLine": "lf", 5 | "printWidth": 100, 6 | "semi": true, 7 | "singleQuote": false, 8 | "tabWidth": 2 9 | } 10 | -------------------------------------------------------------------------------- /.scripts/checkConstantsVersion.ts: -------------------------------------------------------------------------------- 1 | import { 2 | findPackageJsonFileSync, 3 | PackageJson, 4 | readPackageJsonFileSync, 5 | getParentFolderPath, 6 | joinPath, 7 | fileExistsSync, 8 | } from "@ts-common/azure-js-dev-tools"; 9 | import { readFileSync } from "fs"; 10 | import { Logger, getDefaultLogger } from "@azure/logger-js"; 11 | 12 | export function checkConstantsVersion(): number { 13 | const logger: Logger = getDefaultLogger(); 14 | let exitCode = 0; 15 | 16 | function error(text: string): void { 17 | logger.logError(text); 18 | exitCode = 1; 19 | } 20 | 21 | const packageJsonFilePath: string | undefined = findPackageJsonFileSync(__dirname); 22 | if (!packageJsonFilePath) { 23 | error("Could not find a package.json file."); 24 | } else { 25 | const packageJson: PackageJson = readPackageJsonFileSync(packageJsonFilePath); 26 | const packageVersion: string | undefined = packageJson.version; 27 | if (!packageVersion) { 28 | error(`Could not find a version property in ${packageJsonFilePath}.`); 29 | } else { 30 | const repositoryRootFolderPath: string = getParentFolderPath(packageJsonFilePath); 31 | const constantsTsFilePath: string = joinPath( 32 | repositoryRootFolderPath, 33 | "lib/util/constants.ts" 34 | ); 35 | if (!fileExistsSync(constantsTsFilePath)) { 36 | error(`${constantsTsFilePath} doesn't exist anymore. Where'd it go?`); 37 | } else { 38 | const constantsTsFileContents: string = readFileSync(constantsTsFilePath, { 39 | encoding: "utf8", 40 | }); 41 | const regularExpressionString = `msRestVersion: "(.*)"`; 42 | const regularExpression = new RegExp(regularExpressionString); 43 | const match: RegExpMatchArray | null = constantsTsFileContents.match(regularExpression); 44 | if (!match) { 45 | error(`${constantsTsFilePath} doesn't contain a match for ${regularExpressionString}.`); 46 | } else if (match[1] !== packageVersion) { 47 | error( 48 | `Expected ${constantsTsFilePath} to contain an msRestVersion property with the value "${packageVersion}", but it was "${match[1]}" instead.` 49 | ); 50 | } else { 51 | logger.logInfo( 52 | `${constantsTsFilePath} contained the correct value for msRestVersion ("${packageVersion}").` 53 | ); 54 | } 55 | } 56 | } 57 | } 58 | 59 | process.exitCode = exitCode; 60 | 61 | return exitCode; 62 | } 63 | 64 | if (typeof require != undefined && require.main === module) { 65 | checkConstantsVersion(); 66 | } 67 | -------------------------------------------------------------------------------- /.scripts/checkEverything.ts: -------------------------------------------------------------------------------- 1 | import { checkEverything } from "@ts-common/azure-js-dev-tools"; 2 | import { checkConstantsVersion } from "./checkConstantsVersion"; 3 | 4 | checkEverything({ 5 | additionalChecks: { 6 | name: "Constants.ts Version", 7 | check: checkConstantsVersion, 8 | }, 9 | }); 10 | -------------------------------------------------------------------------------- /.scripts/checkForOnlyCalls.ts: -------------------------------------------------------------------------------- 1 | import { checkForOnlyCalls } from "@ts-common/azure-js-dev-tools"; 2 | 3 | checkForOnlyCalls(); 4 | -------------------------------------------------------------------------------- /.scripts/checkPackageJsonVersion.ts: -------------------------------------------------------------------------------- 1 | import { checkPackageJsonVersion } from "@ts-common/azure-js-dev-tools"; 2 | 3 | checkPackageJsonVersion(); 4 | -------------------------------------------------------------------------------- /.scripts/latest.ts: -------------------------------------------------------------------------------- 1 | import { changeClonedDependenciesTo } from "@ts-common/azure-js-dev-tools"; 2 | 3 | changeClonedDependenciesTo(__dirname, "latest"); 4 | -------------------------------------------------------------------------------- /.scripts/local.ts: -------------------------------------------------------------------------------- 1 | import { changeClonedDependenciesTo } from "@ts-common/azure-js-dev-tools"; 2 | 3 | changeClonedDependenciesTo(__dirname, "local"); 4 | -------------------------------------------------------------------------------- /.scripts/publish.js: -------------------------------------------------------------------------------- 1 | const semver = require("semver"); 2 | const util = require("util"); 3 | const cp= require("child_process"); 4 | const exec=util.promisify(cp.exec); 5 | 6 | async function main() { 7 | const package_json = require("../package.json"); 8 | const baseVersion = (package_json.version).trim() 9 | const v = (await exec("git rev-list --parents HEAD --count --full-history")).stdout.trim(); 10 | 11 | const version = `${semver.major(baseVersion)}.${semver.minor(baseVersion)}.${v}` 12 | 13 | console.log(`Using version ${version}`); 14 | process.argv.push(`publish`,`--access`,`public`,`--tag`,`preview`,`--new-version`,`${version}`, `--no-git-tag-version`); 15 | // now, on with the publish... 16 | require( "yarn/lib/cli.js" ); 17 | } 18 | 19 | main(); -------------------------------------------------------------------------------- /.scripts/testDependentProjects.ts: -------------------------------------------------------------------------------- 1 | import path from "path"; 2 | import { 3 | run, 4 | RunResult, 5 | RunOptions, 6 | Command, 7 | commandToString, 8 | } from "@ts-common/azure-js-dev-tools"; 9 | 10 | async function execAndLog(executable: string, args?: string[], options?: RunOptions): Promise { 11 | const command: Command = { 12 | executable, 13 | args, 14 | }; 15 | 16 | console.log(`\n\nRunning ${commandToString(command)}`); 17 | 18 | const result: RunResult = await run(command, undefined, { 19 | ...options, 20 | log: console.log, 21 | showCommand: true, 22 | showResult: true, 23 | }); 24 | 25 | console.log( 26 | `\nResult of "${commandToString(command)}" [Exit code: ${result.exitCode}]:\n` + 27 | result.stdout + 28 | "\n" 29 | ); 30 | 31 | if (result.exitCode) { 32 | console.error(`Error while running "${commandToString(command)}": ${result.error}`); 33 | throw new Error(result.stderr); 34 | } 35 | 36 | return Promise.resolve(result); 37 | } 38 | 39 | async function cloneRepository(projectName: string, projectDirectory: string) { 40 | const gitHubUrl = `https://github.com/Azure/${projectName}.git`; 41 | await execAndLog(`git`, ["clone", gitHubUrl, projectDirectory, "--recursive"]); 42 | await execAndLog(`npm`, ["install"], { executionFolderPath: projectDirectory }); 43 | } 44 | 45 | async function buildAndTest(projectDirectory: string) { 46 | await execAndLog(`npm`, ["run", "build"], { executionFolderPath: projectDirectory }); 47 | await execAndLog(`npm`, ["run", "test"], { executionFolderPath: projectDirectory }); 48 | } 49 | 50 | async function cloneAndRunTest(msRestJsDirectory: string, projectName: string) { 51 | const projectDirectory = path.join(msRestJsDirectory, `../.tmp/${projectName}`); 52 | await cloneRepository(projectName, projectDirectory); 53 | 54 | await execAndLog(`npm`, ["install", msRestJsDirectory], { 55 | executionFolderPath: projectDirectory, 56 | }); 57 | 58 | const additionalCommands: string[] = process.argv.slice(3); 59 | for (const command of additionalCommands) { 60 | await execAndLog(command, undefined, { executionFolderPath: projectDirectory }); 61 | } 62 | 63 | await buildAndTest(projectDirectory); 64 | await execAndLog(`rm`, ["-rf", projectDirectory]); 65 | } 66 | 67 | (async () => { 68 | try { 69 | console.log(`Passed parameters:\n${process.argv}`); 70 | const msRestJsDirectory = path.join(__dirname, ".."); 71 | console.log(`ms-rest-js directory: ${msRestJsDirectory}`); 72 | 73 | const projectName = process.argv[2]; 74 | await cloneAndRunTest(msRestJsDirectory, projectName); 75 | } catch (error) { 76 | process.exit(1); 77 | } 78 | })(); 79 | -------------------------------------------------------------------------------- /.scripts/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "noEmit": true, 5 | "strict": true, 6 | "lib": [ 7 | "dom", 8 | "es5", 9 | "es6", 10 | "es7", 11 | "esnext", 12 | "esnext.asynciterable", 13 | "es2015.iterable" 14 | ] 15 | }, 16 | "include": [ 17 | "*.ts", 18 | "*.js" 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | sudo: true 3 | addons: 4 | chrome: stable 5 | node_js: 6 | - "6" 7 | - "8" -------------------------------------------------------------------------------- /.typings/karma.d.ts: -------------------------------------------------------------------------------- 1 | import * as karma from "karma"; 2 | 3 | export type Config = karma.Config & { 4 | rollupPreprocessor: any; 5 | set: (config: ConfigOptions) => void; 6 | } 7 | 8 | export type ConfigOptions = karma.ConfigOptions & { 9 | rollupPreprocessor: any; 10 | } 11 | -------------------------------------------------------------------------------- /.typings/rollup-plugin-commonjs.d.ts: -------------------------------------------------------------------------------- 1 | declare module "rollup-plugin-commonjs" { 2 | const commonjs(options?: any) => void; 3 | export default commonjs; 4 | } 5 | -------------------------------------------------------------------------------- /.typings/rollup-plugin-json.d.ts: -------------------------------------------------------------------------------- 1 | declare module "rollup-plugin-json" { 2 | const json() => void; 3 | export default json; 4 | } 5 | -------------------------------------------------------------------------------- /.typings/rollup-plugin-multi-entry.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'rollup-plugin-multi-entry' { 2 | const multiEntry: () => void; 3 | export default multiEntry; 4 | } 5 | -------------------------------------------------------------------------------- /.typings/rollup-plugin-node-resolve.d.ts: -------------------------------------------------------------------------------- 1 | declare module "rollup-plugin-node-resolve" { 2 | const nodeResolve(options: { [_: string]: any }) => void; 3 | export default nodeResolve; 4 | } 5 | -------------------------------------------------------------------------------- /.typings/rollup-plugin-sourcemaps.d.ts: -------------------------------------------------------------------------------- 1 | declare module "rollup-plugin-sourcemaps" { 2 | const sourcemaps() => void; 3 | export default sourcemaps; 4 | } 5 | -------------------------------------------------------------------------------- /.typings/rollup-plugin-visualizer.d.ts: -------------------------------------------------------------------------------- 1 | declare module "rollup-plugin-visualizer" { 2 | const visualizer(options?: { [_: string]: any }) => void; 3 | export default visualizer; 4 | } 5 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "type": "node", 9 | "request": "launch", 10 | "name": "Mocha Tests", 11 | "program": "${workspaceFolder}/node_modules/mocha/bin/_mocha", 12 | "args": [ 13 | "--timeout", 14 | "999999", 15 | "--colors" 16 | ], 17 | "internalConsoleOptions": "openOnSessionStart" 18 | } 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) Microsoft Corporation. All rights reserved. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ms-rest-js 2 | 3 | [![Build Status](https://dev.azure.com/azure-public/azsdk/_apis/build/status/public.Azure.ms-rest-js%20-%20CI)](https://dev.azure.com/azure-public/azsdk/_build/latest?definitionId=39) 4 | 5 | Runtime for isomorphic javascript libraries (that work in the browser and node.js environment) generated via [Autorest](https://github.com/Azure/Autorest). 6 | 7 | ## Requirements 8 | - Node.js version > 6.x 9 | - `npm install -g typescript` 10 | 11 | ## Installation 12 | - After cloning the repo, execute `npm install` 13 | 14 | ## Execution 15 | 16 | ### Node.js 17 | - Set the subscriptionId and token as instructed in `samples/node-samples.ts` 18 | - Run `npx ts-node samples/node-sample.js` 19 | 20 | ### In the browser 21 | - Run `npm run build` 22 | - Set the subscriptionId and token then 23 | - Open index.html file in the browser. It should show the response from GET request on the storage account. From Chrome type Ctrl + Shift + I and you can see the logs in console. 24 | 25 | ## Architecture Overview 26 | 27 | You can find an explanation of how this repository's code works by going to our [architecture overview](https://github.com/Azure/ms-rest-js/blob/master/docs/architectureOverview.md). 28 | 29 | # Contributing 30 | 31 | This project welcomes contributions and suggestions. Most contributions require you to agree to a 32 | Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us 33 | the rights to use your contribution. For details, visit https://cla.microsoft.com. 34 | 35 | When you submit a pull request, a CLA-bot will automatically determine whether you need to provide 36 | a CLA and decorate the PR appropriately (e.g., label, comment). Simply follow the instructions 37 | provided by the bot. You will only need to do this once across all repos using our CLA. 38 | 39 | This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). 40 | For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or 41 | contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. 42 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## Security 4 | 5 | Microsoft takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organizations, which include [Microsoft](https://github.com/microsoft), [Azure](https://github.com/Azure), [DotNet](https://github.com/dotnet), [AspNet](https://github.com/aspnet), [Xamarin](https://github.com/xamarin), and [our GitHub organizations](https://opensource.microsoft.com/). 6 | 7 | If you believe you have found a security vulnerability in any Microsoft-owned repository that meets [Microsoft's definition of a security vulnerability](https://aka.ms/opensource/security/definition), please report it to us as described below. 8 | 9 | ## Reporting Security Issues 10 | 11 | **Please do not report security vulnerabilities through public GitHub issues.** 12 | 13 | Instead, please report them to the Microsoft Security Response Center (MSRC) at [https://msrc.microsoft.com/create-report](https://aka.ms/opensource/security/create-report). 14 | 15 | If you prefer to submit without logging in, send email to [secure@microsoft.com](mailto:secure@microsoft.com). If possible, encrypt your message with our PGP key; please download it from the [Microsoft Security Response Center PGP Key page](https://aka.ms/opensource/security/pgpkey). 16 | 17 | You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Additional information can be found at [microsoft.com/msrc](https://aka.ms/opensource/security/msrc). 18 | 19 | Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue: 20 | 21 | * Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.) 22 | * Full paths of source file(s) related to the manifestation of the issue 23 | * The location of the affected source code (tag/branch/commit or direct URL) 24 | * Any special configuration required to reproduce the issue 25 | * Step-by-step instructions to reproduce the issue 26 | * Proof-of-concept or exploit code (if possible) 27 | * Impact of the issue, including how an attacker might exploit the issue 28 | 29 | This information will help us triage your report more quickly. 30 | 31 | If you are reporting for a bug bounty, more complete reports can contribute to a higher bounty award. Please visit our [Microsoft Bug Bounty Program](https://aka.ms/opensource/security/bounty) page for more details about our active programs. 32 | 33 | ## Preferred Languages 34 | 35 | We prefer all communications to be in English. 36 | 37 | ## Policy 38 | 39 | Microsoft follows the principle of [Coordinated Vulnerability Disclosure](https://aka.ms/opensource/security/cvd). 40 | 41 | 42 | -------------------------------------------------------------------------------- /ThirdPartyNotices.txt: -------------------------------------------------------------------------------- 1 | Third Party Notices for ms-rest-js 2 | 3 | This project incorporates material from the project(s) listed below (collectively, Third Party Code). 4 | Microsoft, Inc. Microsoft is not the original author of the Third Party Code. 5 | The original copyright notice and license, under which Microsoft received such Third Party Code, 6 | are set out below. This Third Party Code is licensed to you under their original license terms set forth below. 7 | Microsoft reserves all other rights not expressly granted, whether by implication, estoppel or otherwise. 8 | 9 | 1. uuid (https://github.com/kelektiv/node-uuid) 10 | 11 | %% uuid NOTICES AND INFORMATION BEGIN HERE 12 | ========================================= 13 | The MIT License (MIT) 14 | 15 | Copyright (c) 2010-2016 Robert Kieffer and other contributors 16 | 17 | Permission is hereby granted, free of charge, to any person obtaining a copy 18 | of this software and associated documentation files (the "Software"), to deal 19 | in the Software without restriction, including without limitation the rights 20 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 21 | copies of the Software, and to permit persons to whom the Software is 22 | furnished to do so, subject to the following conditions: 23 | 24 | The above copyright notice and this permission notice shall be included in all 25 | copies or substantial portions of the Software. 26 | 27 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 28 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 29 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 30 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 31 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 32 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 33 | SOFTWARE. 34 | ========================================= 35 | END OF uuid NOTICES AND INFORMATION -------------------------------------------------------------------------------- /api-extractor.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://developer.microsoft.com/json-schemas/api-extractor/v7/api-extractor.schema.json", 3 | "mainEntryPointFilePath": "es/lib/msRest.d.ts", 4 | "docModel": { 5 | "enabled": true 6 | }, 7 | "apiReport": { 8 | "enabled": true, 9 | "reportFolder": "./review" 10 | }, 11 | "dtsRollup": { 12 | "enabled": true, 13 | "untrimmedFilePath": "", 14 | "publicTrimmedFilePath": "./types/ms-rest-js.d.ts" 15 | }, 16 | "messages": { 17 | "tsdocMessageReporting": { 18 | "default": { 19 | "logLevel": "none" 20 | } 21 | }, 22 | "extractorMessageReporting": { 23 | "ae-missing-release-tag": { 24 | "logLevel": "none" 25 | }, 26 | "ae-unresolved-link": { 27 | "logLevel": "none" 28 | } 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /docs/architectureOverview.md: -------------------------------------------------------------------------------- 1 | # Architecture Overview 2 | 3 | This repository is designed to be used as a runtime companion to code that is generated using the [Autorest TypeScript Generator](https://github.com/Azure/autorest.typescript), but this code can also be used as a standalone library too. The following is an explanation of the structure of this repository and how it's code fits together. 4 | 5 | ## ServiceClient 6 | 7 | The top-most type in this runtime repository is the ServiceClient class. This class contains some properties that may benefit from a little explanation. 8 | 9 | - **HttpClient** - The [HttpClient](https://github.com/Azure/ms-rest-js/blob/master/lib/httpClient.ts#L10) interface is a really simple type that just requires an implementing type to have one method: `sendRequest(WebResource): Promise`. This method takes an HTTP request object (WebResource) and returns a Promise that resolves to an HTTP response (HttpOperationResponse). We provide default HttpClients based on your operating environment ([Fetch-based for Node.js](https://github.com/Azure/ms-rest-js/blob/master/lib/fetchHttpClient.ts) and [XHR-based for browser](https://github.com/Azure/ms-rest-js/blob/master/lib/xhrHttpClient.ts)), but you are free to implement your own HttpClient type and to provide it in the [ServiceClientOptions](https://github.com/Azure/ms-rest-js/blob/master/lib/serviceClient.ts#L32) parameter to the [ServiceClient's constructor](https://github.com/Azure/ms-rest-js/blob/master/lib/serviceClient.ts#L106). This is particularly useful if you are migrating to use ms-rest-js from an application that already had special HTTP logic, or if you need to test a part of your application that makes HTTP requests and you want to provide a Mock HttpClient (like we do [here](https://github.com/Azure/ms-rest-js/blob/master/test/shared/serviceClientTests.ts#L15)). 10 | - **RequestPolicyCreators** - This array contains [functions](https://github.com/Azure/ms-rest-js/blob/master/lib/policies/requestPolicy.ts#L12) that create [RequestPolicy](https://github.com/Azure/ms-rest-js/blob/master/lib/policies/requestPolicy.ts#L14) types. In the simplest scenario, you can use a ServiceClient to send an HTTP request and that request will be provided to the ServiceClient object and it will pass that request directly to your HttpClient implementation. [RequestPolicies](https://github.com/Azure/ms-rest-js/blob/master/lib/policies/requestPolicy.ts#L14) are a way of allowing you to transform every request you send through your ServiceClient before it reaches your HttpClient. Other frameworks and libraries call these objects [Interceptors](https://github.com/square/okhttp/wiki/Interceptors) or [Filters](https://tomcat.apache.org/tomcat-5.5-doc/servletapi/javax/servlet/Filter.html). A [RequestPolicy](https://github.com/Azure/ms-rest-js/blob/master/lib/policies/requestPolicy.ts#L14) can be simplified down to the following illustration: 11 |
12 |     ------- (1) ----------------- (2) ------- (3) ------- (4) -------------- (5)   ~~~~~~~
13 |     |     | --> |               | --> |     | --> |     | --> |            | -->  ~       ~
14 |     | App |     | ServiceClient |     | RP1 |     | RPn |     | HttpClient |    ~ Network  ~
15 |     |     | <-- |               | <-- |     | <-- |     | <-- |            | <--  ~       ~
16 |     ------- (8) -----------------     ------- (7) ------- (6) --------------       ~~~~~~~
17 | 
18 | In this illustration, we're looking at an application that uses a ServiceClient. 19 | 20 | 1. First the application creates and gives an HTTP request ([WebResource](https://github.com/Azure/ms-rest-js/blob/master/lib/webResource.ts#L36)) object to the [ServiceClient's sendRequest()](https://github.com/Azure/ms-rest-js/blob/master/lib/serviceClient.ts#L149) method. When this [method](https://github.com/Azure/ms-rest-js/blob/master/lib/serviceClient.ts#L149) is called, the ServiceClient [creates the pipeline](https://github.com/Azure/ms-rest-js/blob/master/lib/serviceClient.ts#L167-L172) that is used to transform the request. This pipeline is a singly-linked list of [RequestPolicies](https://github.com/Azure/ms-rest-js/blob/master/lib/policies/requestPolicy.ts#L14) that ends with the ServiceClient's [HttpClient](https://github.com/Azure/ms-rest-js/blob/master/lib/httpClient.ts#L10). 21 | 2. Once the pipeline is created, the ServiceClient calls the first RequestPolicy's sendRequest() method with the HTTP request object provided to the ServiceClient's sendRequest() method. In the first RequestPolicy's sendRequest() method, the RequestPolicy either does something to the HTTP request (such as adding a [User-Agent header](https://github.com/Azure/ms-rest-js/blob/master/lib/policies/msRestUserAgentPolicy.ts#L20)) or does something because of the HTTP request (such as [emitting logs](https://github.com/Azure/ms-rest-js/blob/master/lib/policies/logPolicy.ts#L14)). 22 | 3. Each [RequestPolicy](https://github.com/Azure/ms-rest-js/blob/master/lib/policies/requestPolicy.ts#L14) has a [nextPolicy](https://github.com/Azure/ms-rest-js/blob/master/lib/policies/requestPolicy.ts#L19) property that points at the next RequestPolicy in the pipeline. When the current RequestPolicy is finished reacting to the HTTP request, it will call sendRequest() on the next policy in the pipeline. 23 | 4. This continues until the next RequestPolicy in the pipeline is actually the HttpClient (you may have noticed that the HttpClient actually extends the RequestPolicy interface, so it can be added as the last entry in the pipeline). 24 | 5. The HttpClient implementation now does whatever it needs to do to send the HTTP request across the network. Most likely the code that sends HTTP requests doesn't know how to handle a WebResource, so the HttpClient first needs to convert the WebResource HTTP request into the type that the real HTTP implementation knows how to deal with. Then it sends that converted request across the network. 25 | 6. Somehow the HttpClient will get an asynchronous response from the Network (either via callback or Promise). Either way, that response needs to be converted to a Promise and returned to the previous RequestPolicy in the pipeline. 26 | 7. The RequestPolicies are free to either return the response as they receive it, or they can perform additional logic based on the response (such as [retrying a failed request](https://github.com/Azure/ms-rest-js/blob/master/lib/policies/exponentialRetryPolicy.ts#L42) or [deserializing the response's headers and/or body](https://github.com/Azure/ms-rest-js/blob/master/lib/policies/deserializationPolicy.ts#L28)). 27 | 8. When the HTTP response has finally been returned from the first RequestPolicy in the pipeline, the ServiceClient returns it to your application's code, where you can handle the response however you want. 28 | -------------------------------------------------------------------------------- /dom-shim.d.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | 4 | // d.ts shims provide types for things we use internally but are not part 5 | // of this package's surface area. 6 | 7 | interface Request {} 8 | interface RequestInit {} 9 | interface Response {} 10 | interface Headers {} 11 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | MOCHA TESTS for ms-rest-js 5 | 6 | 7 | 8 |
9 | 10 | 11 | 14 | 15 | 16 | 17 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /karma.conf.ts: -------------------------------------------------------------------------------- 1 | const defaults = { 2 | port: 9876, 3 | }; 4 | 5 | module.exports = function (config: any) { 6 | config.set({ 7 | plugins: ["karma-mocha", "karma-chrome-launcher", "karma-firefox-launcher"], 8 | 9 | // frameworks to use 10 | // available frameworks: https://npmjs.org/browse/keyword/karma-adapter 11 | frameworks: ["mocha"], 12 | 13 | // list of files / patterns to load in the browser 14 | files: [ 15 | { pattern: "dist/msRest.browser.js" }, 16 | { pattern: "dist/msRest.browser.js.map", included: false }, 17 | { pattern: "test/msRest.browser.test.js" }, 18 | { pattern: "test/msRest.browser.test.js.map", included: false }, 19 | ], 20 | 21 | // test results reporter to use 22 | // possible values: 'dots', 'progress' 23 | // available reporters: https://npmjs.org/browse/keyword/karma-reporter 24 | reporters: ["progress"], 25 | 26 | // web server port 27 | port: defaults.port, 28 | 29 | // level of logging 30 | // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG 31 | logLevel: config.LOG_INFO, 32 | 33 | // enable / disable watching file and executing tests whenever any file changes 34 | autoWatch: false, 35 | 36 | // start these browsers 37 | // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher 38 | browsers: ["ChromeHeadless"], 39 | 40 | // Continuous Integration mode 41 | // if true, Karma captures browsers, runs the tests and exits 42 | singleRun: false, 43 | 44 | // Concurrency level 45 | // how many browser should be started simultaneous 46 | concurrency: Infinity, 47 | 48 | customLaunchers: { 49 | ChromeNoSecurity: { 50 | base: "ChromeHeadless", 51 | flags: ["--disable-web-security"], 52 | }, 53 | ChromeDebugging: { 54 | base: "Chrome", 55 | flags: [ 56 | `http://localhost:${defaults.port}/debug.html`, 57 | "--auto-open-devtools-for-tabs", 58 | "--disable-web-security", 59 | ], 60 | }, 61 | FirefoxDebugging: { 62 | base: "Firefox", 63 | flags: ["-url", `http://localhost:${defaults.port}/debug.html`, "-devtools"], 64 | }, 65 | }, 66 | }); 67 | }; 68 | -------------------------------------------------------------------------------- /lib/browserFetchHttpClient.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. See License.txt in the project root for license information. 3 | 4 | import { 5 | CommonRequestInfo, 6 | CommonRequestInit, 7 | CommonResponse, 8 | FetchHttpClient, 9 | } from "./fetchHttpClient"; 10 | import { HttpOperationResponse } from "./httpOperationResponse"; 11 | import { WebResourceLike } from "./webResource"; 12 | 13 | export class BrowserFetchHttpClient extends FetchHttpClient { 14 | prepareRequest(_httpRequest: WebResourceLike): Promise> { 15 | return Promise.resolve({}); 16 | } 17 | 18 | processRequest(_operationResponse: HttpOperationResponse): Promise { 19 | return Promise.resolve(); 20 | } 21 | 22 | fetch(input: CommonRequestInfo, init?: CommonRequestInit): Promise { 23 | return fetch(input, init); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /lib/credentials/apiKeyCredentials.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. See License.txt in the project root for license information. 3 | 4 | import { HttpHeaders } from "../httpHeaders"; 5 | import { WebResourceLike } from "../webResource"; 6 | import { ServiceClientCredentials } from "./serviceClientCredentials"; 7 | 8 | /** 9 | * @interface ApiKeyCredentialOptions 10 | * Describes the options to be provided while creating an instance of ApiKeyCredentials 11 | */ 12 | export interface ApiKeyCredentialOptions { 13 | /** 14 | * A key value pair of the header parameters that need to be applied to the request. 15 | */ 16 | inHeader?: { [x: string]: any }; 17 | /** 18 | * A key value pair of the query parameters that need to be applied to the request. 19 | */ 20 | inQuery?: { [x: string]: any }; 21 | } 22 | 23 | /** 24 | * Authenticates to a service using an API key. 25 | */ 26 | export class ApiKeyCredentials implements ServiceClientCredentials { 27 | /** 28 | * A key value pair of the header parameters that need to be applied to the request. 29 | */ 30 | private readonly inHeader?: { [x: string]: any }; 31 | /** 32 | * A key value pair of the query parameters that need to be applied to the request. 33 | */ 34 | private readonly inQuery?: { [x: string]: any }; 35 | 36 | /** 37 | * @constructor 38 | * @param {object} options Specifies the options to be provided for auth. Either header or query needs to be provided. 39 | */ 40 | constructor(options: ApiKeyCredentialOptions) { 41 | if (!options || (options && !options.inHeader && !options.inQuery)) { 42 | throw new Error( 43 | `options cannot be null or undefined. Either "inHeader" or "inQuery" property of the options object needs to be provided.` 44 | ); 45 | } 46 | this.inHeader = options.inHeader; 47 | this.inQuery = options.inQuery; 48 | } 49 | 50 | /** 51 | * Signs a request with the values provided in the inHeader and inQuery parameter. 52 | * 53 | * @param {WebResource} webResource The WebResource to be signed. 54 | * @returns {Promise} The signed request object. 55 | */ 56 | signRequest(webResource: WebResourceLike): Promise { 57 | if (!webResource) { 58 | return Promise.reject( 59 | new Error(`webResource cannot be null or undefined and must be of type "object".`) 60 | ); 61 | } 62 | 63 | if (this.inHeader) { 64 | if (!webResource.headers) { 65 | webResource.headers = new HttpHeaders(); 66 | } 67 | for (const headerName in this.inHeader) { 68 | webResource.headers.set(headerName, this.inHeader[headerName]); 69 | } 70 | } 71 | 72 | if (this.inQuery) { 73 | if (!webResource.url) { 74 | return Promise.reject(new Error(`url cannot be null in the request object.`)); 75 | } 76 | if (webResource.url.indexOf("?") < 0) { 77 | webResource.url += "?"; 78 | } 79 | for (const key in this.inQuery) { 80 | if (!webResource.url.endsWith("?")) { 81 | webResource.url += "&"; 82 | } 83 | webResource.url += `${key}=${this.inQuery[key]}`; 84 | } 85 | } 86 | 87 | return Promise.resolve(webResource); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /lib/credentials/azureIdentityTokenCredentialAdapter.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. See License.txt in the project root for license information. 3 | 4 | import { ServiceClientCredentials } from "./serviceClientCredentials"; 5 | import { Constants as MSRestConstants } from "../util/constants"; 6 | import { WebResource } from "../webResource"; 7 | 8 | import { TokenCredential } from "@azure/core-auth"; 9 | import { TokenResponse } from "./tokenResponse"; 10 | 11 | const DEFAULT_AUTHORIZATION_SCHEME = "Bearer"; 12 | 13 | /** 14 | * Resource manager endpoints to match in order to specify a valid scope to the AzureIdentityCredentialAdapter. 15 | */ 16 | export const azureResourceManagerEndpoints = [ 17 | "https://management.windows.net", 18 | "https://management.chinacloudapi.cn", 19 | "https://management.usgovcloudapi.net", 20 | "https://management.cloudapi.de", 21 | ]; 22 | 23 | /** 24 | * This class provides a simple extension to use {@link TokenCredential} from `@azure/identity` library to 25 | * use with legacy Azure SDKs that accept {@link ServiceClientCredentials} family of credentials for authentication. 26 | */ 27 | export class AzureIdentityCredentialAdapter implements ServiceClientCredentials { 28 | private azureTokenCredential: TokenCredential; 29 | private scopes: string | string[]; 30 | constructor( 31 | azureTokenCredential: TokenCredential, 32 | scopes: string | string[] = "https://management.azure.com/.default" 33 | ) { 34 | this.azureTokenCredential = azureTokenCredential; 35 | this.scopes = scopes; 36 | } 37 | 38 | public async getToken(): Promise { 39 | const accessToken = await this.azureTokenCredential.getToken(this.scopes); 40 | if (accessToken !== null) { 41 | const result: TokenResponse = { 42 | accessToken: accessToken.token, 43 | tokenType: DEFAULT_AUTHORIZATION_SCHEME, 44 | expiresOn: accessToken.expiresOnTimestamp, 45 | }; 46 | return result; 47 | } else { 48 | throw new Error("Could find token for scope"); 49 | } 50 | } 51 | 52 | public async signRequest(webResource: WebResource) { 53 | const tokenResponse = await this.getToken(); 54 | webResource.headers.set( 55 | MSRestConstants.HeaderConstants.AUTHORIZATION, 56 | `${tokenResponse.tokenType} ${tokenResponse.accessToken}` 57 | ); 58 | return Promise.resolve(webResource); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /lib/credentials/basicAuthenticationCredentials.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. See License.txt in the project root for license information. 3 | 4 | import { HttpHeaders } from "../httpHeaders"; 5 | import * as base64 from "../util/base64"; 6 | import { Constants } from "../util/constants"; 7 | import { WebResourceLike } from "../webResource"; 8 | import { ServiceClientCredentials } from "./serviceClientCredentials"; 9 | const HeaderConstants = Constants.HeaderConstants; 10 | const DEFAULT_AUTHORIZATION_SCHEME = "Basic"; 11 | 12 | export class BasicAuthenticationCredentials implements ServiceClientCredentials { 13 | userName: string; 14 | password: string; 15 | authorizationScheme: string = DEFAULT_AUTHORIZATION_SCHEME; 16 | 17 | /** 18 | * Creates a new BasicAuthenticationCredentials object. 19 | * 20 | * @constructor 21 | * @param {string} userName User name. 22 | * @param {string} password Password. 23 | * @param {string} [authorizationScheme] The authorization scheme. 24 | */ 25 | constructor( 26 | userName: string, 27 | password: string, 28 | authorizationScheme: string = DEFAULT_AUTHORIZATION_SCHEME 29 | ) { 30 | if (userName === null || userName === undefined || typeof userName.valueOf() !== "string") { 31 | throw new Error("userName cannot be null or undefined and must be of type string."); 32 | } 33 | if (password === null || password === undefined || typeof password.valueOf() !== "string") { 34 | throw new Error("password cannot be null or undefined and must be of type string."); 35 | } 36 | this.userName = userName; 37 | this.password = password; 38 | this.authorizationScheme = authorizationScheme; 39 | } 40 | 41 | /** 42 | * Signs a request with the Authentication header. 43 | * 44 | * @param {WebResourceLike} webResource The WebResourceLike to be signed. 45 | * @returns {Promise} The signed request object. 46 | */ 47 | signRequest(webResource: WebResourceLike) { 48 | const credentials = `${this.userName}:${this.password}`; 49 | const encodedCredentials = `${this.authorizationScheme} ${base64.encodeString(credentials)}`; 50 | if (!webResource.headers) webResource.headers = new HttpHeaders(); 51 | webResource.headers.set(HeaderConstants.AUTHORIZATION, encodedCredentials); 52 | return Promise.resolve(webResource); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /lib/credentials/credentials.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. See License.txt in the project root for license information. 3 | 4 | export type Authenticator = (challenge: object) => Promise; 5 | -------------------------------------------------------------------------------- /lib/credentials/domainCredentials.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. See License.txt in the project root for license information. 3 | 4 | import { ApiKeyCredentials, ApiKeyCredentialOptions } from "./apiKeyCredentials"; 5 | 6 | export class DomainCredentials extends ApiKeyCredentials { 7 | /** 8 | * Creates a new EventGrid DomainCredentials object. 9 | * 10 | * @constructor 11 | * @param {string} domainKey The EventGrid domain key 12 | */ 13 | constructor(domainKey: string) { 14 | if (!domainKey || (domainKey && typeof domainKey !== "string")) { 15 | throw new Error("domainKey cannot be null or undefined and must be of type string."); 16 | } 17 | const options: ApiKeyCredentialOptions = { 18 | inHeader: { 19 | "aeg-sas-key": domainKey, 20 | }, 21 | }; 22 | super(options); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /lib/credentials/serviceClientCredentials.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. See License.txt in the project root for license information. 3 | 4 | import { WebResourceLike } from "../webResource"; 5 | 6 | export interface ServiceClientCredentials { 7 | /** 8 | * Signs a request with the Authentication header. 9 | * 10 | * @param {WebResourceLike} webResource The WebResourceLike/request to be signed. 11 | * @returns {Promise} The signed request object; 12 | */ 13 | signRequest(webResource: WebResourceLike): Promise; 14 | } 15 | -------------------------------------------------------------------------------- /lib/credentials/tokenCredentials.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. See License.txt in the project root for license information. 3 | 4 | import { HttpHeaders } from "../httpHeaders"; 5 | import { Constants } from "../util/constants"; 6 | import { WebResourceLike } from "../webResource"; 7 | import { ServiceClientCredentials } from "./serviceClientCredentials"; 8 | 9 | const HeaderConstants = Constants.HeaderConstants; 10 | const DEFAULT_AUTHORIZATION_SCHEME = "Bearer"; 11 | 12 | /** 13 | * A credentials object that uses a token string and a authorzation scheme to authenticate. 14 | */ 15 | export class TokenCredentials implements ServiceClientCredentials { 16 | token: string; 17 | authorizationScheme: string = DEFAULT_AUTHORIZATION_SCHEME; 18 | 19 | /** 20 | * Creates a new TokenCredentials object. 21 | * 22 | * @constructor 23 | * @param {string} token The token. 24 | * @param {string} [authorizationScheme] The authorization scheme. 25 | */ 26 | constructor(token: string, authorizationScheme: string = DEFAULT_AUTHORIZATION_SCHEME) { 27 | if (!token) { 28 | throw new Error("token cannot be null or undefined."); 29 | } 30 | this.token = token; 31 | this.authorizationScheme = authorizationScheme; 32 | } 33 | 34 | /** 35 | * Signs a request with the Authentication header. 36 | * 37 | * @param {WebResourceLike} webResource The WebResourceLike to be signed. 38 | * @return {Promise} The signed request object. 39 | */ 40 | signRequest(webResource: WebResourceLike) { 41 | if (!webResource.headers) webResource.headers = new HttpHeaders(); 42 | webResource.headers.set( 43 | HeaderConstants.AUTHORIZATION, 44 | `${this.authorizationScheme} ${this.token}` 45 | ); 46 | return Promise.resolve(webResource); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /lib/credentials/tokenResponse.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. See License.txt in the project root for license information. 3 | 4 | /** 5 | * TokenResponse is defined in `@azure/ms-rest-nodeauth` and is copied here to not 6 | * add an unnecessary dependency. 7 | */ 8 | export interface TokenResponse { 9 | readonly tokenType: string; 10 | readonly accessToken: string; 11 | readonly [x: string]: any; 12 | } 13 | -------------------------------------------------------------------------------- /lib/credentials/topicCredentials.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. See License.txt in the project root for license information. 3 | 4 | import { ApiKeyCredentials, ApiKeyCredentialOptions } from "./apiKeyCredentials"; 5 | 6 | export class TopicCredentials extends ApiKeyCredentials { 7 | /** 8 | * Creates a new EventGrid TopicCredentials object. 9 | * 10 | * @constructor 11 | * @param {string} topicKey The EventGrid topic key 12 | */ 13 | constructor(topicKey: string) { 14 | if (!topicKey || (topicKey && typeof topicKey !== "string")) { 15 | throw new Error("topicKey cannot be null or undefined and must be of type string."); 16 | } 17 | const options: ApiKeyCredentialOptions = { 18 | inHeader: { 19 | "aeg-sas-key": topicKey, 20 | }, 21 | }; 22 | super(options); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /lib/defaultHttpClient.browser.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. See License.txt in the project root for license information. 3 | 4 | export { XhrHttpClient as DefaultHttpClient } from "./xhrHttpClient"; 5 | -------------------------------------------------------------------------------- /lib/defaultHttpClient.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. See License.txt in the project root for license information. 3 | 4 | export { NodeFetchHttpClient as DefaultHttpClient } from "./nodeFetchHttpClient"; 5 | -------------------------------------------------------------------------------- /lib/dom.d.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | 4 | /// 5 | -------------------------------------------------------------------------------- /lib/httpClient.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. See License.txt in the project root for license information. 3 | 4 | import { RequestPolicy } from "./policies/requestPolicy"; 5 | 6 | /** 7 | * An interface that can send HttpRequests and receive promised HttpResponses. 8 | */ 9 | export interface HttpClient extends RequestPolicy {} 10 | -------------------------------------------------------------------------------- /lib/httpOperationResponse.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. See License.txt in the project root for license information. 3 | 4 | import { WebResourceLike } from "./webResource"; 5 | import { HttpHeadersLike } from "./httpHeaders"; 6 | 7 | /** 8 | * The properties on an HTTP response which will always be present. 9 | */ 10 | export interface HttpResponse { 11 | /** 12 | * The raw request 13 | */ 14 | request: WebResourceLike; 15 | 16 | /** 17 | * The HTTP response status (e.g. 200) 18 | */ 19 | status: number; 20 | 21 | /** 22 | * The HTTP response headers. 23 | */ 24 | headers: HttpHeadersLike; 25 | } 26 | 27 | declare global { 28 | /** 29 | * Stub declaration of the browser-only Blob type. 30 | * Full type information can be obtained by including "lib": ["dom"] in tsconfig.json. 31 | */ 32 | interface Blob {} 33 | } 34 | 35 | /** 36 | * Wrapper object for http request and response. Deserialized object is stored in 37 | * the `parsedBody` property when the response body is received in JSON or XML. 38 | */ 39 | export interface HttpOperationResponse extends HttpResponse { 40 | /** 41 | * The parsed HTTP response headers. 42 | */ 43 | parsedHeaders?: { [key: string]: any }; 44 | 45 | /** 46 | * The response body as text (string format) 47 | */ 48 | bodyAsText?: string | null; 49 | 50 | /** 51 | * The response body as parsed JSON or XML 52 | */ 53 | parsedBody?: any; 54 | 55 | /** 56 | * BROWSER ONLY 57 | * 58 | * The response body as a browser Blob. 59 | * Always undefined in node.js. 60 | */ 61 | blobBody?: Promise; 62 | 63 | /** 64 | * NODEJS ONLY 65 | * 66 | * The response body as a node.js Readable stream. 67 | * Always undefined in the browser. 68 | */ 69 | readableStreamBody?: NodeJS.ReadableStream; 70 | 71 | /** 72 | * The redirected property indicates whether the response is the result of a request which was redirected. 73 | */ 74 | redirected?: boolean; 75 | 76 | /** 77 | * The url property contains the URL of the response. The value will be the final URL obtained after any redirects. 78 | */ 79 | url?: string; 80 | } 81 | 82 | /** 83 | * The flattened response to a REST call. 84 | * Contains the underlying HttpOperationResponse as well as 85 | * the merged properties of the parsedBody, parsedHeaders, etc. 86 | */ 87 | export interface RestResponse { 88 | /** 89 | * The underlying HTTP response containing both raw and deserialized response data. 90 | */ 91 | _response: HttpOperationResponse; 92 | 93 | [key: string]: any; 94 | } 95 | -------------------------------------------------------------------------------- /lib/httpPipelineLogLevel.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. See License.txt in the project root for license information. 3 | 4 | /** 5 | * The different levels of logs that can be used with the HttpPipelineLogger. 6 | */ 7 | export enum HttpPipelineLogLevel { 8 | /** 9 | * A log level that indicates that no logs will be logged. 10 | */ 11 | OFF, 12 | 13 | /** 14 | * An error log. 15 | */ 16 | ERROR, 17 | 18 | /** 19 | * A warning log. 20 | */ 21 | WARNING, 22 | 23 | /** 24 | * An information log. 25 | */ 26 | INFO, 27 | } 28 | -------------------------------------------------------------------------------- /lib/httpPipelineLogger.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. See License.txt in the project root for license information. 3 | 4 | import { HttpPipelineLogLevel } from "./httpPipelineLogLevel"; 5 | 6 | /** 7 | * A Logger that can be added to a HttpPipeline. This enables each RequestPolicy to log messages 8 | * that can be used for debugging purposes. 9 | */ 10 | export interface HttpPipelineLogger { 11 | /** 12 | * The log level threshold for what logs will be logged. 13 | */ 14 | minimumLogLevel: HttpPipelineLogLevel; 15 | 16 | /** 17 | * Log the provided message. 18 | * @param logLevel The HttpLogDetailLevel associated with this message. 19 | * @param message The message to log. 20 | */ 21 | log(logLevel: HttpPipelineLogLevel, message: string): void; 22 | } 23 | 24 | /** 25 | * A HttpPipelineLogger that will send its logs to the console. 26 | */ 27 | export class ConsoleHttpPipelineLogger implements HttpPipelineLogger { 28 | /** 29 | * Create a new ConsoleHttpPipelineLogger. 30 | * @param minimumLogLevel The log level threshold for what logs will be logged. 31 | */ 32 | constructor(public minimumLogLevel: HttpPipelineLogLevel) {} 33 | 34 | /** 35 | * Log the provided message. 36 | * @param logLevel The HttpLogDetailLevel associated with this message. 37 | * @param message The message to log. 38 | */ 39 | log(logLevel: HttpPipelineLogLevel, message: string): void { 40 | const logMessage = `${HttpPipelineLogLevel[logLevel]}: ${message}`; 41 | switch (logLevel) { 42 | case HttpPipelineLogLevel.ERROR: 43 | console.error(logMessage); 44 | break; 45 | 46 | case HttpPipelineLogLevel.WARNING: 47 | console.warn(logMessage); 48 | break; 49 | 50 | case HttpPipelineLogLevel.INFO: 51 | console.log(logMessage); 52 | break; 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /lib/msRest.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. See License.txt in the project root for license information. 3 | 4 | /// 5 | 6 | export { 7 | WebResource, 8 | WebResourceLike, 9 | HttpRequestBody, 10 | RequestPrepareOptions, 11 | HttpMethods, 12 | ParameterValue, 13 | RequestOptionsBase, 14 | TransferProgressEvent, 15 | AbortSignalLike, 16 | } from "./webResource"; 17 | export { DefaultHttpClient } from "./defaultHttpClient"; 18 | export { CommonRequestInfo, CommonRequestInit, CommonResponse } from "./fetchHttpClient"; 19 | export { HttpClient } from "./httpClient"; 20 | export { HttpHeader, HttpHeaders, HttpHeadersLike, RawHttpHeaders } from "./httpHeaders"; 21 | export { HttpOperationResponse, HttpResponse, RestResponse } from "./httpOperationResponse"; 22 | export { HttpPipelineLogger } from "./httpPipelineLogger"; 23 | export { HttpPipelineLogLevel } from "./httpPipelineLogLevel"; 24 | export { RestError } from "./restError"; 25 | export { OperationArguments } from "./operationArguments"; 26 | export { 27 | OperationParameter, 28 | OperationQueryParameter, 29 | OperationURLParameter, 30 | ParameterPath, 31 | } from "./operationParameter"; 32 | export { OperationResponse } from "./operationResponse"; 33 | export { OperationSpec } from "./operationSpec"; 34 | export { 35 | AgentSettings, 36 | ProxySettings, 37 | ServiceClient, 38 | ServiceClientOptions, 39 | flattenResponse, 40 | } from "./serviceClient"; 41 | export { QueryCollectionFormat } from "./queryCollectionFormat"; 42 | export { Constants } from "./util/constants"; 43 | export { logPolicy } from "./policies/logPolicy"; 44 | export { 45 | BaseRequestPolicy, 46 | RequestPolicy, 47 | RequestPolicyFactory, 48 | RequestPolicyOptions, 49 | RequestPolicyOptionsLike, 50 | } from "./policies/requestPolicy"; 51 | export { generateClientRequestIdPolicy } from "./policies/generateClientRequestIdPolicy"; 52 | export { exponentialRetryPolicy } from "./policies/exponentialRetryPolicy"; 53 | export { systemErrorRetryPolicy } from "./policies/systemErrorRetryPolicy"; 54 | export { throttlingRetryPolicy } from "./policies/throttlingRetryPolicy"; 55 | export { agentPolicy } from "./policies/agentPolicy"; 56 | export { getDefaultProxySettings, proxyPolicy } from "./policies/proxyPolicy"; 57 | export { RedirectOptions, redirectPolicy } from "./policies/redirectPolicy"; 58 | export { signingPolicy } from "./policies/signingPolicy"; 59 | export { 60 | TelemetryInfo, 61 | userAgentPolicy, 62 | getDefaultUserAgentValue, 63 | } from "./policies/userAgentPolicy"; 64 | export { 65 | DeserializationContentTypes, 66 | deserializationPolicy, 67 | deserializeResponseBody, 68 | } from "./policies/deserializationPolicy"; 69 | export { 70 | MapperType, 71 | SimpleMapperType, 72 | CompositeMapperType, 73 | DictionaryMapperType, 74 | SequenceMapperType, 75 | EnumMapperType, 76 | Mapper, 77 | BaseMapper, 78 | CompositeMapper, 79 | SequenceMapper, 80 | DictionaryMapper, 81 | EnumMapper, 82 | MapperConstraints, 83 | PolymorphicDiscriminator, 84 | Serializer, 85 | UrlParameterValue, 86 | serializeObject, 87 | } from "./serializer"; 88 | export { 89 | stripRequest, 90 | stripResponse, 91 | delay, 92 | executePromisesSequentially, 93 | generateUuid, 94 | encodeUri, 95 | ServiceCallback, 96 | promiseToCallback, 97 | promiseToServiceCallback, 98 | isValidUuid, 99 | applyMixins, 100 | isNode, 101 | isDuration, 102 | } from "./util/utils"; 103 | export { URLBuilder, URLQuery } from "./url"; 104 | 105 | // Credentials 106 | export { TokenCredentials } from "./credentials/tokenCredentials"; 107 | export { TokenResponse } from "./credentials/tokenResponse"; 108 | export { BasicAuthenticationCredentials } from "./credentials/basicAuthenticationCredentials"; 109 | export { ApiKeyCredentials, ApiKeyCredentialOptions } from "./credentials/apiKeyCredentials"; 110 | export { ServiceClientCredentials } from "./credentials/serviceClientCredentials"; 111 | export { TopicCredentials } from "./credentials/topicCredentials"; 112 | export { DomainCredentials } from "./credentials/domainCredentials"; 113 | export { Authenticator } from "./credentials/credentials"; 114 | export { AzureIdentityCredentialAdapter } from "./credentials/azureIdentityTokenCredentialAdapter"; 115 | -------------------------------------------------------------------------------- /lib/nodeFetchHttpClient.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. See License.txt in the project root for license information. 3 | 4 | import * as http from "http"; 5 | import * as https from "https"; 6 | import node_fetch from "node-fetch"; 7 | 8 | import { 9 | CommonRequestInfo, 10 | CommonRequestInit, 11 | CommonResponse, 12 | FetchHttpClient, 13 | } from "./fetchHttpClient"; 14 | import { HttpOperationResponse } from "./httpOperationResponse"; 15 | import { WebResourceLike } from "./webResource"; 16 | import { createProxyAgent, ProxyAgent } from "./proxyAgent"; 17 | 18 | export class NodeFetchHttpClient extends FetchHttpClient { 19 | async fetch(input: CommonRequestInfo, init?: CommonRequestInit): Promise { 20 | return (node_fetch(input, init) as unknown) as Promise; 21 | } 22 | 23 | async prepareRequest(httpRequest: WebResourceLike): Promise> { 24 | const requestInit: Partial = {}; 25 | 26 | if (httpRequest.agentSettings) { 27 | const { http: httpAgent, https: httpsAgent } = httpRequest.agentSettings; 28 | if (httpsAgent && httpRequest.url.startsWith("https")) { 29 | requestInit.agent = httpsAgent; 30 | } else if (httpAgent) { 31 | requestInit.agent = httpAgent; 32 | } 33 | } else if (httpRequest.proxySettings) { 34 | const tunnel: ProxyAgent = createProxyAgent( 35 | httpRequest.url, 36 | httpRequest.proxySettings, 37 | httpRequest.headers 38 | ); 39 | requestInit.agent = tunnel.agent; 40 | } 41 | 42 | if (httpRequest.keepAlive === true) { 43 | if (requestInit.agent) { 44 | requestInit.agent.keepAlive = true; 45 | } else { 46 | const options: http.AgentOptions | https.AgentOptions = { keepAlive: true }; 47 | const agent = httpRequest.url.startsWith("https") 48 | ? new https.Agent(options) 49 | : new http.Agent(options); 50 | requestInit.agent = agent; 51 | } 52 | } 53 | 54 | return requestInit; 55 | } 56 | 57 | async processRequest(_operationResponse: HttpOperationResponse): Promise { 58 | /* no_op */ 59 | return; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /lib/operationArguments.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. See License.txt in the project root for license information. 3 | 4 | import { RequestOptionsBase } from "./webResource"; 5 | 6 | /** 7 | * A collection of properties that apply to a single invocation of an operation. 8 | */ 9 | export interface OperationArguments { 10 | /** 11 | * The parameters that were passed to the operation method. 12 | */ 13 | [parameterName: string]: any; 14 | 15 | /** 16 | * The optional arugments that are provided to an operation. 17 | */ 18 | options?: RequestOptionsBase; 19 | } 20 | -------------------------------------------------------------------------------- /lib/operationParameter.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. See License.txt in the project root for license information. 3 | 4 | import { QueryCollectionFormat } from "./queryCollectionFormat"; 5 | import { Mapper } from "./serializer"; 6 | 7 | export type ParameterPath = string | string[] | { [propertyName: string]: ParameterPath }; 8 | 9 | /** 10 | * A common interface that all Operation parameter's extend. 11 | */ 12 | export interface OperationParameter { 13 | /** 14 | * The path to this parameter's value in OperationArguments or the object that contains paths for 15 | * each property's value in OperationArguments. 16 | */ 17 | parameterPath: ParameterPath; 18 | 19 | /** 20 | * The mapper that defines how to validate and serialize this parameter's value. 21 | */ 22 | mapper: Mapper; 23 | } 24 | 25 | /** 26 | * A parameter for an operation that will be substituted into the operation's request URL. 27 | */ 28 | export interface OperationURLParameter extends OperationParameter { 29 | /** 30 | * Whether or not to skip encoding the URL parameter's value before adding it to the URL. 31 | */ 32 | skipEncoding?: boolean; 33 | } 34 | 35 | /** 36 | * A parameter for an operation that will be added as a query parameter to the operation's HTTP 37 | * request. 38 | */ 39 | export interface OperationQueryParameter extends OperationParameter { 40 | /** 41 | * Whether or not to skip encoding the query parameter's value before adding it to the URL. 42 | */ 43 | skipEncoding?: boolean; 44 | 45 | /** 46 | * If this query parameter's value is a collection, what type of format should the value be 47 | * converted to. 48 | */ 49 | collectionFormat?: QueryCollectionFormat; 50 | } 51 | 52 | /** 53 | * Get the path to this parameter's value as a dotted string (a.b.c). 54 | * @param parameter The parameter to get the path string for. 55 | * @returns The path to this parameter's value as a dotted string. 56 | */ 57 | export function getPathStringFromParameter(parameter: OperationParameter): string { 58 | return getPathStringFromParameterPath(parameter.parameterPath, parameter.mapper); 59 | } 60 | 61 | export function getPathStringFromParameterPath( 62 | parameterPath: ParameterPath, 63 | mapper: Mapper 64 | ): string { 65 | let result: string; 66 | if (typeof parameterPath === "string") { 67 | result = parameterPath; 68 | } else if (Array.isArray(parameterPath)) { 69 | result = parameterPath.join("."); 70 | } else { 71 | result = mapper.serializedName!; 72 | } 73 | return result; 74 | } 75 | -------------------------------------------------------------------------------- /lib/operationResponse.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. See License.txt in the project root for license information. 3 | 4 | import { Mapper } from "./serializer"; 5 | 6 | /** 7 | * An OperationResponse that can be returned from an operation request for a single status code. 8 | */ 9 | export interface OperationResponse { 10 | /** 11 | * The mapper that will be used to deserialize the response headers. 12 | */ 13 | headersMapper?: Mapper; 14 | 15 | /** 16 | * The mapper that will be used to deserialize the response body. 17 | */ 18 | bodyMapper?: Mapper; 19 | } 20 | -------------------------------------------------------------------------------- /lib/operationSpec.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. See License.txt in the project root for license information. 3 | 4 | import { 5 | OperationParameter, 6 | OperationQueryParameter, 7 | OperationURLParameter, 8 | } from "./operationParameter"; 9 | import { OperationResponse } from "./operationResponse"; 10 | import { MapperType, Serializer } from "./serializer"; 11 | import { HttpMethods } from "./webResource"; 12 | 13 | /** 14 | * A specification that defines an operation. 15 | */ 16 | export interface OperationSpec { 17 | /** 18 | * The serializer to use in this operation. 19 | */ 20 | readonly serializer: Serializer; 21 | 22 | /** 23 | * The HTTP method that should be used by requests for this operation. 24 | */ 25 | readonly httpMethod: HttpMethods; 26 | 27 | /** 28 | * The URL that was provided in the service's specification. This will still have all of the URL 29 | * template variables in it. If this is not provided when the OperationSpec is created, then it 30 | * will be populated by a "baseUri" property on the ServiceClient. 31 | */ 32 | readonly baseUrl?: string; 33 | 34 | /** 35 | * The fixed path for this operation's URL. This will still have all of the URL template variables 36 | * in it. 37 | */ 38 | readonly path?: string; 39 | 40 | /** 41 | * The content type of the request body. This value will be used as the "Content-Type" header if 42 | * it is provided. 43 | */ 44 | readonly contentType?: string; 45 | 46 | /** 47 | * The parameter that will be used to construct the HTTP request's body. 48 | */ 49 | readonly requestBody?: OperationParameter; 50 | 51 | /** 52 | * Whether or not this operation uses XML request and response bodies. 53 | */ 54 | readonly isXML?: boolean; 55 | 56 | /** 57 | * The parameters to the operation method that will be substituted into the constructed URL. 58 | */ 59 | readonly urlParameters?: ReadonlyArray; 60 | 61 | /** 62 | * The parameters to the operation method that will be added to the constructed URL's query. 63 | */ 64 | readonly queryParameters?: ReadonlyArray; 65 | 66 | /** 67 | * The parameters to the operation method that will be converted to headers on the operation's 68 | * HTTP request. 69 | */ 70 | readonly headerParameters?: ReadonlyArray; 71 | 72 | /** 73 | * The parameters to the operation method that will be used to create a formdata body for the 74 | * operation's HTTP request. 75 | */ 76 | readonly formDataParameters?: ReadonlyArray; 77 | 78 | /** 79 | * The different types of responses that this operation can return based on what status code is 80 | * returned. 81 | */ 82 | readonly responses: { [responseCode: string]: OperationResponse }; 83 | } 84 | 85 | export function isStreamOperation(operationSpec: OperationSpec): boolean { 86 | let result = false; 87 | for (const statusCode in operationSpec.responses) { 88 | const operationResponse: OperationResponse = operationSpec.responses[statusCode]; 89 | if ( 90 | operationResponse.bodyMapper && 91 | operationResponse.bodyMapper.type.name === MapperType.Stream 92 | ) { 93 | result = true; 94 | break; 95 | } 96 | } 97 | return result; 98 | } 99 | -------------------------------------------------------------------------------- /lib/policies/agentPolicy.browser.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. See License.txt in the project root for license information. 3 | 4 | import { AgentSettings } from "../serviceClient"; 5 | import { 6 | BaseRequestPolicy, 7 | RequestPolicy, 8 | RequestPolicyFactory, 9 | RequestPolicyOptionsLike, 10 | } from "./requestPolicy"; 11 | import { HttpOperationResponse } from "../httpOperationResponse"; 12 | import { WebResourceLike } from "../webResource"; 13 | 14 | const agentNotSupportedInBrowser = new Error("AgentPolicy is not supported in browser environment"); 15 | 16 | export function agentPolicy(_agentSettings?: AgentSettings): RequestPolicyFactory { 17 | return { 18 | create: (_nextPolicy: RequestPolicy, _options: RequestPolicyOptionsLike) => { 19 | throw agentNotSupportedInBrowser; 20 | }, 21 | }; 22 | } 23 | 24 | export class AgentPolicy extends BaseRequestPolicy { 25 | constructor(nextPolicy: RequestPolicy, options: RequestPolicyOptionsLike) { 26 | super(nextPolicy, options); 27 | throw agentNotSupportedInBrowser; 28 | } 29 | 30 | public sendRequest(_request: WebResourceLike): Promise { 31 | throw agentNotSupportedInBrowser; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /lib/policies/agentPolicy.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. See License.txt in the project root for license information. 3 | 4 | import { AgentSettings } from "../serviceClient"; 5 | import { 6 | BaseRequestPolicy, 7 | RequestPolicy, 8 | RequestPolicyFactory, 9 | RequestPolicyOptionsLike, 10 | } from "./requestPolicy"; 11 | import { HttpOperationResponse } from "../httpOperationResponse"; 12 | import { WebResourceLike } from "../webResource"; 13 | 14 | export function agentPolicy(agentSettings?: AgentSettings): RequestPolicyFactory { 15 | return { 16 | create: (nextPolicy: RequestPolicy, options: RequestPolicyOptionsLike) => { 17 | return new AgentPolicy(nextPolicy, options, agentSettings!); 18 | }, 19 | }; 20 | } 21 | 22 | export class AgentPolicy extends BaseRequestPolicy { 23 | agentSettings: AgentSettings; 24 | 25 | constructor( 26 | nextPolicy: RequestPolicy, 27 | options: RequestPolicyOptionsLike, 28 | agentSettings: AgentSettings 29 | ) { 30 | super(nextPolicy, options); 31 | this.agentSettings = agentSettings; 32 | } 33 | 34 | public sendRequest(request: WebResourceLike): Promise { 35 | if (!request.agentSettings) { 36 | request.agentSettings = this.agentSettings; 37 | } 38 | return this._nextPolicy.sendRequest(request); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /lib/policies/generateClientRequestIdPolicy.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. See License.txt in the project root for license information. 3 | 4 | import { HttpOperationResponse } from "../httpOperationResponse"; 5 | import * as utils from "../util/utils"; 6 | import { WebResourceLike } from "../webResource"; 7 | import { 8 | BaseRequestPolicy, 9 | RequestPolicy, 10 | RequestPolicyFactory, 11 | RequestPolicyOptionsLike, 12 | } from "./requestPolicy"; 13 | 14 | export function generateClientRequestIdPolicy( 15 | requestIdHeaderName = "x-ms-client-request-id" 16 | ): RequestPolicyFactory { 17 | return { 18 | create: (nextPolicy: RequestPolicy, options: RequestPolicyOptionsLike) => { 19 | return new GenerateClientRequestIdPolicy(nextPolicy, options, requestIdHeaderName); 20 | }, 21 | }; 22 | } 23 | 24 | export class GenerateClientRequestIdPolicy extends BaseRequestPolicy { 25 | constructor( 26 | nextPolicy: RequestPolicy, 27 | options: RequestPolicyOptionsLike, 28 | private _requestIdHeaderName: string 29 | ) { 30 | super(nextPolicy, options); 31 | } 32 | 33 | public sendRequest(request: WebResourceLike): Promise { 34 | if (!request.headers.contains(this._requestIdHeaderName)) { 35 | request.headers.set(this._requestIdHeaderName, utils.generateUuid()); 36 | } 37 | return this._nextPolicy.sendRequest(request); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /lib/policies/logPolicy.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. See License.txt in the project root for license information. 3 | 4 | import { HttpOperationResponse } from "../httpOperationResponse"; 5 | import { WebResourceLike } from "../webResource"; 6 | import { 7 | BaseRequestPolicy, 8 | RequestPolicy, 9 | RequestPolicyFactory, 10 | RequestPolicyOptionsLike, 11 | } from "./requestPolicy"; 12 | 13 | export function logPolicy(logger: any = console.log): RequestPolicyFactory { 14 | return { 15 | create: (nextPolicy: RequestPolicy, options: RequestPolicyOptionsLike) => { 16 | return new LogPolicy(nextPolicy, options, logger); 17 | }, 18 | }; 19 | } 20 | 21 | export class LogPolicy extends BaseRequestPolicy { 22 | logger?: any; 23 | 24 | constructor( 25 | nextPolicy: RequestPolicy, 26 | options: RequestPolicyOptionsLike, 27 | logger: any = console.log 28 | ) { 29 | super(nextPolicy, options); 30 | this.logger = logger; 31 | } 32 | 33 | public sendRequest(request: WebResourceLike): Promise { 34 | return this._nextPolicy.sendRequest(request).then((response) => logResponse(this, response)); 35 | } 36 | } 37 | 38 | function logResponse( 39 | policy: LogPolicy, 40 | response: HttpOperationResponse 41 | ): Promise { 42 | policy.logger(`>> Request: ${JSON.stringify(response.request, undefined, 2)}`); 43 | policy.logger(`>> Response status code: ${response.status}`); 44 | const responseBody = response.bodyAsText; 45 | policy.logger(`>> Body: ${responseBody}`); 46 | return Promise.resolve(response); 47 | } 48 | -------------------------------------------------------------------------------- /lib/policies/msRestUserAgentPolicy.browser.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. See License.txt in the project root for license information. 3 | 4 | /* 5 | * NOTE: When moving this file, please update "browser" section in package.json 6 | * and "plugins" section in webpack.testconfig.ts. 7 | */ 8 | 9 | import { TelemetryInfo } from "./userAgentPolicy"; 10 | 11 | interface NavigatorEx extends Navigator { 12 | // oscpu is not yet standards-compliant, but can not be undefined in TypeScript 3.6.2 13 | readonly oscpu: string; 14 | } 15 | 16 | export function getDefaultUserAgentKey(): string { 17 | return "x-ms-command-name"; 18 | } 19 | 20 | export function getPlatformSpecificData(): TelemetryInfo[] { 21 | const navigator = self.navigator as NavigatorEx; 22 | const osInfo = { 23 | key: "OS", 24 | value: (navigator.oscpu || navigator.platform).replace(" ", ""), 25 | }; 26 | 27 | return [osInfo]; 28 | } 29 | -------------------------------------------------------------------------------- /lib/policies/msRestUserAgentPolicy.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. See License.txt in the project root for license information. 3 | 4 | import * as os from "os"; 5 | import { TelemetryInfo } from "./userAgentPolicy"; 6 | import { Constants } from "../util/constants"; 7 | 8 | export function getDefaultUserAgentKey(): string { 9 | return Constants.HeaderConstants.USER_AGENT; 10 | } 11 | 12 | export function getPlatformSpecificData(): TelemetryInfo[] { 13 | const runtimeInfo = { 14 | key: "Node", 15 | value: process.version, 16 | }; 17 | 18 | const osInfo = { 19 | key: "OS", 20 | value: `(${os.arch()}-${os.type()}-${os.release()})`, 21 | }; 22 | 23 | return [runtimeInfo, osInfo]; 24 | } 25 | -------------------------------------------------------------------------------- /lib/policies/proxyPolicy.browser.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. See License.txt in the project root for license information. 3 | 4 | import { ProxySettings } from "../serviceClient"; 5 | import { 6 | BaseRequestPolicy, 7 | RequestPolicy, 8 | RequestPolicyFactory, 9 | RequestPolicyOptionsLike, 10 | } from "./requestPolicy"; 11 | import { HttpOperationResponse } from "../httpOperationResponse"; 12 | import { WebResourceLike } from "../webResource"; 13 | 14 | const proxyNotSupportedInBrowser = new Error("ProxyPolicy is not supported in browser environment"); 15 | 16 | export function getDefaultProxySettings(_proxyUrl?: string): ProxySettings | undefined { 17 | return undefined; 18 | } 19 | 20 | export function proxyPolicy(_proxySettings?: ProxySettings): RequestPolicyFactory { 21 | return { 22 | create: (_nextPolicy: RequestPolicy, _options: RequestPolicyOptionsLike) => { 23 | throw proxyNotSupportedInBrowser; 24 | }, 25 | }; 26 | } 27 | 28 | export class ProxyPolicy extends BaseRequestPolicy { 29 | constructor(nextPolicy: RequestPolicy, options: RequestPolicyOptionsLike) { 30 | super(nextPolicy, options); 31 | throw proxyNotSupportedInBrowser; 32 | } 33 | 34 | public sendRequest(_request: WebResourceLike): Promise { 35 | throw proxyNotSupportedInBrowser; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /lib/policies/proxyPolicy.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. See License.txt in the project root for license information. 3 | 4 | import { 5 | BaseRequestPolicy, 6 | RequestPolicy, 7 | RequestPolicyFactory, 8 | RequestPolicyOptionsLike, 9 | } from "./requestPolicy"; 10 | import { HttpOperationResponse } from "../httpOperationResponse"; 11 | import { ProxySettings } from "../serviceClient"; 12 | import { WebResourceLike } from "../webResource"; 13 | import { Constants } from "../util/constants"; 14 | import { URLBuilder } from "../url"; 15 | 16 | /** 17 | * @internal 18 | */ 19 | export const noProxyList: string[] = loadNoProxy(); 20 | const byPassedList: Map = new Map(); 21 | 22 | /** 23 | * @internal 24 | */ 25 | export function getEnvironmentValue(name: string): string | undefined { 26 | if (process.env[name]) { 27 | return process.env[name]; 28 | } else if (process.env[name.toLowerCase()]) { 29 | return process.env[name.toLowerCase()]; 30 | } 31 | return undefined; 32 | } 33 | 34 | function loadEnvironmentProxyValue(): string | undefined { 35 | if (!process) { 36 | return undefined; 37 | } 38 | 39 | const httpsProxy = getEnvironmentValue(Constants.HTTPS_PROXY); 40 | const allProxy = getEnvironmentValue(Constants.ALL_PROXY); 41 | const httpProxy = getEnvironmentValue(Constants.HTTP_PROXY); 42 | 43 | return httpsProxy || allProxy || httpProxy; 44 | } 45 | 46 | // Check whether the host of a given `uri` is in the noProxyList. 47 | // If there's a match, any request sent to the same host won't have the proxy settings set. 48 | // This implementation is a port of https://github.com/Azure/azure-sdk-for-net/blob/8cca811371159e527159c7eb65602477898683e2/sdk/core/Azure.Core/src/Pipeline/Internal/HttpEnvironmentProxy.cs#L210 49 | function isBypassed(uri: string): boolean | undefined { 50 | if (noProxyList.length === 0) { 51 | return false; 52 | } 53 | const host = URLBuilder.parse(uri).getHost()!; 54 | if (byPassedList.has(host)) { 55 | return byPassedList.get(host); 56 | } 57 | let isBypassedFlag = false; 58 | for (const pattern of noProxyList) { 59 | if (pattern[0] === ".") { 60 | // This should match either domain it self or any subdomain or host 61 | // .foo.com will match foo.com it self or *.foo.com 62 | if (host.endsWith(pattern)) { 63 | isBypassedFlag = true; 64 | } else { 65 | if (host.length === pattern.length - 1 && host === pattern.slice(1)) { 66 | isBypassedFlag = true; 67 | } 68 | } 69 | } else { 70 | if (host === pattern) { 71 | isBypassedFlag = true; 72 | } 73 | } 74 | } 75 | byPassedList.set(host, isBypassedFlag); 76 | return isBypassedFlag; 77 | } 78 | 79 | /** 80 | * @internal 81 | */ 82 | export function loadNoProxy(): string[] { 83 | const noProxy = getEnvironmentValue(Constants.NO_PROXY); 84 | if (noProxy) { 85 | return noProxy 86 | .split(",") 87 | .map((item) => item.trim()) 88 | .filter((item) => item.length); 89 | } 90 | 91 | return []; 92 | } 93 | 94 | /** 95 | * @internal 96 | */ 97 | function extractAuthFromUrl( 98 | url: string 99 | ): { username?: string; password?: string; urlWithoutAuth: string } { 100 | const atIndex = url.indexOf("@"); 101 | if (atIndex === -1) { 102 | return { urlWithoutAuth: url }; 103 | } 104 | 105 | const schemeIndex = url.indexOf("://"); 106 | const authStart = schemeIndex !== -1 ? schemeIndex + 3 : 0; 107 | const auth = url.substring(authStart, atIndex); 108 | const colonIndex = auth.indexOf(":"); 109 | const hasPassword = colonIndex !== -1; 110 | const username = hasPassword ? auth.substring(0, colonIndex) : auth; 111 | const password = hasPassword ? auth.substring(colonIndex + 1) : undefined; 112 | const urlWithoutAuth = url.substring(0, authStart) + url.substring(atIndex + 1); 113 | return { 114 | username, 115 | password, 116 | urlWithoutAuth, 117 | }; 118 | } 119 | 120 | export function getDefaultProxySettings(proxyUrl?: string): ProxySettings | undefined { 121 | if (!proxyUrl) { 122 | proxyUrl = loadEnvironmentProxyValue(); 123 | if (!proxyUrl) { 124 | return undefined; 125 | } 126 | } 127 | 128 | const { username, password, urlWithoutAuth } = extractAuthFromUrl(proxyUrl); 129 | const parsedUrl = URLBuilder.parse(urlWithoutAuth); 130 | const schema = parsedUrl.getScheme() ? parsedUrl.getScheme() + "://" : ""; 131 | return { 132 | host: schema + parsedUrl.getHost(), 133 | port: Number.parseInt(parsedUrl.getPort() || "80"), 134 | username, 135 | password, 136 | }; 137 | } 138 | 139 | export function proxyPolicy(proxySettings?: ProxySettings): RequestPolicyFactory { 140 | if (!proxySettings) { 141 | proxySettings = getDefaultProxySettings(); 142 | } 143 | return { 144 | create: (nextPolicy: RequestPolicy, options: RequestPolicyOptionsLike) => { 145 | return new ProxyPolicy(nextPolicy, options, proxySettings!); 146 | }, 147 | }; 148 | } 149 | 150 | export class ProxyPolicy extends BaseRequestPolicy { 151 | proxySettings: ProxySettings; 152 | 153 | constructor( 154 | nextPolicy: RequestPolicy, 155 | options: RequestPolicyOptionsLike, 156 | proxySettings: ProxySettings 157 | ) { 158 | super(nextPolicy, options); 159 | this.proxySettings = proxySettings; 160 | } 161 | 162 | public sendRequest(request: WebResourceLike): Promise { 163 | if (!request.proxySettings && !isBypassed(request.url)) { 164 | request.proxySettings = this.proxySettings; 165 | } 166 | return this._nextPolicy.sendRequest(request); 167 | } 168 | } 169 | -------------------------------------------------------------------------------- /lib/policies/redirectPolicy.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. See License.txt in the project root for license information. 3 | 4 | import { HttpOperationResponse } from "../httpOperationResponse"; 5 | import { URLBuilder } from "../url"; 6 | import { WebResourceLike } from "../webResource"; 7 | import { 8 | BaseRequestPolicy, 9 | RequestPolicy, 10 | RequestPolicyFactory, 11 | RequestPolicyOptionsLike, 12 | } from "./requestPolicy"; 13 | 14 | /** 15 | * Options for how redirect responses are handled. 16 | */ 17 | export interface RedirectOptions { 18 | /* 19 | * When true, redirect responses are followed. Defaults to true. 20 | */ 21 | handleRedirects: boolean; 22 | 23 | /* 24 | * The maximum number of times the redirect URL will be tried before 25 | * failing. Defaults to 20. 26 | */ 27 | maxRetries?: number; 28 | } 29 | 30 | export const DefaultRedirectOptions: RedirectOptions = { 31 | handleRedirects: true, 32 | maxRetries: 20, 33 | }; 34 | 35 | export function redirectPolicy(maximumRetries = 20): RequestPolicyFactory { 36 | return { 37 | create: (nextPolicy: RequestPolicy, options: RequestPolicyOptionsLike) => { 38 | return new RedirectPolicy(nextPolicy, options, maximumRetries); 39 | }, 40 | }; 41 | } 42 | 43 | export class RedirectPolicy extends BaseRequestPolicy { 44 | constructor( 45 | nextPolicy: RequestPolicy, 46 | options: RequestPolicyOptionsLike, 47 | readonly maxRetries = 20 48 | ) { 49 | super(nextPolicy, options); 50 | } 51 | 52 | public sendRequest(request: WebResourceLike): Promise { 53 | return this._nextPolicy 54 | .sendRequest(request) 55 | .then((response) => handleRedirect(this, response, 0)); 56 | } 57 | } 58 | 59 | function handleRedirect( 60 | policy: RedirectPolicy, 61 | response: HttpOperationResponse, 62 | currentRetries: number 63 | ): Promise { 64 | const { request, status } = response; 65 | const locationHeader = response.headers.get("location"); 66 | if ( 67 | locationHeader && 68 | (status === 300 || 69 | (status === 301 && ["GET", "HEAD"].includes(request.method)) || 70 | (status === 302 && ["GET", "POST", "HEAD"].includes(request.method)) || 71 | (status === 303 && "POST" === request.method) || 72 | status === 307) && 73 | ((request.redirectLimit !== undefined && currentRetries < request.redirectLimit) || 74 | (request.redirectLimit === undefined && currentRetries < policy.maxRetries)) 75 | ) { 76 | const builder = URLBuilder.parse(request.url); 77 | builder.setPath(locationHeader); 78 | request.url = builder.toString(); 79 | 80 | // POST request with Status code 302 and 303 should be converted into a 81 | // redirected GET request if the redirect url is present in the location header 82 | // reference: https://tools.ietf.org/html/rfc7231#page-57 && https://fetch.spec.whatwg.org/#http-redirect-fetch 83 | if ((status === 302 || status === 303) && request.method === "POST") { 84 | request.method = "GET"; 85 | delete request.body; 86 | } 87 | 88 | return policy._nextPolicy 89 | .sendRequest(request) 90 | .then((res) => handleRedirect(policy, res, currentRetries + 1)) 91 | .then((res) => recordRedirect(res, request.url)); 92 | } 93 | 94 | return Promise.resolve(response); 95 | } 96 | 97 | function recordRedirect(response: HttpOperationResponse, redirect: string): HttpOperationResponse { 98 | // This is called as the recursive calls to handleRedirect() unwind, 99 | // only record the deepest/last redirect 100 | if (!response.redirected) { 101 | response.redirected = true; 102 | response.url = redirect; 103 | } 104 | return response; 105 | } 106 | -------------------------------------------------------------------------------- /lib/policies/requestPolicy.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. See License.txt in the project root for license information. 3 | 4 | import { HttpOperationResponse } from "../httpOperationResponse"; 5 | import { HttpPipelineLogger } from "../httpPipelineLogger"; 6 | import { HttpPipelineLogLevel } from "../httpPipelineLogLevel"; 7 | import { WebResourceLike } from "../webResource"; 8 | 9 | /** 10 | * Creates a new RequestPolicy per-request that uses the provided nextPolicy. 11 | */ 12 | export type RequestPolicyFactory = { 13 | create(nextPolicy: RequestPolicy, options: RequestPolicyOptionsLike): RequestPolicy; 14 | }; 15 | 16 | export interface RequestPolicy { 17 | sendRequest(httpRequest: WebResourceLike): Promise; 18 | } 19 | 20 | export abstract class BaseRequestPolicy implements RequestPolicy { 21 | protected constructor( 22 | readonly _nextPolicy: RequestPolicy, 23 | readonly _options: RequestPolicyOptionsLike 24 | ) {} 25 | 26 | public abstract sendRequest(webResource: WebResourceLike): Promise; 27 | 28 | /** 29 | * Get whether or not a log with the provided log level should be logged. 30 | * @param logLevel The log level of the log that will be logged. 31 | * @returns Whether or not a log with the provided log level should be logged. 32 | */ 33 | public shouldLog(logLevel: HttpPipelineLogLevel): boolean { 34 | return this._options.shouldLog(logLevel); 35 | } 36 | 37 | /** 38 | * Attempt to log the provided message to the provided logger. If no logger was provided or if 39 | * the log level does not meat the logger's threshold, then nothing will be logged. 40 | * @param logLevel The log level of this log. 41 | * @param message The message of this log. 42 | */ 43 | public log(logLevel: HttpPipelineLogLevel, message: string): void { 44 | this._options.log(logLevel, message); 45 | } 46 | } 47 | 48 | /** 49 | * Optional properties that can be used when creating a RequestPolicy. 50 | */ 51 | export interface RequestPolicyOptionsLike { 52 | /** 53 | * Get whether or not a log with the provided log level should be logged. 54 | * @param logLevel The log level of the log that will be logged. 55 | * @returns Whether or not a log with the provided log level should be logged. 56 | */ 57 | shouldLog(logLevel: HttpPipelineLogLevel): boolean; 58 | 59 | /** 60 | * Attempt to log the provided message to the provided logger. If no logger was provided or if 61 | * the log level does not meet the logger's threshold, then nothing will be logged. 62 | * @param logLevel The log level of this log. 63 | * @param message The message of this log. 64 | */ 65 | log(logLevel: HttpPipelineLogLevel, message: string): void; 66 | } 67 | 68 | /** 69 | * Optional properties that can be used when creating a RequestPolicy. 70 | */ 71 | export class RequestPolicyOptions implements RequestPolicyOptionsLike { 72 | constructor(private _logger?: HttpPipelineLogger) {} 73 | 74 | /** 75 | * Get whether or not a log with the provided log level should be logged. 76 | * @param logLevel The log level of the log that will be logged. 77 | * @returns Whether or not a log with the provided log level should be logged. 78 | */ 79 | public shouldLog(logLevel: HttpPipelineLogLevel): boolean { 80 | return ( 81 | !!this._logger && 82 | logLevel !== HttpPipelineLogLevel.OFF && 83 | logLevel <= this._logger.minimumLogLevel 84 | ); 85 | } 86 | 87 | /** 88 | * Attempt to log the provided message to the provided logger. If no logger was provided or if 89 | * the log level does not meat the logger's threshold, then nothing will be logged. 90 | * @param logLevel The log level of this log. 91 | * @param message The message of this log. 92 | */ 93 | public log(logLevel: HttpPipelineLogLevel, message: string): void { 94 | if (this._logger && this.shouldLog(logLevel)) { 95 | this._logger.log(logLevel, message); 96 | } 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /lib/policies/signingPolicy.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. See License.txt in the project root for license information. 3 | 4 | import { ServiceClientCredentials } from "../credentials/serviceClientCredentials"; 5 | import { HttpOperationResponse } from "../httpOperationResponse"; 6 | import { WebResourceLike } from "../webResource"; 7 | import { 8 | BaseRequestPolicy, 9 | RequestPolicyFactory, 10 | RequestPolicy, 11 | RequestPolicyOptionsLike, 12 | } from "./requestPolicy"; 13 | 14 | export function signingPolicy( 15 | authenticationProvider: ServiceClientCredentials 16 | ): RequestPolicyFactory { 17 | return { 18 | create: (nextPolicy: RequestPolicy, options: RequestPolicyOptionsLike) => { 19 | return new SigningPolicy(nextPolicy, options, authenticationProvider); 20 | }, 21 | }; 22 | } 23 | 24 | export class SigningPolicy extends BaseRequestPolicy { 25 | constructor( 26 | nextPolicy: RequestPolicy, 27 | options: RequestPolicyOptionsLike, 28 | public authenticationProvider: ServiceClientCredentials 29 | ) { 30 | super(nextPolicy, options); 31 | } 32 | 33 | signRequest(request: WebResourceLike): Promise { 34 | return this.authenticationProvider.signRequest(request); 35 | } 36 | 37 | public sendRequest(request: WebResourceLike): Promise { 38 | return this.signRequest(request).then((nextRequest) => 39 | this._nextPolicy.sendRequest(nextRequest) 40 | ); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /lib/policies/systemErrorRetryPolicy.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. See License.txt in the project root for license information. 3 | 4 | import { HttpOperationResponse } from "../httpOperationResponse"; 5 | import * as utils from "../util/utils"; 6 | import { WebResourceLike } from "../webResource"; 7 | import { 8 | BaseRequestPolicy, 9 | RequestPolicy, 10 | RequestPolicyFactory, 11 | RequestPolicyOptionsLike, 12 | } from "./requestPolicy"; 13 | 14 | export interface RetryData { 15 | retryCount: number; 16 | retryInterval: number; 17 | error?: RetryError; 18 | } 19 | 20 | export interface RetryError extends Error { 21 | message: string; 22 | code?: string; 23 | innerError?: RetryError; 24 | } 25 | 26 | export function systemErrorRetryPolicy( 27 | retryCount?: number, 28 | retryInterval?: number, 29 | minRetryInterval?: number, 30 | maxRetryInterval?: number 31 | ): RequestPolicyFactory { 32 | return { 33 | create: (nextPolicy: RequestPolicy, options: RequestPolicyOptionsLike) => { 34 | return new SystemErrorRetryPolicy( 35 | nextPolicy, 36 | options, 37 | retryCount, 38 | retryInterval, 39 | minRetryInterval, 40 | maxRetryInterval 41 | ); 42 | }, 43 | }; 44 | } 45 | 46 | /** 47 | * @class 48 | * Instantiates a new "ExponentialRetryPolicyFilter" instance. 49 | * 50 | * @constructor 51 | * @param {number} retryCount The client retry count. 52 | * @param {number} retryInterval The client retry interval, in milliseconds. 53 | * @param {number} minRetryInterval The minimum retry interval, in milliseconds. 54 | * @param {number} maxRetryInterval The maximum retry interval, in milliseconds. 55 | */ 56 | export class SystemErrorRetryPolicy extends BaseRequestPolicy { 57 | retryCount: number; 58 | retryInterval: number; 59 | minRetryInterval: number; 60 | maxRetryInterval: number; 61 | DEFAULT_CLIENT_RETRY_INTERVAL = 1000 * 30; 62 | DEFAULT_CLIENT_RETRY_COUNT = 3; 63 | DEFAULT_CLIENT_MAX_RETRY_INTERVAL = 1000 * 90; 64 | DEFAULT_CLIENT_MIN_RETRY_INTERVAL = 1000 * 3; 65 | 66 | constructor( 67 | nextPolicy: RequestPolicy, 68 | options: RequestPolicyOptionsLike, 69 | retryCount?: number, 70 | retryInterval?: number, 71 | minRetryInterval?: number, 72 | maxRetryInterval?: number 73 | ) { 74 | super(nextPolicy, options); 75 | this.retryCount = typeof retryCount === "number" ? retryCount : this.DEFAULT_CLIENT_RETRY_COUNT; 76 | this.retryInterval = 77 | typeof retryInterval === "number" ? retryInterval : this.DEFAULT_CLIENT_RETRY_INTERVAL; 78 | this.minRetryInterval = 79 | typeof minRetryInterval === "number" 80 | ? minRetryInterval 81 | : this.DEFAULT_CLIENT_MIN_RETRY_INTERVAL; 82 | this.maxRetryInterval = 83 | typeof maxRetryInterval === "number" 84 | ? maxRetryInterval 85 | : this.DEFAULT_CLIENT_MAX_RETRY_INTERVAL; 86 | } 87 | 88 | public sendRequest(request: WebResourceLike): Promise { 89 | return this._nextPolicy 90 | .sendRequest(request.clone()) 91 | .catch((error) => retry(this, request, error.response, error)); 92 | } 93 | } 94 | 95 | /** 96 | * Determines if the operation should be retried and how long to wait until the next retry. 97 | * 98 | * @param {number} statusCode The HTTP status code. 99 | * @param {RetryData} retryData The retry data. 100 | * @return {boolean} True if the operation qualifies for a retry; false otherwise. 101 | */ 102 | function shouldRetry(policy: SystemErrorRetryPolicy, retryData: RetryData): boolean { 103 | let currentCount; 104 | if (!retryData) { 105 | throw new Error("retryData for the SystemErrorRetryPolicyFilter cannot be null."); 106 | } else { 107 | currentCount = retryData && retryData.retryCount; 108 | } 109 | return currentCount < policy.retryCount; 110 | } 111 | 112 | /** 113 | * Updates the retry data for the next attempt. 114 | * 115 | * @param {RetryData} retryData The retry data. 116 | * @param {object} err The operation"s error, if any. 117 | */ 118 | function updateRetryData( 119 | policy: SystemErrorRetryPolicy, 120 | retryData?: RetryData, 121 | err?: RetryError 122 | ): RetryData { 123 | if (!retryData) { 124 | retryData = { 125 | retryCount: 0, 126 | retryInterval: 0, 127 | }; 128 | } 129 | 130 | if (err) { 131 | if (retryData.error) { 132 | err.innerError = retryData.error; 133 | } 134 | 135 | retryData.error = err; 136 | } 137 | 138 | // Adjust retry count 139 | retryData.retryCount++; 140 | 141 | // Adjust retry interval 142 | let incrementDelta = Math.pow(2, retryData.retryCount) - 1; 143 | const boundedRandDelta = 144 | policy.retryInterval * 0.8 + Math.floor(Math.random() * (policy.retryInterval * 0.4)); 145 | incrementDelta *= boundedRandDelta; 146 | 147 | retryData.retryInterval = Math.min( 148 | policy.minRetryInterval + incrementDelta, 149 | policy.maxRetryInterval 150 | ); 151 | 152 | return retryData; 153 | } 154 | 155 | async function retry( 156 | policy: SystemErrorRetryPolicy, 157 | request: WebResourceLike, 158 | operationResponse: HttpOperationResponse, 159 | err?: RetryError, 160 | retryData?: RetryData 161 | ): Promise { 162 | retryData = updateRetryData(policy, retryData, err); 163 | if ( 164 | err && 165 | err.code && 166 | shouldRetry(policy, retryData) && 167 | (err.code === "ETIMEDOUT" || 168 | err.code === "ESOCKETTIMEDOUT" || 169 | err.code === "ECONNREFUSED" || 170 | err.code === "ECONNRESET" || 171 | err.code === "ENOENT") 172 | ) { 173 | // If previous operation ended with an error and the policy allows a retry, do that 174 | try { 175 | await utils.delay(retryData.retryInterval); 176 | return policy._nextPolicy.sendRequest(request.clone()); 177 | } catch (error) { 178 | return retry(policy, request, operationResponse, error, retryData); 179 | } 180 | } else { 181 | if (err) { 182 | // If the operation failed in the end, return all errors instead of just the last one 183 | return Promise.reject(retryData.error); 184 | } 185 | return operationResponse; 186 | } 187 | } 188 | -------------------------------------------------------------------------------- /lib/policies/throttlingRetryPolicy.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. See License.txt in the project root for license information. 3 | 4 | import { 5 | BaseRequestPolicy, 6 | RequestPolicy, 7 | RequestPolicyOptionsLike, 8 | RequestPolicyFactory, 9 | } from "./requestPolicy"; 10 | import { WebResourceLike } from "../webResource"; 11 | import { HttpOperationResponse } from "../httpOperationResponse"; 12 | import { Constants } from "../util/constants"; 13 | import { delay } from "../util/utils"; 14 | 15 | const StatusCodes = Constants.HttpConstants.StatusCodes; 16 | const DEFAULT_RETRY_COUNT = 3; 17 | 18 | /** 19 | * Options that control how to retry on response status code 429. 20 | */ 21 | export interface ThrottlingRetryOptions { 22 | /** 23 | * The maximum number of retry attempts. Defaults to 3. 24 | */ 25 | maxRetries?: number; 26 | } 27 | 28 | export function throttlingRetryPolicy( 29 | maxRetries: number = DEFAULT_RETRY_COUNT 30 | ): RequestPolicyFactory { 31 | return { 32 | create: (nextPolicy: RequestPolicy, options: RequestPolicyOptionsLike) => { 33 | return new ThrottlingRetryPolicy(nextPolicy, options, maxRetries); 34 | }, 35 | }; 36 | } 37 | 38 | /** 39 | * To learn more, please refer to 40 | * https://docs.microsoft.com/en-us/azure/azure-resource-manager/resource-manager-request-limits, 41 | * https://docs.microsoft.com/en-us/azure/azure-subscription-service-limits and 42 | * https://docs.microsoft.com/en-us/azure/virtual-machines/troubleshooting/troubleshooting-throttling-errors 43 | */ 44 | export class ThrottlingRetryPolicy extends BaseRequestPolicy { 45 | private retryLimit: number; 46 | 47 | constructor(nextPolicy: RequestPolicy, options: RequestPolicyOptionsLike, retryLimit: number) { 48 | super(nextPolicy, options); 49 | this.retryLimit = retryLimit; 50 | } 51 | 52 | public async sendRequest(httpRequest: WebResourceLike): Promise { 53 | return this._nextPolicy.sendRequest(httpRequest.clone()).then((response) => { 54 | return this.retry(httpRequest, response, 0); 55 | }); 56 | } 57 | 58 | private async retry( 59 | httpRequest: WebResourceLike, 60 | httpResponse: HttpOperationResponse, 61 | retryCount: number 62 | ): Promise { 63 | if (httpResponse.status !== StatusCodes.TooManyRequests) { 64 | return httpResponse; 65 | } 66 | 67 | const retryAfterHeader: string | undefined = httpResponse.headers.get( 68 | Constants.HeaderConstants.RETRY_AFTER 69 | ); 70 | 71 | if (retryAfterHeader && retryCount < this.retryLimit) { 72 | const delayInMs: number | undefined = ThrottlingRetryPolicy.parseRetryAfterHeader( 73 | retryAfterHeader 74 | ); 75 | if (delayInMs) { 76 | await delay(delayInMs); 77 | const res = await this._nextPolicy.sendRequest(httpRequest); 78 | return this.retry(httpRequest, res, retryCount + 1); 79 | } 80 | } 81 | 82 | return httpResponse; 83 | } 84 | 85 | public static parseRetryAfterHeader(headerValue: string): number | undefined { 86 | const retryAfterInSeconds = Number(headerValue); 87 | if (Number.isNaN(retryAfterInSeconds)) { 88 | return ThrottlingRetryPolicy.parseDateRetryAfterHeader(headerValue); 89 | } else { 90 | return retryAfterInSeconds * 1000; 91 | } 92 | } 93 | 94 | public static parseDateRetryAfterHeader(headerValue: string): number | undefined { 95 | try { 96 | const now: number = Date.now(); 97 | const date: number = Date.parse(headerValue); 98 | const diff = date - now; 99 | 100 | return Number.isNaN(diff) ? undefined : diff; 101 | } catch (error) { 102 | return undefined; 103 | } 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /lib/policies/userAgentPolicy.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. See License.txt in the project root for license information. 3 | 4 | import { HttpHeaders } from "../httpHeaders"; 5 | import { HttpOperationResponse } from "../httpOperationResponse"; 6 | import { Constants } from "../util/constants"; 7 | import { WebResourceLike } from "../webResource"; 8 | import { getDefaultUserAgentKey, getPlatformSpecificData } from "./msRestUserAgentPolicy"; 9 | import { 10 | BaseRequestPolicy, 11 | RequestPolicy, 12 | RequestPolicyFactory, 13 | RequestPolicyOptionsLike, 14 | } from "./requestPolicy"; 15 | 16 | export type TelemetryInfo = { key?: string; value?: string }; 17 | 18 | function getRuntimeInfo(): TelemetryInfo[] { 19 | const msRestRuntime = { 20 | key: "ms-rest-js", 21 | value: Constants.msRestVersion, 22 | }; 23 | 24 | return [msRestRuntime]; 25 | } 26 | 27 | function getUserAgentString( 28 | telemetryInfo: TelemetryInfo[], 29 | keySeparator = " ", 30 | valueSeparator = "/" 31 | ): string { 32 | return telemetryInfo 33 | .map((info) => { 34 | const value = info.value ? `${valueSeparator}${info.value}` : ""; 35 | return `${info.key}${value}`; 36 | }) 37 | .join(keySeparator); 38 | } 39 | 40 | export const getDefaultUserAgentHeaderName = getDefaultUserAgentKey; 41 | 42 | export function getDefaultUserAgentValue(): string { 43 | const runtimeInfo = getRuntimeInfo(); 44 | const platformSpecificData = getPlatformSpecificData(); 45 | const userAgent = getUserAgentString(runtimeInfo.concat(platformSpecificData)); 46 | return userAgent; 47 | } 48 | 49 | export function userAgentPolicy(userAgentData?: TelemetryInfo): RequestPolicyFactory { 50 | const key: string = 51 | !userAgentData || userAgentData.key == undefined ? getDefaultUserAgentKey() : userAgentData.key; 52 | const value: string = 53 | !userAgentData || userAgentData.value == undefined 54 | ? getDefaultUserAgentValue() 55 | : userAgentData.value; 56 | 57 | return { 58 | create: (nextPolicy: RequestPolicy, options: RequestPolicyOptionsLike) => { 59 | return new UserAgentPolicy(nextPolicy, options, key, value); 60 | }, 61 | }; 62 | } 63 | 64 | export class UserAgentPolicy extends BaseRequestPolicy { 65 | constructor( 66 | readonly _nextPolicy: RequestPolicy, 67 | readonly _options: RequestPolicyOptionsLike, 68 | protected headerKey: string, 69 | protected headerValue: string 70 | ) { 71 | super(_nextPolicy, _options); 72 | } 73 | 74 | sendRequest(request: WebResourceLike): Promise { 75 | this.addUserAgentHeader(request); 76 | return this._nextPolicy.sendRequest(request); 77 | } 78 | 79 | addUserAgentHeader(request: WebResourceLike): void { 80 | if (!request.headers) { 81 | request.headers = new HttpHeaders(); 82 | } 83 | 84 | if (!request.headers.get(this.headerKey) && this.headerValue) { 85 | request.headers.set(this.headerKey, this.headerValue); 86 | } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /lib/proxyAgent.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. See License.txt in the project root for license information. 3 | 4 | import * as http from "http"; 5 | import * as https from "https"; 6 | import * as tunnel from "tunnel"; 7 | 8 | import { ProxySettings } from "./serviceClient"; 9 | import { URLBuilder } from "./url"; 10 | import { HttpHeadersLike } from "./httpHeaders"; 11 | 12 | export type ProxyAgent = { isHttps: boolean; agent: http.Agent | https.Agent }; 13 | export function createProxyAgent( 14 | requestUrl: string, 15 | proxySettings: ProxySettings, 16 | headers?: HttpHeadersLike 17 | ): ProxyAgent { 18 | const tunnelOptions: tunnel.HttpsOverHttpsOptions = { 19 | proxy: { 20 | host: URLBuilder.parse(proxySettings.host).getHost() as string, 21 | port: proxySettings.port, 22 | headers: (headers && headers.rawHeaders()) || {}, 23 | }, 24 | }; 25 | 26 | if (proxySettings.username && proxySettings.password) { 27 | tunnelOptions.proxy!.proxyAuth = `${proxySettings.username}:${proxySettings.password}`; 28 | } else if (proxySettings.username) { 29 | tunnelOptions.proxy!.proxyAuth = `${proxySettings.username}`; 30 | } 31 | 32 | const requestScheme = URLBuilder.parse(requestUrl).getScheme() || ""; 33 | const isRequestHttps = requestScheme.toLowerCase() === "https"; 34 | const proxyScheme = URLBuilder.parse(proxySettings.host).getScheme() || ""; 35 | const isProxyHttps = proxyScheme.toLowerCase() === "https"; 36 | 37 | const proxyAgent = { 38 | isHttps: isRequestHttps, 39 | agent: createTunnel(isRequestHttps, isProxyHttps, tunnelOptions), 40 | }; 41 | 42 | return proxyAgent; 43 | } 44 | 45 | // Duplicate tunnel.HttpsOverHttpsOptions to avoid exporting createTunnel() with dependency on @types/tunnel 46 | // createIunnel() is only imported by tests. 47 | export interface HttpsProxyOptions { 48 | host: string; 49 | port: number; 50 | localAddress?: string; 51 | proxyAuth?: string; 52 | headers?: { [key: string]: any }; 53 | ca?: Buffer[]; 54 | servername?: string; 55 | key?: Buffer; 56 | cert?: Buffer; 57 | } 58 | 59 | interface HttpsOverHttpsOptions { 60 | maxSockets?: number; 61 | ca?: Buffer[]; 62 | key?: Buffer; 63 | cert?: Buffer; 64 | proxy?: HttpsProxyOptions; 65 | } 66 | 67 | export function createTunnel( 68 | isRequestHttps: boolean, 69 | isProxyHttps: boolean, 70 | tunnelOptions: HttpsOverHttpsOptions 71 | ): http.Agent | https.Agent { 72 | if (isRequestHttps && isProxyHttps) { 73 | return tunnel.httpsOverHttps(tunnelOptions); 74 | } else if (isRequestHttps && !isProxyHttps) { 75 | return tunnel.httpsOverHttp(tunnelOptions); 76 | } else if (!isRequestHttps && isProxyHttps) { 77 | return tunnel.httpOverHttps(tunnelOptions); 78 | } else { 79 | return tunnel.httpOverHttp(tunnelOptions); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /lib/queryCollectionFormat.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. See License.txt in the project root for license information. 3 | 4 | /** 5 | * The format that will be used to join an array of values together for a query parameter value. 6 | */ 7 | export enum QueryCollectionFormat { 8 | Csv = ",", 9 | Ssv = " ", 10 | Tsv = "\t", 11 | Pipes = "|", 12 | Multi = "Multi", 13 | } 14 | -------------------------------------------------------------------------------- /lib/restError.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. See License.txt in the project root for license information. 3 | 4 | import { HttpOperationResponse } from "./httpOperationResponse"; 5 | import { WebResourceLike } from "./webResource"; 6 | 7 | export class RestError extends Error { 8 | static readonly REQUEST_SEND_ERROR: string = "REQUEST_SEND_ERROR"; 9 | static readonly REQUEST_ABORTED_ERROR: string = "REQUEST_ABORTED_ERROR"; 10 | static readonly PARSE_ERROR: string = "PARSE_ERROR"; 11 | 12 | code?: string; 13 | statusCode?: number; 14 | request?: WebResourceLike; 15 | response?: HttpOperationResponse; 16 | body?: any; 17 | constructor( 18 | message: string, 19 | code?: string, 20 | statusCode?: number, 21 | request?: WebResourceLike, 22 | response?: HttpOperationResponse, 23 | body?: any 24 | ) { 25 | super(message); 26 | this.code = code; 27 | this.statusCode = statusCode; 28 | this.request = request; 29 | this.response = response; 30 | this.body = body; 31 | 32 | Object.setPrototypeOf(this, RestError.prototype); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /lib/util/base64.browser.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. See License.txt in the project root for license information. 3 | 4 | /** 5 | * Encodes a string in base64 format. 6 | * @param value the string to encode 7 | */ 8 | export function encodeString(value: string): string { 9 | return btoa(value); 10 | } 11 | 12 | /** 13 | * Encodes a byte array in base64 format. 14 | * @param value the Uint8Aray to encode 15 | */ 16 | export function encodeByteArray(value: Uint8Array): string { 17 | let str = ""; 18 | for (let i = 0; i < value.length; i++) { 19 | str += String.fromCharCode(value[i]); 20 | } 21 | return btoa(str); 22 | } 23 | 24 | /** 25 | * Decodes a base64 string into a byte array. 26 | * @param value the base64 string to decode 27 | */ 28 | export function decodeString(value: string): Uint8Array { 29 | const byteString = atob(value); 30 | const arr = new Uint8Array(byteString.length); 31 | for (let i = 0; i < byteString.length; i++) { 32 | arr[i] = byteString.charCodeAt(i); 33 | } 34 | return arr; 35 | } 36 | -------------------------------------------------------------------------------- /lib/util/base64.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. See License.txt in the project root for license information. 3 | 4 | /** 5 | * Encodes a string in base64 format. 6 | * @param value the string to encode 7 | */ 8 | export function encodeString(value: string): string { 9 | return Buffer.from(value).toString("base64"); 10 | } 11 | 12 | /** 13 | * Encodes a byte array in base64 format. 14 | * @param value the Uint8Aray to encode 15 | */ 16 | export function encodeByteArray(value: Uint8Array): string { 17 | // Buffer.from accepts | -- the TypeScript definition is off here 18 | // https://nodejs.org/api/buffer.html#buffer_class_method_buffer_from_arraybuffer_byteoffset_length 19 | const bufferValue = value instanceof Buffer ? value : Buffer.from(value.buffer as ArrayBuffer); 20 | return bufferValue.toString("base64"); 21 | } 22 | 23 | /** 24 | * Decodes a base64 string into a byte array. 25 | * @param value the base64 string to decode 26 | */ 27 | export function decodeString(value: string): Uint8Array { 28 | return Buffer.from(value, "base64"); 29 | } 30 | -------------------------------------------------------------------------------- /lib/util/constants.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. See License.txt in the project root for license information. 3 | 4 | export const Constants = { 5 | /** 6 | * The ms-rest version 7 | * @const 8 | * @type {string} 9 | */ 10 | msRestVersion: "2.7.0", 11 | 12 | /** 13 | * Specifies HTTP. 14 | * 15 | * @const 16 | * @type {string} 17 | */ 18 | HTTP: "http:", 19 | 20 | /** 21 | * Specifies HTTPS. 22 | * 23 | * @const 24 | * @type {string} 25 | */ 26 | HTTPS: "https:", 27 | 28 | /** 29 | * Specifies HTTP Proxy. 30 | * 31 | * @const 32 | * @type {string} 33 | */ 34 | HTTP_PROXY: "HTTP_PROXY", 35 | 36 | /** 37 | * Specifies HTTPS Proxy. 38 | * 39 | * @const 40 | * @type {string} 41 | */ 42 | HTTPS_PROXY: "HTTPS_PROXY", 43 | 44 | /** 45 | * Specifies NO Proxy. 46 | */ 47 | NO_PROXY: "NO_PROXY", 48 | 49 | /** 50 | * Specifies ALL Proxy. 51 | */ 52 | ALL_PROXY: "ALL_PROXY", 53 | 54 | HttpConstants: { 55 | /** 56 | * Http Verbs 57 | * 58 | * @const 59 | * @enum {string} 60 | */ 61 | HttpVerbs: { 62 | PUT: "PUT", 63 | GET: "GET", 64 | DELETE: "DELETE", 65 | POST: "POST", 66 | MERGE: "MERGE", 67 | HEAD: "HEAD", 68 | PATCH: "PATCH", 69 | }, 70 | 71 | StatusCodes: { 72 | TooManyRequests: 429, 73 | }, 74 | }, 75 | 76 | /** 77 | * Defines constants for use with HTTP headers. 78 | */ 79 | HeaderConstants: { 80 | /** 81 | * The Authorization header. 82 | * 83 | * @const 84 | * @type {string} 85 | */ 86 | AUTHORIZATION: "authorization", 87 | 88 | AUTHORIZATION_SCHEME: "Bearer", 89 | 90 | /** 91 | * The Retry-After response-header field can be used with a 503 (Service 92 | * Unavailable) or 349 (Too Many Requests) responses to indicate how long 93 | * the service is expected to be unavailable to the requesting client. 94 | * 95 | * @const 96 | * @type {string} 97 | */ 98 | RETRY_AFTER: "Retry-After", 99 | 100 | /** 101 | * The UserAgent header. 102 | * 103 | * @const 104 | * @type {string} 105 | */ 106 | USER_AGENT: "User-Agent", 107 | }, 108 | }; 109 | -------------------------------------------------------------------------------- /lib/util/xml.browser.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. See License.txt in the project root for license information. 3 | 4 | const parser = new DOMParser(); 5 | 6 | // Policy to make our code Trusted Types compliant. 7 | // https://github.com/w3c/webappsec-trusted-types 8 | // We are calling DOMParser.parseFromString() to parse XML payload from Azure services. 9 | // The parsed DOM object is not exposed to outside. Scripts are disabled when parsing 10 | // according to the spec. There are no HTML/XSS security concerns on the usage of 11 | // parseFromString() here. 12 | let ttPolicy: Pick | undefined; 13 | try { 14 | if (typeof self.trustedTypes !== "undefined") { 15 | ttPolicy = self.trustedTypes.createPolicy("@azure/ms-rest-js#xml.browser", { 16 | createHTML: (s: any) => s, 17 | }); 18 | } 19 | } catch (e) { 20 | console.warn('Could not create trusted types policy "@azure/ms-rest-js#xml.browser"'); 21 | } 22 | 23 | export function parseXML(str: string): Promise { 24 | try { 25 | const dom = parser.parseFromString((ttPolicy?.createHTML(str) ?? str) as string, "application/xml"); 26 | throwIfError(dom); 27 | 28 | const obj = domToObject(dom.childNodes[0]); 29 | return Promise.resolve(obj); 30 | } catch (err) { 31 | return Promise.reject(err); 32 | } 33 | } 34 | 35 | let errorNS = ""; 36 | try { 37 | const invalidXML = (ttPolicy?.createHTML("INVALID") ?? "INVALID") as string; 38 | errorNS = 39 | parser.parseFromString(invalidXML, "text/xml").getElementsByTagName("parsererror")[0] 40 | .namespaceURI! ?? ""; 41 | } catch (ignored) { 42 | // Most browsers will return a document containing , but IE will throw. 43 | } 44 | 45 | function throwIfError(dom: Document) { 46 | if (errorNS) { 47 | const parserErrors = dom.getElementsByTagNameNS(errorNS, "parsererror"); 48 | if (parserErrors.length) { 49 | throw new Error(parserErrors.item(0)!.innerHTML); 50 | } 51 | } 52 | } 53 | 54 | function isElement(node: Node): node is Element { 55 | return !!(node as Element).attributes; 56 | } 57 | 58 | /** 59 | * Get the Element-typed version of the provided Node if the provided node is an element with 60 | * attributes. If it isn't, then undefined is returned. 61 | */ 62 | function asElementWithAttributes(node: Node): Element | undefined { 63 | return isElement(node) && node.hasAttributes() ? node : undefined; 64 | } 65 | 66 | function domToObject(node: Node): any { 67 | let result: any = {}; 68 | 69 | const childNodeCount: number = node.childNodes.length; 70 | 71 | const firstChildNode: Node = node.childNodes[0]; 72 | const onlyChildTextValue: string | undefined = 73 | (firstChildNode && 74 | childNodeCount === 1 && 75 | firstChildNode.nodeType === Node.TEXT_NODE && 76 | firstChildNode.nodeValue) || 77 | undefined; 78 | 79 | const elementWithAttributes: Element | undefined = asElementWithAttributes(node); 80 | if (elementWithAttributes) { 81 | result["$"] = {}; 82 | 83 | for (let i = 0; i < elementWithAttributes.attributes.length; i++) { 84 | const attr = elementWithAttributes.attributes[i]; 85 | result["$"][attr.nodeName] = attr.nodeValue; 86 | } 87 | 88 | if (onlyChildTextValue) { 89 | result["_"] = onlyChildTextValue; 90 | } 91 | } else if (childNodeCount === 0) { 92 | result = ""; 93 | } else if (onlyChildTextValue) { 94 | result = onlyChildTextValue; 95 | } 96 | 97 | if (!onlyChildTextValue) { 98 | for (let i = 0; i < childNodeCount; i++) { 99 | const child = node.childNodes[i]; 100 | // Ignore leading/trailing whitespace nodes 101 | if (child.nodeType !== Node.TEXT_NODE) { 102 | const childObject: any = domToObject(child); 103 | if (!result[child.nodeName]) { 104 | result[child.nodeName] = childObject; 105 | } else if (Array.isArray(result[child.nodeName])) { 106 | result[child.nodeName].push(childObject); 107 | } else { 108 | result[child.nodeName] = [result[child.nodeName], childObject]; 109 | } 110 | } 111 | } 112 | } 113 | 114 | return result; 115 | } 116 | 117 | // tslint:disable-next-line:no-null-keyword 118 | const doc = document.implementation.createDocument(null, null, null); 119 | const serializer = new XMLSerializer(); 120 | 121 | export function stringifyXML(obj: any, opts?: { rootName?: string }) { 122 | const rootName = (opts && opts.rootName) || "root"; 123 | const dom = buildNode(obj, rootName)[0]; 124 | return ( 125 | '' + serializer.serializeToString(dom) 126 | ); 127 | } 128 | 129 | function buildAttributes(attrs: { [key: string]: { toString(): string } }): Attr[] { 130 | const result = []; 131 | for (const key of Object.keys(attrs)) { 132 | const attr = doc.createAttribute(key); 133 | attr.value = attrs[key].toString(); 134 | result.push(attr); 135 | } 136 | return result; 137 | } 138 | 139 | function buildNode(obj: any, elementName: string): Node[] { 140 | if (typeof obj === "string" || typeof obj === "number" || typeof obj === "boolean") { 141 | const elem = doc.createElement(elementName); 142 | elem.textContent = obj.toString(); 143 | return [elem]; 144 | } else if (Array.isArray(obj)) { 145 | const result = []; 146 | for (const arrayElem of obj) { 147 | for (const child of buildNode(arrayElem, elementName)) { 148 | result.push(child); 149 | } 150 | } 151 | return result; 152 | } else if (typeof obj === "object") { 153 | const elem = doc.createElement(elementName); 154 | for (const key of Object.keys(obj)) { 155 | if (key === "$") { 156 | for (const attr of buildAttributes(obj[key])) { 157 | elem.attributes.setNamedItem(attr); 158 | } 159 | } else { 160 | for (const child of buildNode(obj[key], key)) { 161 | elem.appendChild(child); 162 | } 163 | } 164 | } 165 | return [elem]; 166 | } else { 167 | throw new Error(`Illegal value passed to buildObject: ${obj}`); 168 | } 169 | } 170 | -------------------------------------------------------------------------------- /lib/util/xml.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. See License.txt in the project root for license information. 3 | 4 | import * as xml2js from "xml2js"; 5 | 6 | export function stringifyXML(obj: any, opts?: { rootName?: string }) { 7 | const builder = new xml2js.Builder({ 8 | rootName: (opts || {}).rootName, 9 | renderOpts: { 10 | pretty: false, 11 | }, 12 | }); 13 | return builder.buildObject(obj); 14 | } 15 | 16 | export function parseXML(str: string): Promise { 17 | const xmlParser = new xml2js.Parser({ 18 | explicitArray: false, 19 | explicitCharkey: false, 20 | explicitRoot: false, 21 | }); 22 | return new Promise((resolve, reject) => { 23 | if (!str) { 24 | reject(new Error("Document is empty")); 25 | } else { 26 | xmlParser.parseString(str, (err: any, res: any) => { 27 | if (err) { 28 | reject(err); 29 | } else { 30 | resolve(res); 31 | } 32 | }); 33 | } 34 | }); 35 | } 36 | -------------------------------------------------------------------------------- /lib/xhrHttpClient.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. See License.txt in the project root for license information. 3 | 4 | import { HttpClient } from "./httpClient"; 5 | import { HttpHeaders } from "./httpHeaders"; 6 | import { WebResourceLike, TransferProgressEvent } from "./webResource"; 7 | import { HttpOperationResponse } from "./httpOperationResponse"; 8 | import { RestError } from "./restError"; 9 | 10 | /** 11 | * A HttpClient implementation that uses XMLHttpRequest to send HTTP requests. 12 | */ 13 | export class XhrHttpClient implements HttpClient { 14 | public sendRequest(request: WebResourceLike): Promise { 15 | const xhr = new XMLHttpRequest(); 16 | 17 | if (request.agentSettings) { 18 | throw new Error("HTTP agent settings not supported in browser environment"); 19 | } 20 | 21 | if (request.proxySettings) { 22 | throw new Error("HTTP proxy is not supported in browser environment"); 23 | } 24 | 25 | const abortSignal = request.abortSignal; 26 | if (abortSignal) { 27 | const listener = () => { 28 | xhr.abort(); 29 | }; 30 | abortSignal.addEventListener("abort", listener); 31 | xhr.addEventListener("readystatechange", () => { 32 | if (xhr.readyState === XMLHttpRequest.DONE) { 33 | abortSignal.removeEventListener("abort", listener); 34 | } 35 | }); 36 | } 37 | 38 | addProgressListener(xhr.upload, request.onUploadProgress); 39 | addProgressListener(xhr, request.onDownloadProgress); 40 | 41 | if (request.formData) { 42 | const formData = request.formData; 43 | const requestForm = new FormData(); 44 | const appendFormValue = (key: string, value: any) => { 45 | if (value && value.hasOwnProperty("value") && value.hasOwnProperty("options")) { 46 | requestForm.append(key, value.value, value.options); 47 | } else { 48 | requestForm.append(key, value); 49 | } 50 | }; 51 | for (const formKey of Object.keys(formData)) { 52 | const formValue = formData[formKey]; 53 | if (Array.isArray(formValue)) { 54 | for (let j = 0; j < formValue.length; j++) { 55 | appendFormValue(formKey, formValue[j]); 56 | } 57 | } else { 58 | appendFormValue(formKey, formValue); 59 | } 60 | } 61 | 62 | request.body = requestForm; 63 | request.formData = undefined; 64 | const contentType = request.headers.get("Content-Type"); 65 | if (contentType && contentType.indexOf("multipart/form-data") !== -1) { 66 | // browser will automatically apply a suitable content-type header 67 | request.headers.remove("Content-Type"); 68 | } 69 | } 70 | 71 | xhr.open(request.method, request.url); 72 | xhr.timeout = request.timeout; 73 | xhr.withCredentials = request.withCredentials; 74 | for (const header of request.headers.headersArray()) { 75 | xhr.setRequestHeader(header.name, header.value); 76 | } 77 | xhr.responseType = request.streamResponseBody ? "blob" : "text"; 78 | 79 | // tslint:disable-next-line:no-null-keyword 80 | xhr.send(request.body === undefined ? null : request.body); 81 | 82 | if (request.streamResponseBody) { 83 | return new Promise((resolve, reject) => { 84 | xhr.addEventListener("readystatechange", () => { 85 | // Resolve as soon as headers are loaded 86 | if (xhr.readyState === XMLHttpRequest.HEADERS_RECEIVED) { 87 | const blobBody = new Promise((resolve, reject) => { 88 | xhr.addEventListener("load", () => { 89 | resolve(xhr.response); 90 | }); 91 | rejectOnTerminalEvent(request, xhr, reject); 92 | }); 93 | resolve({ 94 | request, 95 | status: xhr.status, 96 | headers: parseHeaders(xhr), 97 | blobBody, 98 | }); 99 | } 100 | }); 101 | rejectOnTerminalEvent(request, xhr, reject); 102 | }); 103 | } else { 104 | return new Promise(function (resolve, reject) { 105 | xhr.addEventListener("load", () => 106 | resolve({ 107 | request, 108 | status: xhr.status, 109 | headers: parseHeaders(xhr), 110 | bodyAsText: xhr.responseText, 111 | }) 112 | ); 113 | rejectOnTerminalEvent(request, xhr, reject); 114 | }); 115 | } 116 | } 117 | } 118 | 119 | function addProgressListener( 120 | xhr: XMLHttpRequestEventTarget, 121 | listener?: (progress: TransferProgressEvent) => void 122 | ) { 123 | if (listener) { 124 | xhr.addEventListener("progress", (rawEvent) => 125 | listener({ 126 | loadedBytes: rawEvent.loaded, 127 | }) 128 | ); 129 | } 130 | } 131 | 132 | // exported locally for testing 133 | export function parseHeaders(xhr: XMLHttpRequest) { 134 | const responseHeaders = new HttpHeaders(); 135 | const headerLines = xhr 136 | .getAllResponseHeaders() 137 | .trim() 138 | .split(/[\r\n]+/); 139 | for (const line of headerLines) { 140 | const index = line.indexOf(":"); 141 | const headerName = line.slice(0, index); 142 | const headerValue = line.slice(index + 2); 143 | responseHeaders.set(headerName, headerValue); 144 | } 145 | return responseHeaders; 146 | } 147 | 148 | function rejectOnTerminalEvent( 149 | request: WebResourceLike, 150 | xhr: XMLHttpRequest, 151 | reject: (err: any) => void 152 | ) { 153 | xhr.addEventListener("error", () => 154 | reject( 155 | new RestError( 156 | `Failed to send request to ${request.url}`, 157 | RestError.REQUEST_SEND_ERROR, 158 | undefined, 159 | request 160 | ) 161 | ) 162 | ); 163 | xhr.addEventListener("abort", () => 164 | reject( 165 | new RestError("The request was aborted", RestError.REQUEST_ABORTED_ERROR, undefined, request) 166 | ) 167 | ); 168 | xhr.addEventListener("timeout", () => 169 | reject( 170 | new RestError( 171 | `timeout of ${xhr.timeout}ms exceeded`, 172 | RestError.REQUEST_SEND_ERROR, 173 | undefined, 174 | request 175 | ) 176 | ) 177 | ); 178 | } 179 | -------------------------------------------------------------------------------- /mocha.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "reporterEnabled": "list, mocha-junit-reporter" 3 | } 4 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@azure/ms-rest-js", 3 | "author": { 4 | "name": "Microsoft Corporation", 5 | "email": "azsdkteam@microsoft.com", 6 | "url": "https://github.com/Azure/ms-rest-js" 7 | }, 8 | "version": "2.7.0", 9 | "description": "Isomorphic client Runtime for Typescript/node.js/browser javascript client libraries generated using AutoRest", 10 | "tags": [ 11 | "isomorphic", 12 | "browser", 13 | "javascript", 14 | "node", 15 | "microsoft", 16 | "autorest", 17 | "clientruntime" 18 | ], 19 | "keywords": [ 20 | "isomorphic", 21 | "browser", 22 | "javascript", 23 | "node", 24 | "microsoft", 25 | "autorest", 26 | "clientruntime" 27 | ], 28 | "main": "./dist/msRest.node.js", 29 | "module": "./es/lib/msRest.js", 30 | "types": "./es/lib/msRest.d.ts", 31 | "files": [ 32 | "dist/**/*.js", 33 | "dist/**/*.js.map", 34 | "es/lib/**/*.js", 35 | "es/lib/**/*.js.map", 36 | "es/lib/**/*.d.ts", 37 | "es/lib/**/*.d.ts.map", 38 | "lib/**/!(dom.d).ts", 39 | "dom-shim.d.ts", 40 | "LICENSE", 41 | "README.md", 42 | "ThirdPartyNotices.txt" 43 | ], 44 | "browser": { 45 | "./es/lib/policies/msRestUserAgentPolicy.js": "./es/lib/policies/msRestUserAgentPolicy.browser.js", 46 | "./es/lib/policies/agentPolicy.js": "./es/lib/policies/agentPolicy.browser.js", 47 | "./es/lib/policies/proxyPolicy.js": "./es/lib/policies/proxyPolicy.browser.js", 48 | "./es/lib/util/base64.js": "./es/lib/util/base64.browser.js", 49 | "./es/lib/util/xml.js": "./es/lib/util/xml.browser.js", 50 | "./es/lib/defaultHttpClient.js": "./es/lib/defaultHttpClient.browser.js" 51 | }, 52 | "license": "MIT", 53 | "dependencies": { 54 | "@azure/core-auth": "^1.1.4", 55 | "abort-controller": "^3.0.0", 56 | "form-data": "^2.5.0", 57 | "node-fetch": "^2.6.7", 58 | "tslib": "^1.10.0", 59 | "tunnel": "0.0.6", 60 | "uuid": "^8.3.2", 61 | "xml2js": "^0.5.0" 62 | }, 63 | "devDependencies": { 64 | "@azure/logger-js": "^1.1.0", 65 | "@microsoft/api-extractor": "^7.18.11", 66 | "@ts-common/azure-js-dev-tools": "^19.4.0", 67 | "@types/bluebird": "3.5.36", 68 | "@types/chai": "^4.1.7", 69 | "@types/express": "4.17.0", 70 | "@types/express-serve-static-core": "4.17.0", 71 | "@types/fetch-mock": "^7.3.1", 72 | "@types/form-data": "^2.2.1", 73 | "@types/glob": "^7.1.1", 74 | "@types/karma": "^3.0.3", 75 | "@types/mocha": "^5.2.7", 76 | "@types/node": "^12.0.12", 77 | "@types/node-fetch": "^2.3.7", 78 | "@types/semver": "^6.0.1", 79 | "@types/sinon": "^7.0.13", 80 | "@types/trusted-types": "^2.0.0", 81 | "@types/tunnel": "0.0.1", 82 | "@types/uuid": "^8.3.2", 83 | "@types/webpack": "^4.4.34", 84 | "@types/webpack-dev-middleware": "^2.0.3", 85 | "@types/xml2js": "^0.4.4", 86 | "abortcontroller-polyfill": "^1.3.0", 87 | "chai": "4.3.4", 88 | "cross-env": "^7.0.3", 89 | "express": "^4.17.1", 90 | "fetch-mock": "^7.3.3", 91 | "glob": "^7.1.4", 92 | "karma": "^4.1.0", 93 | "karma-chai": "^0.1.0", 94 | "karma-chrome-launcher": "^2.2.0", 95 | "karma-firefox-launcher": "^1.1.0", 96 | "karma-mocha": "^1.3.0", 97 | "karma-rollup-preprocessor": "^7.0.0", 98 | "karma-sourcemap-loader": "^0.3.7", 99 | "karma-typescript-es6-transform": "^4.1.1", 100 | "karma-webpack": "^4.0.2", 101 | "mocha": "^6.1.4", 102 | "mocha-chrome": "^2.0.0", 103 | "mocha-junit-reporter": "^1.23.0", 104 | "mocha-multi-reporters": "^1.1.7", 105 | "npm-run-all": "^4.1.5", 106 | "nyc": "^14.1.1", 107 | "prettier": "2.2.1", 108 | "rollup": "^1.16.6", 109 | "rollup-plugin-commonjs": "^10.0.1", 110 | "rollup-plugin-json": "^4.0.0", 111 | "rollup-plugin-multi-entry": "^2.1.0", 112 | "rollup-plugin-node-resolve": "^5.2.0", 113 | "rollup-plugin-resolve": "0.0.1-predev.1", 114 | "rollup-plugin-sourcemaps": "^0.4.2", 115 | "rollup-plugin-visualizer": "^2.4.4", 116 | "semver": "^6.2.0", 117 | "shx": "^0.3.2", 118 | "sinon": "^7.3.2", 119 | "terser": "^4.0.2", 120 | "ts-loader": "^6.0.4", 121 | "ts-node": "^8.3.0", 122 | "tslint": "^5.18.0", 123 | "tslint-eslint-rules": "^5.4.0", 124 | "typescript": "^3.5.2", 125 | "webpack": "^4.35.2", 126 | "webpack-cli": "^3.3.5", 127 | "webpack-dev-middleware": "^3.7.0", 128 | "xhr-mock": "^2.4.1", 129 | "yarn": "^1.16.0" 130 | }, 131 | "homepage": "https://github.com/Azure/ms-rest-js", 132 | "repository": { 133 | "type": "git", 134 | "url": "git@github.com:Azure/ms-rest-js.git" 135 | }, 136 | "bugs": { 137 | "url": "http://github.com/Azure/ms-rest-js/issues" 138 | }, 139 | "scripts": { 140 | "build": "run-p build:scripts build:lib", 141 | "build:scripts": "tsc -p ./.scripts/", 142 | "build:lib": "run-s build:tsc build:rollup build:minify-browser extract-api", 143 | "build:tsc": "tsc -p tsconfig.es.json", 144 | "build:rollup": "rollup -c rollup.config.ts", 145 | "build:minify-browser": "terser -c -m --comments --source-map \"content='./dist/msRest.browser.js.map'\" -o ./dist/msRest.browser.min.js ./dist/msRest.browser.js", 146 | "build:test-browser": "webpack --config webpack.testconfig.ts", 147 | "extract-api": "api-extractor run --local", 148 | "format": "prettier --write \"./**/*.ts\" *.json", 149 | "test": "run-p test:tslint test:unit test:karma", 150 | "test:tslint": "tslint -p .", 151 | "test:unit": "cross-env TS_NODE_FILES=true nyc mocha", 152 | "test:karma": "npm run build:test-browser && node ./node_modules/karma/bin/karma start karma.conf.ts --browsers ChromeNoSecurity --single-run ", 153 | "test:karma:debug": "npm run build:test-browser && node ./node_modules/karma/bin/karma start karma.conf.ts --log-level debug --browsers ChromeDebugging --debug --auto-watch", 154 | "test:karma:debugff": "npm run build:test-browser && node ./node_modules/karma/bin/karma start karma.conf.ts --log-level debug --browsers FirefoxDebugging --debug --auto-watch", 155 | "dep:autorest.typescript": "npx ts-node .scripts/testDependentProjects.ts autorest.typescript 'gulp build' 'gulp regenerate' 'npm run local'", 156 | "dep:ms-rest-azure-js": "npx ts-node .scripts/testDependentProjects.ts ms-rest-azure-js", 157 | "publish-preview": "mocha --no-colors && shx rm -rf dist/test && node ./.scripts/publish", 158 | "local": "ts-node ./.scripts/local.ts", 159 | "latest": "ts-node ./.scripts/latest.ts", 160 | "prepack": "npm i && npm run build", 161 | "check:packagejsonversion": "ts-node ./.scripts/checkPackageJsonVersion.ts", 162 | "check:foronlycalls": "ts-node ./.scripts/checkForOnlyCalls.ts", 163 | "check:everything": "ts-node ./.scripts/checkEverything.ts" 164 | }, 165 | "sideEffects": false, 166 | "nyc": { 167 | "extension": [ 168 | ".ts" 169 | ], 170 | "exclude": [ 171 | "coverage/**/*", 172 | "**/*.d.ts", 173 | "**/*.js" 174 | ], 175 | "reporter": [ 176 | "text", 177 | "html", 178 | "cobertura" 179 | ], 180 | "all": true 181 | } 182 | } 183 | -------------------------------------------------------------------------------- /rollup.config.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | /// 4 | /// 5 | /// 6 | 7 | import commonjs from "rollup-plugin-commonjs"; 8 | import json from "rollup-plugin-json"; 9 | import nodeResolve from "rollup-plugin-node-resolve"; 10 | import sourcemaps from "rollup-plugin-sourcemaps"; 11 | import visualizer from "rollup-plugin-visualizer"; 12 | 13 | const banner = `/** @license ms-rest-js 14 | * Copyright (c) Microsoft Corporation. All rights reserved. 15 | * Licensed under the MIT License. See License.txt and ThirdPartyNotices.txt in the project root for license information. 16 | */`; 17 | 18 | /** 19 | * @type {import('rollup').RollupFileOptions} 20 | */ 21 | const nodeConfig = { 22 | input: "./es/lib/msRest.js", 23 | external: [ 24 | "form-data", 25 | "http", 26 | "https", 27 | "node-fetch", 28 | "os", 29 | "stream", 30 | "tough-cookie", 31 | "tslib", 32 | "tunnel", 33 | "uuid", 34 | "xml2js", 35 | ], 36 | output: { 37 | file: "./dist/msRest.node.js", 38 | format: "cjs", 39 | sourcemap: true, 40 | banner, 41 | }, 42 | plugins: [ 43 | nodeResolve({ 44 | mainFields: ["module", "main"], 45 | }), 46 | commonjs(), 47 | sourcemaps(), 48 | json(), 49 | visualizer({ 50 | filename: "dist/node-stats.html", 51 | sourcemap: true, 52 | }), 53 | ], 54 | }; 55 | 56 | /** 57 | * @type {import('rollup').RollupFileOptions} 58 | */ 59 | const browserConfig = { 60 | input: "./es/lib/msRest.js", 61 | external: [], 62 | output: { 63 | file: "./dist/msRest.browser.js", 64 | format: "umd", 65 | name: "msRest", 66 | sourcemap: true, 67 | banner, 68 | }, 69 | plugins: [ 70 | nodeResolve({ 71 | mainFields: ["module", "main", "browser"], 72 | }), 73 | commonjs(), 74 | sourcemaps(), 75 | visualizer({ 76 | filename: "dist/browser-stats.html", 77 | sourcemap: true, 78 | }), 79 | ], 80 | }; 81 | 82 | export default [nodeConfig, browserConfig]; 83 | -------------------------------------------------------------------------------- /samples/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | My Todos 6 | 7 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /samples/node-sample.ts: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | import * as msRest from "../lib/msRest"; 4 | const clientOptions: msRest.ServiceClientOptions = { 5 | // add log policy to list of default factories. 6 | requestPolicyFactories: (factories) => factories.concat([msRest.logPolicy()]), 7 | }; 8 | 9 | const subscriptionId = process.env["AZURE_SUBSCRIPTION_ID"] || "subscriptionId"; 10 | // An easy way to get the token using Azure CLI (https://docs.microsoft.com/cli/azure/?view=azure-cli-latest) 11 | // 1. `az login` using the above subscription 12 | // 2. `az account set -s ` 13 | // 3. `az account get-access-token --resource=https://management.azure.com` 14 | // 4. copy paste that token here. That token is valid for 1 hour 15 | const token = process.env["ACCESS_TOKEN"] || "token"; 16 | const creds = new msRest.TokenCredentials(token); 17 | const client = new msRest.ServiceClient(creds, clientOptions); 18 | const req: msRest.RequestPrepareOptions = { 19 | url: `https://management.azure.com/subscriptions/${subscriptionId}/providers/Microsoft.Storage/storageAccounts?api-version=2015-06-15`, 20 | method: "GET", 21 | }; 22 | 23 | client.sendRequest(req).then(function (res: msRest.HttpOperationResponse) { 24 | console.log(res.bodyAsText); 25 | }); 26 | -------------------------------------------------------------------------------- /test/credentialTests.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. See License.txt in the project root for license information. 3 | 4 | import "chai/register-should"; 5 | import * as msRest from "../lib/msRest"; 6 | import * as base64 from "../lib/util/base64"; 7 | const TokenCredentials = msRest.TokenCredentials; 8 | const BasicAuthenticationCredentials = msRest.BasicAuthenticationCredentials; 9 | const ApiKeyCredentials = msRest.ApiKeyCredentials; 10 | const dummyToken = "A-dummy-access-token"; 11 | const fakeScheme = "fake-auth-scheme"; 12 | const dummyUsername = "dummy@mummy.com"; 13 | const dummyPassword = "IL0veDummies"; 14 | 15 | describe("Token credentials", () => { 16 | describe("usage", () => { 17 | it("should set auth header with bearer scheme in request", (done) => { 18 | const creds = new TokenCredentials(dummyToken); 19 | const request = new msRest.WebResource(); 20 | 21 | creds.signRequest(request).then((signedRequest: msRest.WebResourceLike) => { 22 | signedRequest.headers.get("authorization")!.should.exist; 23 | signedRequest.headers 24 | .get("authorization")! 25 | .should.match(new RegExp("^Bearer\\s+" + dummyToken + "$")); 26 | done(); 27 | }); 28 | }); 29 | 30 | it("should set auth header with custom scheme in request", (done) => { 31 | const creds = new TokenCredentials(dummyToken, fakeScheme); 32 | const request = new msRest.WebResource(); 33 | creds.signRequest(request).then((signedRequest: msRest.WebResourceLike) => { 34 | signedRequest.headers.get("authorization")!.should.exist; 35 | signedRequest.headers 36 | .get("authorization")! 37 | .should.match(new RegExp("^" + fakeScheme + "\\s+" + dummyToken + "$")); 38 | done(); 39 | }); 40 | }); 41 | }); 42 | 43 | describe("construction", () => { 44 | it("should succeed with token", () => { 45 | (() => { 46 | new TokenCredentials(dummyToken); 47 | }).should.not.throw(); 48 | }); 49 | 50 | // it("should fail without credentials", () => { 51 | // (() => { 52 | // new TokenCredentials(); 53 | // }).should.throw(); 54 | // }); 55 | 56 | // it("should fail without token", () => { 57 | // (() => { 58 | // new TokenCredentials(null, fakeScheme); 59 | // }).should.throw(); 60 | // }); 61 | }); 62 | }); 63 | 64 | describe("Basic Authentication credentials", () => { 65 | const encodedCredentials = base64.encodeString(dummyUsername + ":" + dummyPassword); 66 | describe("usage", () => { 67 | it("should base64 encode the username and password and set auth header with baisc scheme in request", (done) => { 68 | const creds = new BasicAuthenticationCredentials(dummyUsername, dummyPassword); 69 | const request = new msRest.WebResource(); 70 | creds.signRequest(request).then((signedRequest: msRest.WebResourceLike) => { 71 | signedRequest.headers.get("authorization")!.should.exist; 72 | signedRequest.headers 73 | .get("authorization")! 74 | .should.match(new RegExp("^Basic\\s+" + encodedCredentials + "$")); 75 | done(); 76 | }); 77 | }); 78 | 79 | it("should base64 encode the username and password and set auth header with custom scheme in request", (done) => { 80 | const creds = new BasicAuthenticationCredentials(dummyUsername, dummyPassword, fakeScheme); 81 | const request = new msRest.WebResource(); 82 | 83 | creds.signRequest(request).then((signedRequest: msRest.WebResourceLike) => { 84 | signedRequest.headers.get("authorization")!.should.exist; 85 | signedRequest.headers 86 | .get("authorization")! 87 | .should.match(new RegExp("^" + fakeScheme + "\\s+" + encodedCredentials + "$")); 88 | done(); 89 | }); 90 | }); 91 | }); 92 | 93 | describe("construction", () => { 94 | it("should succeed with userName and password", () => { 95 | (() => { 96 | new BasicAuthenticationCredentials(dummyUsername, dummyPassword); 97 | }).should.not.throw(); 98 | }); 99 | }); 100 | 101 | describe("ApiKey credentials", () => { 102 | describe("usage", function () { 103 | it("should set header parameters properly in request", async function () { 104 | const creds = new ApiKeyCredentials({ inHeader: { key1: "value1", key2: "value2" } }); 105 | const request = new msRest.WebResource(); 106 | request.headers = new msRest.HttpHeaders(); 107 | 108 | await creds.signRequest(request); 109 | 110 | request.headers.get("key1")!.should.exist; 111 | request.headers.get("key2")!.should.exist; 112 | request.headers.get("key1")!.should.match(new RegExp("^value1$")); 113 | request.headers.get("key2")!.should.match(new RegExp("^value2$")); 114 | }); 115 | 116 | it("should set query parameters properly in the request url without any query parameters", async function () { 117 | const creds = new ApiKeyCredentials({ inQuery: { key1: "value1", key2: "value2" } }); 118 | const request = { 119 | headers: {}, 120 | url: "https://example.com", 121 | } as msRest.WebResource; 122 | 123 | await creds.signRequest(request); 124 | request.url.should.equal("https://example.com?key1=value1&key2=value2"); 125 | }); 126 | 127 | it("should set query parameters properly in the request url with existing query parameters", async function () { 128 | const creds = new ApiKeyCredentials({ inQuery: { key1: "value1", key2: "value2" } }); 129 | const request = { 130 | headers: {}, 131 | url: "https://example.com?q1=v2", 132 | } as msRest.WebResource; 133 | 134 | await creds.signRequest(request); 135 | request.url.should.equal("https://example.com?q1=v2&key1=value1&key2=value2"); 136 | }); 137 | }); 138 | 139 | describe("construction", function () { 140 | it("should fail with options.inHeader and options.inQuery set to null or undefined", function (done) { 141 | (function () { 142 | new ApiKeyCredentials({ inHeader: undefined, inQuery: undefined } as any); 143 | }.should.throw()); 144 | done(); 145 | }); 146 | 147 | it("should fail without options", function (done) { 148 | (function () { 149 | new (ApiKeyCredentials as any)(); 150 | }.should.throw()); 151 | done(); 152 | }); 153 | 154 | it("should fail with empty options", function (done) { 155 | (function () { 156 | new ApiKeyCredentials({}); 157 | }.should.throw()); 158 | done(); 159 | }); 160 | }); 161 | }); 162 | }); 163 | -------------------------------------------------------------------------------- /test/data/TestClient/lib/testClient.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for 4 | * license information. 5 | * 6 | * Code generated by Microsoft (R) AutoRest Code Generator 0.14.0.0 7 | * Changes may cause incorrect behavior and will be lost if the code is 8 | * regenerated. 9 | */ 10 | 11 | /* jshint latedef:false */ 12 | /* jshint forin:false */ 13 | /* jshint noempty:false */ 14 | 15 | "use strict"; 16 | 17 | import * as msRest from "../../../../lib/msRest"; 18 | import { Mappers } from "./models/mappers"; 19 | 20 | /** 21 | * @class 22 | * Initializes a new instance of the TestClient class. 23 | * @constructor 24 | * 25 | * @param {string} [baseUri] - The base URI of the service. 26 | * 27 | * @param {object} [options] - The parameter options 28 | * 29 | * @param {Array} [options.filters] - Filters to be added to the request pipeline 30 | * 31 | * @param {object} [options.requestOptions] - Options for the underlying request object 32 | * {@link https://github.com/request/request#requestoptions-callback Options doc} 33 | * 34 | * @param {bool} [options.noRetryPolicy] - If set to true, turn off default retry policy 35 | */ 36 | 37 | class TestClient extends msRest.ServiceClient { 38 | baseUri?: string; 39 | acceptLanguage?: string; 40 | models?: any; 41 | serializer: msRest.Serializer; 42 | constructor(baseUri: string, options?: msRest.ServiceClientOptions) { 43 | if (!options) options = {}; 44 | super(undefined, options); 45 | this.baseUri = baseUri; 46 | if (!this.baseUri) { 47 | this.baseUri = "https://management.azure.com"; 48 | } 49 | 50 | if (!this.acceptLanguage) { 51 | this.acceptLanguage = "en-US"; 52 | } 53 | this.serializer = new msRest.Serializer(Mappers); 54 | } 55 | } 56 | 57 | export { TestClient }; 58 | -------------------------------------------------------------------------------- /test/logFilterTests.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. See License.txt in the project root for license information. 3 | 4 | import { assert } from "chai"; 5 | import { HttpHeaders } from "../lib/httpHeaders"; 6 | import { HttpOperationResponse } from "../lib/httpOperationResponse"; 7 | import { LogPolicy } from "../lib/policies/logPolicy"; 8 | import { RequestPolicy, RequestPolicyOptions } from "../lib/policies/requestPolicy"; 9 | import { WebResource } from "../lib/webResource"; 10 | 11 | const emptyRequestPolicy: RequestPolicy = { 12 | sendRequest(request: WebResource): Promise { 13 | // tslint:disable-next-line: no-null-keyword 14 | return Promise.resolve({ request, status: 200, headers: new HttpHeaders(), bodyAsText: null }); 15 | }, 16 | }; 17 | 18 | describe("Log filter", () => { 19 | it("should log messages when a logger object is provided", (done) => { 20 | const expected = `>> Request: { 21 | "url": "https://foo.com", 22 | "method": "PUT", 23 | "headers": { 24 | "_headersMap": {} 25 | }, 26 | "body": { 27 | "a": 1 28 | }, 29 | "withCredentials": false, 30 | "timeout": 0 31 | } 32 | >> Response status code: 200 33 | >> Body: null 34 | `; 35 | let output = ""; 36 | const logger = (message: string): void => { 37 | output += message + "\n"; 38 | }; 39 | const lf = new LogPolicy(emptyRequestPolicy, new RequestPolicyOptions(), logger); 40 | const req = new WebResource("https://foo.com", "PUT", { a: 1 }); 41 | lf.sendRequest(req) 42 | .then(() => { 43 | // console.dir(output, { depth: null }); 44 | // console.log(">>>>>>>"); 45 | // console.dir(expected); 46 | assert.deepEqual(output, expected); 47 | done(); 48 | }) 49 | .catch((err: Error) => { 50 | done(err); 51 | }); 52 | }); 53 | }); 54 | -------------------------------------------------------------------------------- /test/mocha.opts: -------------------------------------------------------------------------------- 1 | --require ts-node/register 2 | --timeout 50000 3 | --reporter mocha-multi-reporters 4 | --reporter-options configFile=mocha.config.json 5 | --colors 6 | test/**/*.ts 7 | -------------------------------------------------------------------------------- /test/mockHttp.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. See License.txt in the project root for license information. 3 | 4 | import xhrMock, { proxy } from "xhr-mock"; 5 | import { isNode, HttpMethods } from "../lib/msRest"; 6 | import fetchMock, * as fetch from "fetch-mock"; 7 | import { Readable } from "stream"; 8 | import node_fetch from "node-fetch"; 9 | 10 | export type UrlFilter = string | RegExp; 11 | 12 | export type MockResponseData = { 13 | status?: number; 14 | body?: any; 15 | headers?: any; 16 | }; 17 | 18 | export type MockResponseFunction = ( 19 | url?: string, 20 | method?: string, 21 | body?: any, 22 | headers?: any 23 | ) => Promise; 24 | 25 | export type MockResponse = MockResponseData | MockResponseFunction; 26 | 27 | export interface HttpMockFacade { 28 | setup(): void; 29 | teardown(): void; 30 | passThrough(url?: UrlFilter): void; 31 | timeout(method: HttpMethods, url?: UrlFilter): void; 32 | mockHttpMethod(method: HttpMethods, url: UrlFilter, response: MockResponse): void; 33 | get(url: UrlFilter, response: MockResponse): void; 34 | post(url: UrlFilter, response: MockResponse): void; 35 | put(url: UrlFilter, response: MockResponse): void; 36 | getFetch(): typeof node_fetch | undefined; 37 | } 38 | 39 | export function getHttpMock(): HttpMockFacade { 40 | return isNode ? new FetchHttpMock() : new BrowserHttpMock(); 41 | } 42 | 43 | class FetchHttpMock implements HttpMockFacade { 44 | private _fetch: fetchMock.FetchMockSandbox; 45 | 46 | constructor() { 47 | this._fetch = fetchMock.sandbox(); 48 | } 49 | 50 | getFetch(): typeof node_fetch { 51 | return (this._fetch as unknown) as typeof node_fetch; 52 | } 53 | 54 | setup(): void { 55 | this._fetch.resetHistory(); 56 | } 57 | 58 | teardown(): void { 59 | this._fetch.resetHistory(); 60 | } 61 | 62 | passThrough(_url?: string | RegExp | undefined): void { 63 | this._fetch.reset(); 64 | } 65 | 66 | timeout(_method: HttpMethods, url: UrlFilter): void { 67 | const delay = new Promise((resolve) => { 68 | setTimeout(() => resolve({ $uri: url, delay: 500 }), 2500); 69 | }); 70 | 71 | this._fetch.mock(url, delay); 72 | } 73 | 74 | convertStreamToBuffer(stream: Readable): Promise { 75 | return new Promise((resolve) => { 76 | const buffer: any = []; 77 | 78 | stream.on("data", (chunk: any) => { 79 | buffer.push(chunk); 80 | }); 81 | 82 | stream.on("end", () => { 83 | return resolve(buffer); 84 | }); 85 | }); 86 | } 87 | 88 | mockHttpMethod(method: HttpMethods, url: UrlFilter, response: MockResponse) { 89 | let mockResponse: fetch.MockResponse | fetch.MockResponseFunction = response; 90 | 91 | if (typeof response === "function") { 92 | const mockFunction: MockResponseFunction = response; 93 | mockResponse = (async (url: string, opts: any) => { 94 | if (opts.body && typeof opts.body.pipe === "function") { 95 | opts.body = await this.convertStreamToBuffer(opts.body); 96 | } 97 | 98 | return mockFunction(url, method, opts.body, opts.headers); 99 | }) as fetch.MockResponseFunction; 100 | } 101 | 102 | const matcher = (_url: string, opts: fetch.MockRequest) => 103 | url === _url && opts.method === method; 104 | this._fetch.mock(matcher, mockResponse); 105 | } 106 | 107 | get(url: UrlFilter, response: MockResponse): void { 108 | this.mockHttpMethod("GET", url, response); 109 | } 110 | 111 | post(url: UrlFilter, response: MockResponse): void { 112 | this.mockHttpMethod("POST", url, response); 113 | } 114 | 115 | put(url: UrlFilter, response: MockResponse): void { 116 | this.mockHttpMethod("PUT", url, response); 117 | } 118 | } 119 | 120 | export class BrowserHttpMock implements HttpMockFacade { 121 | setup(): void { 122 | xhrMock.setup(); 123 | } 124 | 125 | teardown(): void { 126 | xhrMock.teardown(); 127 | } 128 | 129 | mockHttpMethod(method: HttpMethods, url: UrlFilter, response: MockResponse): void { 130 | if (typeof response === "function") { 131 | xhrMock.use(method, url, async (req, res) => { 132 | const result = await response( 133 | req.url().toString(), 134 | req.method().toString(), 135 | req.body(), 136 | req.headers() 137 | ); 138 | return res 139 | .status(result.status || 200) 140 | .body(result.body || {}) 141 | .headers(result.headers || {}); 142 | }); 143 | } else { 144 | xhrMock.use(method, url, { 145 | status: response.status, 146 | body: response.body, 147 | }); 148 | } 149 | } 150 | 151 | get(url: UrlFilter, response: MockResponse): void { 152 | this.mockHttpMethod("GET", url, response); 153 | } 154 | 155 | post(url: UrlFilter, response: MockResponse): void { 156 | this.mockHttpMethod("POST", url, response); 157 | } 158 | 159 | put(url: UrlFilter, response: MockResponse): void { 160 | this.mockHttpMethod("PUT", url, response); 161 | } 162 | 163 | passThrough(url?: UrlFilter): void { 164 | if (url) { 165 | console.warn("Browser mock doesn't support filtered passThrough calls."); 166 | } 167 | 168 | xhrMock.use(proxy); 169 | } 170 | 171 | timeout(method: HttpMethods, url: UrlFilter): void { 172 | return this.mockHttpMethod(method, url, () => new Promise(() => {})); 173 | } 174 | 175 | getFetch(): undefined { 176 | return undefined; 177 | } 178 | } 179 | -------------------------------------------------------------------------------- /test/msAssert.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. See License.txt in the project root for license information. 3 | 4 | import { assert } from "chai"; 5 | import { SuiteFunction, PendingSuiteFunction, TestFunction, PendingTestFunction } from "mocha"; 6 | import { isNode } from "../lib/util/utils"; 7 | 8 | export const nodeIt: TestFunction | PendingTestFunction = !isNode ? it.skip : it; 9 | export const browserIt: TestFunction | PendingTestFunction = isNode ? it.skip : it; 10 | export const nodeDescribe: SuiteFunction | PendingSuiteFunction = !isNode 11 | ? describe.skip 12 | : describe; 13 | export const browserDescribe: SuiteFunction | PendingSuiteFunction = isNode 14 | ? describe.skip 15 | : describe; 16 | 17 | /** 18 | * Assert that the provided syncFunction throws an Error. If the expectedError is undefined, then 19 | * this function will just assert that an Error was thrown. If the expectedError is defined, then 20 | * this function will assert that the Error that was thrown is equal to the provided expectedError. 21 | * @param syncFunction The synchronous function that is expected to thrown an Error. 22 | * @param expectedError The Error that is expected to be thrown. 23 | */ 24 | export function throws( 25 | syncFunction: () => void, 26 | expectedError?: ((error: Error) => void) | Error 27 | ): Error { 28 | let thrownError: Error | undefined; 29 | 30 | try { 31 | syncFunction(); 32 | } catch (error) { 33 | thrownError = error; 34 | } 35 | 36 | if (!thrownError) { 37 | assert.throws(() => {}); 38 | } else if (expectedError instanceof Error) { 39 | assert.deepEqual(thrownError, expectedError); 40 | } else if (expectedError) { 41 | expectedError(thrownError); 42 | } 43 | 44 | return thrownError!; 45 | } 46 | 47 | /** 48 | * Assert that the provided asyncFunction throws an Error. If the expectedError is undefined, then 49 | * this function will just assert that an Error was thrown. If the expectedError is defined, then 50 | * this function will assert that the Error that was thrown is equal to the provided expectedError. 51 | * @param asyncFunction The asynchronous function that is expected to thrown an Error. 52 | * @param expectedError The Error that is expected to be thrown. 53 | */ 54 | export async function throwsAsync( 55 | asyncFunction: (() => Promise) | Promise, 56 | expectedError?: ((error: Error) => void) | Error 57 | ): Promise { 58 | let thrownError: Error | undefined; 59 | 60 | try { 61 | await (typeof asyncFunction === "function" ? asyncFunction() : asyncFunction); 62 | } catch (error) { 63 | thrownError = error; 64 | } 65 | 66 | if (!thrownError) { 67 | assert.throws(() => {}); 68 | } else if (expectedError instanceof Error) { 69 | assert.deepEqual(thrownError, expectedError); 70 | } else if (expectedError) { 71 | expectedError(thrownError); 72 | } 73 | 74 | return thrownError!; 75 | } 76 | -------------------------------------------------------------------------------- /test/msRestUserAgentPolicyTests.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. See License.txt in the project root for license information. 3 | 4 | import "chai/register-should"; 5 | 6 | import { HttpOperationResponse } from "../lib/httpOperationResponse"; 7 | import { RequestPolicy, RequestPolicyOptions } from "../lib/policies/requestPolicy"; 8 | import { Constants } from "../lib/util/constants"; 9 | import { WebResource } from "../lib/webResource"; 10 | import { userAgentPolicy } from "../lib/policies/userAgentPolicy"; 11 | import { nodeDescribe, browserDescribe } from "./msAssert"; 12 | 13 | const userAgentHeaderKey = Constants.HeaderConstants.USER_AGENT; 14 | 15 | const emptyRequestPolicy: RequestPolicy = { 16 | sendRequest(request: WebResource): Promise { 17 | request.should.exist; 18 | return Promise.resolve({ request: request, status: 200, headers: request.headers }); 19 | }, 20 | }; 21 | 22 | const getPlainUserAgentPolicy = (headerValue?: string): RequestPolicy => { 23 | const factory = userAgentPolicy({ value: headerValue }); 24 | return factory.create(emptyRequestPolicy, new RequestPolicyOptions()); 25 | }; 26 | 27 | const getUserAgent = async (headerValue?: string): Promise => { 28 | const policy = getPlainUserAgentPolicy(headerValue); 29 | const resource = new WebResource(); 30 | await policy.sendRequest(resource); 31 | const userAgent = resource.headers.get(userAgentHeaderKey); 32 | return userAgent!; 33 | }; 34 | 35 | describe("MsRestUserAgentPolicy", () => { 36 | nodeDescribe("for Node.js", () => { 37 | it("should not modify user agent header if already present", async () => { 38 | const userAgentPolicy = getPlainUserAgentPolicy(); 39 | const customUserAgent = "my custom user agent"; 40 | const resource = new WebResource(); 41 | resource.headers.set(userAgentHeaderKey, customUserAgent); 42 | await userAgentPolicy.sendRequest(resource); 43 | 44 | const userAgentHeader: string = resource.headers.get(userAgentHeaderKey)!; 45 | 46 | userAgentHeader.should.be.equal(customUserAgent); 47 | }); 48 | 49 | it("should not set the user agent header if custom user agent is empty", async () => { 50 | const customUserAgent = ""; 51 | const factory = userAgentPolicy({ value: customUserAgent }); 52 | const nodeUserAgentPolicy = factory.create(emptyRequestPolicy, new RequestPolicyOptions()); 53 | const resource = new WebResource(); 54 | await nodeUserAgentPolicy.sendRequest(resource); 55 | 56 | const userAgentHeader: string = resource.headers.get(userAgentHeaderKey)!; 57 | 58 | (userAgentHeader === undefined).should.be.true; 59 | }); 60 | 61 | it("should use injected user agent string if provided", async () => { 62 | const customUserAgent = "my custom user agent"; 63 | const factory = userAgentPolicy({ value: customUserAgent }); 64 | const nodeUserAgentPolicy = factory.create(emptyRequestPolicy, new RequestPolicyOptions()); 65 | const resource = new WebResource(); 66 | await nodeUserAgentPolicy.sendRequest(resource); 67 | 68 | const userAgentHeader: string = resource.headers.get(userAgentHeaderKey)!; 69 | 70 | userAgentHeader.should.be.equal(customUserAgent); 71 | }); 72 | 73 | it("should be space delimited and contain three fields", async () => { 74 | const userAgent = await getUserAgent(); 75 | const userAgentParts = userAgent.split(" "); 76 | userAgentParts.length.should.be.equal(3); 77 | }); 78 | 79 | it("should contain runtime information", async () => { 80 | const userAgent = await getUserAgent(); 81 | userAgent.should.match(/ms-rest-js\/[\d\w\.-]+ .+/); 82 | }); 83 | 84 | it("should have operating system information at the third place", async () => { 85 | const userAgent = await getUserAgent(); 86 | const userAgentParts = userAgent.split(" "); 87 | const osInfo = userAgentParts[2]; 88 | osInfo.should.match(/OS\/\([\w\d\.\-]+\)/); 89 | }); 90 | 91 | it("should have Node information at the second place", async () => { 92 | const userAgent = await getUserAgent(); 93 | const userAgentParts = userAgent.split(" "); 94 | const osInfo = userAgentParts[1]; 95 | osInfo.should.match(/Node\/v[\d.]+/); 96 | }); 97 | }); 98 | 99 | browserDescribe("for browser", function () { 100 | const userAgentHeaderKey = "x-ms-command-name"; 101 | 102 | const emptyRequestPolicy: RequestPolicy = { 103 | sendRequest(request: WebResource): Promise { 104 | request.should.exist; 105 | return Promise.resolve({ request: request, status: 200, headers: request.headers }); 106 | }, 107 | }; 108 | 109 | const getUserAgent = async (headerValue?: string): Promise => { 110 | const factory = userAgentPolicy({ value: headerValue }); 111 | const policy = factory.create(emptyRequestPolicy, new RequestPolicyOptions()); 112 | const resource = new WebResource(); 113 | await policy.sendRequest(resource); 114 | const userAgent = resource.headers.get(userAgentHeaderKey); 115 | return userAgent!; 116 | }; 117 | 118 | describe("MsRestUserAgentPolicy (Browser)", () => { 119 | it("should not modify user agent header if already present", async () => { 120 | const factory = userAgentPolicy(); 121 | const browserUserAgentPolicy = factory.create( 122 | emptyRequestPolicy, 123 | new RequestPolicyOptions() 124 | ); 125 | const customUserAgent = "my custom user agent"; 126 | const resource = new WebResource(); 127 | resource.headers.set(userAgentHeaderKey, customUserAgent); 128 | await browserUserAgentPolicy.sendRequest(resource); 129 | 130 | const userAgentHeader: string = resource.headers.get(userAgentHeaderKey)!; 131 | 132 | userAgentHeader.should.be.equal(customUserAgent); 133 | }); 134 | 135 | it("should use injected user agent string if provided", async () => { 136 | const customUserAgent = "my custom user agent"; 137 | const factory = userAgentPolicy({ value: customUserAgent }); 138 | const browserUserAgentPolicy = factory.create( 139 | emptyRequestPolicy, 140 | new RequestPolicyOptions() 141 | ); 142 | const resource = new WebResource(); 143 | await browserUserAgentPolicy.sendRequest(resource); 144 | 145 | const userAgentHeader: string = resource.headers.get(userAgentHeaderKey)!; 146 | 147 | userAgentHeader.should.be.equal(customUserAgent); 148 | }); 149 | 150 | it("should be space delimited and contain two fields", async () => { 151 | const userAgent = await getUserAgent(); 152 | const userAgentParts = userAgent.split(" "); 153 | userAgentParts.length.should.be.equal(2); 154 | }); 155 | 156 | it("should contain runtime information", async () => { 157 | const userAgent = await getUserAgent(); 158 | userAgent.should.match(/ms-rest-js\/[\d\w\.-]+ .+/); 159 | }); 160 | 161 | it("should have operating system information at the second place", async () => { 162 | const userAgent = await getUserAgent(); 163 | const userAgentParts = userAgent.split(" "); 164 | const osInfo = userAgentParts[1]; 165 | osInfo.should.match(/OS\/[\w\d\.\-]+/); 166 | }); 167 | }); 168 | }); 169 | }); 170 | -------------------------------------------------------------------------------- /test/operationParameterTests.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. See License.txt in the project root for license information. 3 | 4 | import { assert } from "chai"; 5 | import { getPathStringFromParameter, OperationParameter } from "../lib/operationParameter"; 6 | 7 | describe("getParameterPathString()", () => { 8 | it("should throw when given undefined", () => { 9 | assert.throws(() => getPathStringFromParameter(undefined as any)); 10 | }); 11 | 12 | it("should throw when given null", () => { 13 | // tslint:disable-next-line:no-null-keyword 14 | assert.throws(() => getPathStringFromParameter(null as any)); 15 | }); 16 | 17 | it("should return the parameterPath value when parameterPath is a string", () => { 18 | const parameter: OperationParameter = { 19 | parameterPath: "pathToParameterValue", 20 | mapper: { 21 | serializedName: "value", 22 | type: { 23 | name: "Number", 24 | }, 25 | }, 26 | }; 27 | assert.strictEqual(getPathStringFromParameter(parameter), "pathToParameterValue"); 28 | }); 29 | 30 | it("should return the dotted version of parameterPath when parameterPath is a string[]", () => { 31 | const parameter: OperationParameter = { 32 | parameterPath: ["path", "to", "parameter", "value"], 33 | mapper: { 34 | serializedName: "value", 35 | type: { 36 | name: "Number", 37 | }, 38 | }, 39 | }; 40 | assert.strictEqual(getPathStringFromParameter(parameter), "path.to.parameter.value"); 41 | }); 42 | 43 | it("should return the escaped dotted version of parameterPath when parameterPath is a string[] with dots", () => { 44 | const parameter: OperationParameter = { 45 | parameterPath: ["pa.th", "to", "par.ameter", "valu.e"], 46 | mapper: { 47 | serializedName: "value", 48 | type: { 49 | name: "Number", 50 | }, 51 | }, 52 | }; 53 | assert.strictEqual(getPathStringFromParameter(parameter), "pa.th.to.par.ameter.valu.e"); 54 | }); 55 | 56 | it("should return the mapper's serialized name when the parameterPath is an object", () => { 57 | const parameter: OperationParameter = { 58 | parameterPath: { 59 | a: "A", 60 | b: "B", 61 | }, 62 | mapper: { 63 | serializedName: "value", 64 | type: { 65 | name: "Number", 66 | }, 67 | }, 68 | }; 69 | assert.strictEqual(getPathStringFromParameter(parameter), "value"); 70 | }); 71 | }); 72 | -------------------------------------------------------------------------------- /test/policies/agentPolicyTests.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. See License.txt in the project root for license information. 3 | 4 | import "chai/register-should"; 5 | import { AgentSettings } from "../../lib/serviceClient"; 6 | import { RequestPolicyOptions } from "../../lib/policies/requestPolicy"; 7 | import { WebResource, WebResourceLike } from "../../lib/webResource"; 8 | import { HttpHeaders } from "../../lib/httpHeaders"; 9 | import { agentPolicy, AgentPolicy } from "../../lib/policies/agentPolicy"; 10 | import { nodeDescribe, browserDescribe } from "../msAssert"; 11 | 12 | describe("AgentPolicy", function () { 13 | const emptyRequestPolicy = { 14 | sendRequest: (_: WebResourceLike) => 15 | Promise.resolve({ 16 | request: new WebResource(), 17 | status: 404, 18 | headers: new HttpHeaders(undefined), 19 | }), 20 | }; 21 | 22 | const emptyPolicyOptions = new RequestPolicyOptions(); 23 | 24 | nodeDescribe("for Node.js", function () { 25 | const http = require("http"); 26 | const https = require("https"); 27 | 28 | const agentSettings: AgentSettings = { 29 | http: new http.Agent(), 30 | https: new https.Agent(), 31 | }; 32 | 33 | it("factory passes correct agent settings", function () { 34 | const factory = agentPolicy(agentSettings); 35 | 36 | const policy = factory.create(emptyRequestPolicy, emptyPolicyOptions) as AgentPolicy; 37 | 38 | policy.agentSettings.should.be.deep.equal(agentSettings); 39 | }); 40 | 41 | it("sets correct agent settings through constructor", function () { 42 | const policy = new AgentPolicy(emptyRequestPolicy, emptyPolicyOptions, agentSettings); 43 | 44 | policy.agentSettings.should.be.deep.equal(agentSettings); 45 | }); 46 | 47 | it("should assign agent settings to the web request", async function () { 48 | const policy = new AgentPolicy(emptyRequestPolicy, emptyPolicyOptions, agentSettings); 49 | const request = new WebResource(); 50 | 51 | await policy.sendRequest(request); 52 | 53 | request.agentSettings!.should.be.deep.equal(agentSettings); 54 | }); 55 | 56 | it("should not override agent settings to the web request", async function () { 57 | const policy = new AgentPolicy(emptyRequestPolicy, emptyPolicyOptions, agentSettings); 58 | 59 | const request = new WebResource(); 60 | const requestSpecificAgentSettings = { 61 | http: new http.Agent({ keepAlive: true }), 62 | https: new http.Agent({ keepAlive: true }), 63 | }; 64 | request.agentSettings = requestSpecificAgentSettings; 65 | 66 | await policy.sendRequest(request); 67 | 68 | request.agentSettings!.should.be.deep.equal(requestSpecificAgentSettings); 69 | }); 70 | }); 71 | 72 | browserDescribe("for browser", () => { 73 | it("should throw an Error while constructing object", () => { 74 | const agentSettings = {} as AgentSettings; 75 | const construct = () => 76 | new AgentPolicy(emptyRequestPolicy, emptyPolicyOptions, agentSettings); 77 | construct.should.throw(); 78 | }); 79 | }); 80 | }); 81 | -------------------------------------------------------------------------------- /test/policies/systemErrorRetryPolicyTests.spec.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT license. 3 | 4 | import { assert } from "chai"; 5 | import { SystemErrorRetryPolicy } from "../../lib/policies/systemErrorRetryPolicy"; 6 | import { RetryError } from "../../lib/policies/systemErrorRetryPolicy"; 7 | import { WebResource } from "../../lib/webResource"; 8 | import { HttpOperationResponse } from "../../lib/httpOperationResponse"; 9 | import { HttpHeaders, RequestPolicyOptions } from "../../lib/msRest"; 10 | 11 | describe("SystemErrorRetryPolicy", () => { 12 | class PassThroughPolicy { 13 | constructor(private _response: HttpOperationResponse) {} 14 | public sendRequest(request: WebResource): Promise { 15 | const response = { 16 | ...this._response, 17 | request: request, 18 | }; 19 | 20 | return Promise.resolve(response); 21 | } 22 | } 23 | 24 | // throw error on first sendRequest() 25 | class FailFirstRequestPolicy { 26 | count = 0; 27 | constructor(private _response: HttpOperationResponse, private errorCode: string) {} 28 | public sendRequest(request: WebResource): Promise { 29 | if (this.count === 0) { 30 | this.count++; 31 | const error: RetryError = { 32 | code: this.errorCode, 33 | name: "RetryError", 34 | message: `Error message for ${this.errorCode}`, 35 | }; 36 | return Promise.reject(error); 37 | } 38 | 39 | const response = { 40 | ...this._response, 41 | request: request, 42 | }; 43 | 44 | return Promise.resolve(response); 45 | } 46 | } 47 | 48 | const defaultResponse = { 49 | status: 200, 50 | request: new WebResource(), 51 | headers: new HttpHeaders(), 52 | }; 53 | 54 | function createDefaultSystemErrorRetryPolicy( 55 | response?: HttpOperationResponse 56 | ): SystemErrorRetryPolicy { 57 | if (!response) { 58 | response = defaultResponse; 59 | } 60 | 61 | const passThroughPolicy = new PassThroughPolicy(response); 62 | return new SystemErrorRetryPolicy(passThroughPolicy, new RequestPolicyOptions()); 63 | } 64 | 65 | describe("sendRequest", () => { 66 | it("should clone the request", async () => { 67 | const request = new WebResource(); 68 | const nextPolicy = { 69 | sendRequest: (requestToSend: WebResource): Promise => { 70 | assert(request !== requestToSend); 71 | return Promise.resolve(defaultResponse); 72 | }, 73 | }; 74 | const policy = new SystemErrorRetryPolicy(nextPolicy, new RequestPolicyOptions()); 75 | await policy.sendRequest(request); 76 | }); 77 | 78 | it("should not modify the request", async () => { 79 | const request = new WebResource(); 80 | request.url = "http://url"; 81 | request.method = "PATCH"; 82 | request.body = { someProperty: "someValue" }; 83 | request.headers = new HttpHeaders({ header: "abc" }); 84 | request.query = { q: "param" }; 85 | 86 | const policy = createDefaultSystemErrorRetryPolicy(); 87 | const response = await policy.sendRequest(request); 88 | delete (response.request as any).requestId; 89 | delete (request as any).requestId; 90 | 91 | assert.deepEqual(response.request, request); 92 | }); 93 | 94 | ["ETIMEDOUT", "ESOCKETTIMEDOUT", "ECONNREFUSED", "ECONNRESET", "ENOENT"].forEach((code) => { 95 | it(`should retry if the error code is ${code}`, async () => { 96 | const request = new WebResource(); 97 | const mockResponse = { 98 | status: 200, 99 | headers: new HttpHeaders(), 100 | request: request, 101 | }; 102 | 103 | const faultyPolicy = new FailFirstRequestPolicy(mockResponse, code); 104 | const policy = new SystemErrorRetryPolicy( 105 | faultyPolicy, 106 | new RequestPolicyOptions(), 107 | 3, 108 | 10, 109 | 10, 110 | 20 111 | ); 112 | 113 | const response = await policy.sendRequest(request); 114 | delete (request as any).requestId; 115 | delete (response.request as any).requestId; 116 | assert.deepEqual(response, mockResponse, "Expecting response matches after retrying"); 117 | }); 118 | }); 119 | 120 | it("should do nothing when error code is not one of the retriable errors", async () => { 121 | const request = new WebResource(); 122 | const faultyPolicy = new FailFirstRequestPolicy(defaultResponse, "NonRetriableError"); 123 | const policy = new SystemErrorRetryPolicy( 124 | faultyPolicy, 125 | new RequestPolicyOptions(), 126 | 3, 127 | 10, 128 | 10, 129 | 20 130 | ); 131 | 132 | try { 133 | await policy.sendRequest(request); 134 | assert.fail("Expecting that an error has been thrown"); 135 | } catch (err) { 136 | assert.equal((err as Error).message, "Error message for NonRetriableError"); 137 | } 138 | }); 139 | 140 | ["ETIMEDOUT", "ESOCKETTIMEDOUT", "ECONNREFUSED", "ECONNRESET", "ENOENT"].forEach((code) => { 141 | it(`should fail after max retry count for error code ${code}`, async () => { 142 | class FailEveryRequestPolicy { 143 | count = 0; 144 | constructor(private errorCode: string) {} 145 | public sendRequest(_request: WebResource): Promise { 146 | const error: RetryError = { 147 | code: this.errorCode, 148 | name: "RetryError", 149 | message: `Error message for ${this.errorCode}`, 150 | }; 151 | return Promise.reject(error); 152 | } 153 | } 154 | const request = new WebResource(); 155 | const faultyPolicy = new FailEveryRequestPolicy(code); 156 | const policy = new SystemErrorRetryPolicy( 157 | faultyPolicy, 158 | new RequestPolicyOptions(), 159 | 3, 160 | 10, 161 | 10, 162 | 20 163 | ); 164 | 165 | try { 166 | await policy.sendRequest(request); 167 | assert.fail("Expecting that an error has been thrown"); 168 | } catch (err) { 169 | assert.equal((err as Error).message, `Error message for ${code}`); 170 | } 171 | }); 172 | }); 173 | }); 174 | }).timeout(60000); 175 | -------------------------------------------------------------------------------- /test/policies/throttlingRetryPolicyTests.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. See License.txt in the project root for license information. 3 | 4 | import { assert } from "chai"; 5 | import sinon from "sinon"; 6 | import { ThrottlingRetryPolicy } from "../../lib/policies/throttlingRetryPolicy"; 7 | import { WebResource, WebResourceLike } from "../../lib/webResource"; 8 | import { HttpOperationResponse } from "../../lib/httpOperationResponse"; 9 | import { HttpHeaders, RequestPolicyOptions } from "../../lib/msRest"; 10 | 11 | describe("ThrottlingRetryPolicy", () => { 12 | class PassThroughPolicy { 13 | constructor(private _response: HttpOperationResponse) {} 14 | public sendRequest(request: WebResourceLike): Promise { 15 | const response = { 16 | ...this._response, 17 | request: request, 18 | }; 19 | 20 | return Promise.resolve(response); 21 | } 22 | } 23 | 24 | const defaultResponse = { 25 | status: 200, 26 | request: new WebResource(), 27 | headers: new HttpHeaders(), 28 | }; 29 | 30 | // Inject 429 responses on first numberRetryAfter sendRequest() calls 31 | class RetryFirstNRequestsPolicy { 32 | public count = 0; 33 | constructor(private _response: HttpOperationResponse, private numberRetryAfter: number) {} 34 | public sendRequest(request: WebResource): Promise { 35 | if (this.count < this.numberRetryAfter) { 36 | this.count++; 37 | 38 | return Promise.resolve({ 39 | status: 429, 40 | headers: new HttpHeaders({ 41 | "Retry-After": "1", 42 | }), 43 | request, 44 | }); 45 | } 46 | 47 | return Promise.resolve({ 48 | ...this._response, 49 | request, 50 | }); 51 | } 52 | } 53 | 54 | function createDefaultThrottlingRetryPolicy(response?: HttpOperationResponse) { 55 | if (!response) { 56 | response = defaultResponse; 57 | } 58 | 59 | const passThroughPolicy = new PassThroughPolicy(response); 60 | return new ThrottlingRetryPolicy(passThroughPolicy, new RequestPolicyOptions(), 3); 61 | } 62 | 63 | describe("sendRequest", () => { 64 | it("should clone the request", async () => { 65 | const request = new WebResource(); 66 | const nextPolicy = { 67 | sendRequest: (requestToSend: WebResourceLike): Promise => { 68 | assert(request !== requestToSend); 69 | return Promise.resolve(defaultResponse); 70 | }, 71 | }; 72 | const policy = new ThrottlingRetryPolicy(nextPolicy, new RequestPolicyOptions(), 3); 73 | await policy.sendRequest(request); 74 | }); 75 | 76 | it("should not modify the request", async () => { 77 | const request = new WebResource(); 78 | request.url = "http://url"; 79 | request.method = "PATCH"; 80 | request.body = { someProperty: "someValue" }; 81 | request.headers = new HttpHeaders({ header: "abc" }); 82 | request.query = { q: "param" }; 83 | 84 | const policy = createDefaultThrottlingRetryPolicy(); 85 | const response = await policy.sendRequest(request); 86 | 87 | assert.deepEqual(response.request, request); 88 | }); 89 | 90 | it("should do nothing when status code is not 429", async () => { 91 | const request = new WebResource(); 92 | const mockResponse = { 93 | status: 400, 94 | headers: new HttpHeaders({ 95 | "Retry-After": "100", 96 | }), 97 | request: request, 98 | }; 99 | const faultyPolicy = new RetryFirstNRequestsPolicy(mockResponse, 0); 100 | const policy = new ThrottlingRetryPolicy(faultyPolicy, new RequestPolicyOptions(), 3); 101 | const spy = sinon.spy(policy as any, "retry"); 102 | 103 | const response = await policy.sendRequest(request); 104 | assert.deepEqual(response, mockResponse); 105 | assert.strictEqual(spy.callCount, 1); 106 | }); 107 | 108 | it("should retry if the status code equals 429", async () => { 109 | const request = new WebResource(); 110 | const faultyPolicy = new RetryFirstNRequestsPolicy(defaultResponse, 1); 111 | const policy = new ThrottlingRetryPolicy(faultyPolicy, new RequestPolicyOptions(), 3); 112 | const spy = sinon.spy(policy as any, "retry"); 113 | 114 | const response = await policy.sendRequest(request); 115 | assert.deepEqual(response, defaultResponse); 116 | assert.strictEqual(spy.callCount, 2); // last retry returns directly for 200 response 117 | }); 118 | 119 | it("should give up on 429 after retry limit", async () => { 120 | const request = new WebResource(); 121 | const faultyPolicy = new RetryFirstNRequestsPolicy(defaultResponse, 4); 122 | const policy = new ThrottlingRetryPolicy(faultyPolicy, new RequestPolicyOptions(), 3); 123 | const spy = sinon.spy(policy as any, "retry"); 124 | 125 | const response = await policy.sendRequest(request); 126 | assert.deepEqual(response.status, 429); 127 | assert.strictEqual(spy.callCount, 4); // last retry returns directly after reaching retry limit 128 | }).timeout(5000); 129 | }); 130 | 131 | describe("parseRetryAfterHeader", () => { 132 | it("should return undefined for ill-formed header", function () { 133 | const retryAfter = ThrottlingRetryPolicy.parseRetryAfterHeader("foobar"); 134 | assert.equal(retryAfter, undefined); 135 | }); 136 | 137 | it("should return sleep interval value in milliseconds if parameter is a number", function (done) { 138 | const retryAfter = ThrottlingRetryPolicy.parseRetryAfterHeader("1"); 139 | assert.equal(retryAfter, 1000); 140 | done(); 141 | }); 142 | 143 | it("should return sleep interval value in milliseconds for full date format", function (done) { 144 | const clock = sinon.useFakeTimers(new Date("Fri, 31 Dec 1999 23:00:00 GMT").getTime()); 145 | const retryAfter = ThrottlingRetryPolicy.parseRetryAfterHeader( 146 | "Fri, 31 Dec 1999 23:02:00 GMT" 147 | ); 148 | 149 | assert.equal(retryAfter, 2 * 60 * 1000); 150 | 151 | clock.restore(); 152 | done(); 153 | }); 154 | 155 | it("should return sleep interval value in milliseconds for shorter date format", function (done) { 156 | const clock = sinon.useFakeTimers(new Date("Fri, 31 Dec 1999 23:00:00 GMT").getTime()); 157 | const retryAfter = ThrottlingRetryPolicy.parseRetryAfterHeader("31 Dec 1999 23:03:00 GMT"); 158 | 159 | assert.equal(retryAfter, 3 * 60 * 1000); 160 | 161 | clock.restore(); 162 | done(); 163 | }); 164 | }); 165 | }); 166 | -------------------------------------------------------------------------------- /test/proxyAgent.node.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. See License.txt in the project root for license information. 3 | 4 | import "chai/register-should"; 5 | import { should } from "chai"; 6 | import tunnel from "tunnel"; 7 | import https from "https"; 8 | 9 | import { HttpHeaders } from "../lib/msRest"; 10 | import { createProxyAgent, createTunnel } from "../lib/proxyAgent"; 11 | import { ProxySettings } from "../lib/serviceClient"; 12 | 13 | describe("proxyAgent", () => { 14 | describe("createProxyAgent", () => { 15 | type HttpsAgent = https.Agent & { 16 | defaultPort: number | undefined; 17 | options: { 18 | proxy: tunnel.ProxyOptions; 19 | }; 20 | proxyOptions: tunnel.ProxyOptions; 21 | }; 22 | 23 | [ 24 | { proxy: "http", request: "ftp", port: undefined, isProxyHttps: false }, 25 | { proxy: "http", request: "http", port: undefined, isProxyHttps: false }, 26 | { proxy: "hTtp", request: "https", port: 443, isProxyHttps: true }, 27 | { proxy: "HTTPS", request: "http", port: undefined, isProxyHttps: false }, 28 | { proxy: "https", request: "hTTps", port: 443, isProxyHttps: true }, 29 | ].forEach((testCase) => { 30 | it(`should return ${ 31 | testCase.isProxyHttps ? "HTTPS" : "HTTP" 32 | } proxy for ${testCase.proxy.toUpperCase()} proxy server and ${testCase.request.toUpperCase()} request`, function (done) { 33 | const urlHost = "proxy.microsoft.com"; 34 | const proxySettings = { 35 | host: `${testCase.proxy}://${urlHost}`, 36 | port: 8080, 37 | }; 38 | const requestUrl = `${testCase.request}://example.com`; 39 | 40 | const proxyAgent = createProxyAgent(requestUrl, proxySettings); 41 | 42 | proxyAgent.isHttps.should.equal(testCase.isProxyHttps); 43 | const agent = proxyAgent.agent as HttpsAgent; 44 | should().equal(agent.defaultPort, testCase.port); 45 | agent.options.proxy.host!.should.equal(urlHost); 46 | agent.options.proxy.port!.should.equal(proxySettings.port); 47 | done(); 48 | }); 49 | }); 50 | 51 | it("should copy headers correctly", function (done) { 52 | const proxySettings = { 53 | host: "http://proxy.microsoft.com", 54 | port: 8080, 55 | }; 56 | const headers = new HttpHeaders({ 57 | "User-Agent": "Node.js", 58 | }); 59 | 60 | const proxyAgent = createProxyAgent("http://example.com", proxySettings, headers); 61 | 62 | const agent = proxyAgent.agent as HttpsAgent; 63 | agent.proxyOptions.headers!.should.contain({ "user-agent": "Node.js" }); 64 | done(); 65 | }); 66 | 67 | it("should set agent proxyAuth correctly", function (done) { 68 | const proxySettings: ProxySettings = { 69 | host: "http://proxy.microsoft.com", 70 | port: 8080, 71 | username: "username", 72 | password: "pass123", 73 | }; 74 | 75 | const proxyAgent = createProxyAgent("http://example.com", proxySettings); 76 | 77 | const agent = proxyAgent.agent as HttpsAgent; 78 | should().exist(agent.options.proxy.proxyAuth); 79 | agent.options.proxy.proxyAuth!.should.equal("username:pass123"); 80 | done(); 81 | }); 82 | 83 | it("should set agent proxyAuth correctly when password is not specified", function (done) { 84 | const proxySettings: ProxySettings = { 85 | host: "http://proxy.microsoft.com", 86 | port: 8080, 87 | username: "username", 88 | }; 89 | 90 | const proxyAgent = createProxyAgent("http://example.com", proxySettings); 91 | 92 | const agent = proxyAgent.agent as HttpsAgent; 93 | should().exist(agent.options.proxy.proxyAuth); 94 | agent.options.proxy.proxyAuth!.should.equal("username"); 95 | done(); 96 | }); 97 | }); 98 | 99 | describe("createTunnel", () => { 100 | const defaultProxySettings = { 101 | host: "http://proxy.microsoft.com", 102 | port: 8080, 103 | }; 104 | 105 | type HttpsAgent = https.Agent & { 106 | defaultPort: number | undefined; 107 | options: { 108 | proxy: tunnel.ProxyOptions; 109 | }; 110 | }; 111 | 112 | [true, false].forEach((value) => { 113 | it(`returns HTTP agent for HTTP request and HTTP${value ? "S" : ""} proxy`, function () { 114 | const tunnelConfig: tunnel.HttpsOverHttpsOptions = { 115 | proxy: { 116 | host: defaultProxySettings.host, 117 | port: defaultProxySettings.port, 118 | headers: {}, 119 | }, 120 | }; 121 | 122 | const tunnel = createTunnel(false, value, tunnelConfig) as HttpsAgent; 123 | tunnel.options.proxy.host!.should.equal(defaultProxySettings.host); 124 | tunnel.options.proxy.port!.should.equal(defaultProxySettings.port); 125 | should().not.exist(tunnel.defaultPort); 126 | }); 127 | }); 128 | 129 | [true, false].forEach((value) => { 130 | it(`returns HTTPS agent for HTTPS request and HTTP${value ? "S" : ""} proxy`, function () { 131 | const tunnelConfig: tunnel.HttpsOverHttpsOptions = { 132 | proxy: { 133 | host: defaultProxySettings.host, 134 | port: defaultProxySettings.port, 135 | headers: {}, 136 | }, 137 | }; 138 | 139 | const tunnel = createTunnel(true, value, tunnelConfig) as HttpsAgent; 140 | tunnel.options.proxy.host!.should.equal(defaultProxySettings.host); 141 | tunnel.options.proxy.port!.should.equal(defaultProxySettings.port); 142 | tunnel.defaultPort!.should.equal(443); 143 | }); 144 | }); 145 | }); 146 | }); 147 | -------------------------------------------------------------------------------- /test/redirectLimitTests.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. See License.txt in the project root for license information. 3 | 4 | import { expect } from "chai"; 5 | import sinon from "sinon"; 6 | 7 | import { DefaultHttpClient } from "../lib/defaultHttpClient"; 8 | import { getHttpMock, HttpMockFacade } from "./mockHttp"; 9 | import { CommonResponse } from "../lib/fetchHttpClient"; 10 | import { ServiceClient } from "../lib/serviceClient"; 11 | import { isNode } from "../lib/msRest"; 12 | import { TestFunction } from "mocha"; 13 | import { redirectPolicy } from "../lib/policies/redirectPolicy"; 14 | 15 | const nodeIt = (isNode ? it : it.skip) as TestFunction; 16 | 17 | describe("redirectLimit", function () { 18 | let httpMock: HttpMockFacade; 19 | beforeEach(() => { 20 | httpMock = getHttpMock(); 21 | httpMock.setup(); 22 | }); 23 | afterEach(() => httpMock.teardown()); 24 | 25 | function getMockedHttpClient(): DefaultHttpClient { 26 | const httpClient = new DefaultHttpClient(); 27 | const fetchMock = httpMock.getFetch(); 28 | if (fetchMock) { 29 | sinon.stub(httpClient, "fetch").callsFake(async (input, init) => { 30 | const response = await fetchMock(input, init); 31 | return (response as unknown) as CommonResponse; 32 | }); 33 | } 34 | return httpClient; 35 | } 36 | 37 | const resourceUrl = "/resource"; 38 | const redirectedUrl_1 = "/redirected_1"; 39 | const redirectedUrl_2 = "/redirected_2"; 40 | 41 | function configureMockRedirectResponses() { 42 | httpMock.get(resourceUrl, async () => { 43 | return { status: 300, headers: { location: redirectedUrl_1 } }; 44 | }); 45 | httpMock.get(redirectedUrl_1, async () => { 46 | return { status: 300, headers: { location: redirectedUrl_2 } }; 47 | }); 48 | httpMock.get(redirectedUrl_2, async () => { 49 | return { status: 200 }; 50 | }); 51 | } 52 | 53 | nodeIt( 54 | "of 20 should follow redirects and return last visited url in response.url", 55 | async function () { 56 | configureMockRedirectResponses(); 57 | 58 | const client = new ServiceClient(undefined, { 59 | httpClient: getMockedHttpClient(), 60 | }); 61 | 62 | // Act 63 | const response = await client.sendRequest({ 64 | url: resourceUrl, 65 | method: "GET", 66 | redirectLimit: 20, 67 | }); 68 | 69 | expect(response.status).to.equal(200); 70 | expect(response.redirected).to.be.true; 71 | expect(response.url).to.equal(redirectedUrl_2); 72 | } 73 | ); 74 | 75 | nodeIt( 76 | "of 0 should not follow redirects and should return last visited url in response.url", 77 | async function () { 78 | configureMockRedirectResponses(); 79 | 80 | const client = new ServiceClient(undefined, { 81 | httpClient: getMockedHttpClient(), 82 | }); 83 | 84 | // Act 85 | const response = await client.sendRequest({ 86 | url: resourceUrl, 87 | method: "GET", 88 | redirectLimit: 0, 89 | }); 90 | 91 | expect(response.status).to.equal(300); 92 | expect(response.headers.get("location")).to.equal(redirectedUrl_1); 93 | expect(response.redirected).to.be.false; 94 | expect(response.url).to.equal(resourceUrl); 95 | } 96 | ); 97 | 98 | nodeIt( 99 | "of 1 should follow 1 redirect and return last visited url in response.url", 100 | async function () { 101 | configureMockRedirectResponses(); 102 | 103 | const client = new ServiceClient(undefined, { 104 | httpClient: getMockedHttpClient(), 105 | }); 106 | 107 | // Act 108 | const response = await client.sendRequest({ 109 | url: resourceUrl, 110 | method: "GET", 111 | redirectLimit: 1, 112 | }); 113 | 114 | expect(response.status).to.equal(300); 115 | expect(response.headers.get("location")).to.equal(redirectedUrl_2); 116 | expect(response.redirected).to.be.true; 117 | expect(response.url).to.equal(redirectedUrl_1); 118 | } 119 | ); 120 | 121 | nodeIt( 122 | "of undefined should follow redirects and return last visited url in response.url", 123 | async function () { 124 | configureMockRedirectResponses(); 125 | 126 | const client = new ServiceClient(undefined, { 127 | httpClient: getMockedHttpClient(), 128 | }); 129 | 130 | // Act 131 | const response = await client.sendRequest({ url: resourceUrl, method: "GET" }); 132 | 133 | expect(response.status).to.equal(200); 134 | expect(response.redirected).to.be.true; 135 | expect(response.url).to.equal(redirectedUrl_2); 136 | } 137 | ); 138 | 139 | nodeIt( 140 | "of undefinded with policy limit of 1 should follow 1 redirect and return last visited url in response.url", 141 | async function () { 142 | configureMockRedirectResponses(); 143 | 144 | const client = new ServiceClient(undefined, { 145 | httpClient: getMockedHttpClient(), 146 | requestPolicyFactories: [redirectPolicy(1)], 147 | }); 148 | 149 | // Act 150 | const response = await client.sendRequest({ url: resourceUrl, method: "GET" }); 151 | 152 | expect(response.status).to.equal(300); 153 | expect(response.headers.get("location")).to.equal(redirectedUrl_2); 154 | expect(response.redirected).to.be.true; 155 | expect(response.url).to.equal(redirectedUrl_1); 156 | } 157 | ); 158 | }); 159 | -------------------------------------------------------------------------------- /test/resources/example-index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Example Domain 5 | 6 | 7 | 8 | 9 | 40 | 41 | 42 | 43 |
44 |

Example Domain

45 |

This domain is established to be used for illustrative examples in documents. You may use this 46 | domain in examples without prior coordination or asking for permission.

47 |

More information...

48 |
49 | 50 | 51 | -------------------------------------------------------------------------------- /test/resources/test.txt: -------------------------------------------------------------------------------- 1 | The quick brown fox jumps over the lazy dog 2 | -------------------------------------------------------------------------------- /test/xhrTests.browser.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. See License.txt in the project root for license information. 3 | 4 | import { AgentSettings } from "../lib/serviceClient"; 5 | import { WebResource } from "../lib/webResource"; 6 | import { assert } from "chai"; 7 | import { parseHeaders, XhrHttpClient } from "../lib/xhrHttpClient"; 8 | 9 | describe("XhrHttpClient", function () { 10 | it("parses headers", function () { 11 | const xhr = { 12 | getAllResponseHeaders: () => "Content-Length: 42\r\n" + "value: hello\r\n", 13 | } as XMLHttpRequest; 14 | const headers = parseHeaders(xhr); 15 | assert.strictEqual(headers.get("content-length"), "42"); 16 | assert.strictEqual(headers.get("value"), "hello"); 17 | }); 18 | 19 | it("parses empty string headers", function () { 20 | const xhr = { 21 | getAllResponseHeaders: () => 22 | "Content-Type: \r\n" + // preserve trailing whitespace in test case 23 | "value:\r\n", 24 | } as XMLHttpRequest; 25 | const headers = parseHeaders(xhr); 26 | assert.strictEqual(headers.get("content-type"), ""); 27 | assert.strictEqual(headers.get("value"), ""); 28 | }); 29 | 30 | it("throws when proxy settings are passed", function () { 31 | const request = new WebResource(); 32 | request.proxySettings = { 33 | host: "1.1.1.1", 34 | port: 8080, 35 | }; 36 | 37 | const client = new XhrHttpClient(); 38 | assert.throws(() => { 39 | client.sendRequest(request); 40 | }, Error); 41 | }); 42 | 43 | it("throws when agent settings are passed", function () { 44 | const request = new WebResource(); 45 | request.agentSettings = {} as AgentSettings; 46 | 47 | const client = new XhrHttpClient(); 48 | assert.throws(() => { 49 | client.sendRequest(request); 50 | }, Error); 51 | }); 52 | }); 53 | -------------------------------------------------------------------------------- /test/xmlTests.ts: -------------------------------------------------------------------------------- 1 | import { parseXML } from "../lib/util/xml"; 2 | import { assert } from "chai"; 3 | import * as msAssert from "./msAssert"; 4 | 5 | describe("XML serializer", function () { 6 | describe("parseXML(string)", function () { 7 | it("with undefined", async function () { 8 | const error: Error = await msAssert.throwsAsync(parseXML(undefined as any)); 9 | assert.notStrictEqual( 10 | error.message.indexOf("Document is empty"), 11 | -1, 12 | `error.message ("${error.message}") should have contained "Document is empty"` 13 | ); 14 | }); 15 | 16 | it("with null", async function () { 17 | // tslint:disable-next-line:no-null-keyword 18 | const error: Error = await msAssert.throwsAsync(parseXML(null as any)); 19 | assert.notStrictEqual( 20 | error.message.indexOf("Document is empty"), 21 | -1, 22 | `error.message ("${error.message}") should have contained "Document is empty"` 23 | ); 24 | }); 25 | 26 | it("with empty", async function () { 27 | await msAssert.throwsAsync(parseXML("")); 28 | }); 29 | 30 | it("with text", async function () { 31 | await msAssert.throwsAsync(parseXML("Hello World!")); 32 | }); 33 | 34 | it("with empty element", async function () { 35 | const xml: any = await parseXML(""); 36 | assert.deepStrictEqual(xml, ``); 37 | }); 38 | 39 | it("with empty element with attribute", async function () { 40 | const xml: any = await parseXML(``); 41 | assert.deepStrictEqual(xml, { 42 | $: { 43 | healthy: "true", 44 | }, 45 | }); 46 | }); 47 | 48 | it("with element", async function () { 49 | const xml: any = await parseXML(""); 50 | assert.deepStrictEqual(xml, ``); 51 | }); 52 | 53 | it("with element with value", async function () { 54 | const xml: any = await parseXML("hurray"); 55 | assert.deepStrictEqual(xml, `hurray`); 56 | }); 57 | 58 | it("with element with attribute", async function () { 59 | const xml: any = await parseXML(``); 60 | assert.deepStrictEqual(xml, { 61 | $: { 62 | healthy: "true", 63 | }, 64 | }); 65 | }); 66 | 67 | it("with element with attribute and value", async function () { 68 | const xml: any = await parseXML(`yum`); 69 | assert.deepStrictEqual(xml, { 70 | $: { 71 | healthy: "true", 72 | }, 73 | _: "yum", 74 | }); 75 | }); 76 | 77 | it("with element with child empty element", async function () { 78 | const xml: any = await parseXML(``); 79 | assert.deepStrictEqual(xml, { 80 | apples: ``, 81 | }); 82 | }); 83 | 84 | it("with element with child empty element with attribute", async function () { 85 | const xml: any = await parseXML(``); 86 | assert.deepStrictEqual(xml, { 87 | apples: { 88 | $: { 89 | tasty: "true", 90 | }, 91 | }, 92 | }); 93 | }); 94 | 95 | it("with element with child element with value", async function () { 96 | const xml: any = await parseXML(`yum`); 97 | assert.deepStrictEqual(xml, { 98 | apples: "yum", 99 | }); 100 | }); 101 | 102 | it("with element with child element with attribute and value", async function () { 103 | const xml: any = await parseXML(`yum`); 104 | assert.deepStrictEqual(xml, { 105 | apples: { 106 | $: { 107 | tasty: "true", 108 | }, 109 | _: "yum", 110 | }, 111 | }); 112 | }); 113 | }); 114 | 115 | it("should handle errors gracefully", async function () { 116 | try { 117 | await parseXML("INVALID"); 118 | throw new Error("did not throw"); 119 | } catch (err) { 120 | if (err.message === "did not throw") { 121 | throw err; 122 | } 123 | } 124 | }); 125 | }); 126 | -------------------------------------------------------------------------------- /tsconfig.es.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "module": "es6", 5 | "outDir": "es" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "sourceMap": true, 5 | "newLine": "LF", 6 | "target": "es5", 7 | "moduleResolution": "node", 8 | "noImplicitReturns": true, 9 | "noUnusedLocals": true, 10 | "noUnusedParameters": true, 11 | "outDir": "dist", 12 | "strict": true, 13 | "declaration": true, 14 | "declarationMap": true, 15 | "importHelpers": true, 16 | "esModuleInterop": true, 17 | "allowSyntheticDefaultImports": true, 18 | "lib": ["es5", "es6", "es7", "esnext", "esnext.asynciterable", "es2015.iterable"] 19 | }, 20 | "compileOnSave": true, 21 | "exclude": ["node_modules"], 22 | "include": ["./lib/**/*.ts", "./samples/**/*.ts", "./test/**/*.ts"] 23 | } 24 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["tslint-eslint-rules"], 3 | "rules": { 4 | "class-name": true, 5 | "comment-format": [true, "check-space"], 6 | "indent": [true, "spaces"], 7 | "ter-indent": [ 8 | true, 9 | 2, 10 | { 11 | "SwitchCase": 1 12 | } 13 | ], 14 | "linebreak-style": [true, "LF"], 15 | "one-line": [true, "check-open-brace", "check-whitespace"], 16 | "no-var-keyword": true, 17 | "quotemark": [true, "double", "avoid-escape"], 18 | "semicolon": [true, "always", "ignore-bound-class-methods"], 19 | "whitespace": [ 20 | true, 21 | "check-branch", 22 | "check-decl", 23 | "check-operator", 24 | "check-module", 25 | "check-separator", 26 | "check-type" 27 | ], 28 | "typedef-whitespace": [ 29 | true, 30 | { 31 | "call-signature": "nospace", 32 | "index-signature": "nospace", 33 | "parameter": "nospace", 34 | "property-declaration": "nospace", 35 | "variable-declaration": "nospace" 36 | }, 37 | { 38 | "call-signature": "onespace", 39 | "index-signature": "onespace", 40 | "parameter": "onespace", 41 | "property-declaration": "onespace", 42 | "variable-declaration": "onespace" 43 | } 44 | ], 45 | "jsdoc-format": true, 46 | "no-inferrable-types": [true], 47 | "no-internal-module": true, 48 | "no-null-keyword": true, 49 | "no-return-await": true, 50 | "no-switch-case-fall-through": true, 51 | "no-trailing-whitespace": true, 52 | "prefer-const": true, 53 | "triple-equals": [true, "allow-undefined-check"] 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /webpack.testconfig.ts: -------------------------------------------------------------------------------- 1 | import * as webpack from "webpack"; 2 | import * as glob from "glob"; 3 | import * as path from "path"; 4 | 5 | const config: webpack.Configuration = { 6 | entry: glob.sync(path.join(__dirname, "test/**/*[^node.].ts")), 7 | mode: "development", 8 | devtool: "source-map", 9 | stats: { 10 | colors: true, 11 | hash: false, 12 | version: false, 13 | timings: false, 14 | assets: false, 15 | chunks: false, 16 | modules: false, 17 | reasons: false, 18 | children: false, 19 | source: false, 20 | errors: true, 21 | errorDetails: false, 22 | warnings: false, 23 | publicPath: false, 24 | }, 25 | output: { 26 | filename: "msRest.browser.test.js", 27 | path: path.resolve(__dirname, "test"), 28 | }, 29 | plugins: [ 30 | new webpack.NormalModuleReplacementPlugin( 31 | /(\.).+util\/base64/, 32 | path.resolve(__dirname, "./lib/util/base64.browser.ts") 33 | ), 34 | new webpack.NormalModuleReplacementPlugin( 35 | /(\.).+util\/xml/, 36 | path.resolve(__dirname, "./lib/util/xml.browser.ts") 37 | ), 38 | new webpack.NormalModuleReplacementPlugin( 39 | /(\.).+defaultHttpClient/, 40 | path.resolve(__dirname, "./lib/defaultHttpClient.browser.ts") 41 | ), 42 | new webpack.NormalModuleReplacementPlugin( 43 | /(\.).+msRestUserAgentPolicy/, 44 | path.resolve(__dirname, "./lib/policies/msRestUserAgentPolicy.browser.ts") 45 | ), 46 | new webpack.NormalModuleReplacementPlugin( 47 | /(\.).+agentPolicy/, 48 | path.resolve(__dirname, "./lib/policies/agentPolicy.browser.ts") 49 | ), 50 | new webpack.NormalModuleReplacementPlugin( 51 | /(\.).+proxyPolicy/, 52 | path.resolve(__dirname, "./lib/policies/proxyPolicy.browser.ts") 53 | ), 54 | ], 55 | module: { 56 | rules: [ 57 | { 58 | test: /\.tsx?$/, 59 | loader: "ts-loader", 60 | exclude: /(node_modules)/, 61 | options: { configFile: path.join(__dirname, "./tsconfig.es.json") }, 62 | }, 63 | ], 64 | }, 65 | resolve: { 66 | extensions: [".tsx", ".ts", ".js"], 67 | }, 68 | node: { 69 | dns: false, 70 | fs: "empty", 71 | net: "empty", 72 | path: "empty", 73 | process: "mock", 74 | tls: "empty", 75 | tty: false, 76 | tunnel: "empty", 77 | v8: false, 78 | }, 79 | }; 80 | 81 | export = config; 82 | -------------------------------------------------------------------------------- /xunit.xml: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Azure/ms-rest-js/d0527449c295fc8df511fd7d4c0a0308c9ed2ce6/xunit.xml --------------------------------------------------------------------------------