├── .gitignore ├── src ├── internal │ ├── Helpers.ts │ ├── RequestParameters.ts │ ├── Constants.ts │ ├── AuthenticationType.ts │ ├── Timers.ts │ └── AuthenticationManager.ts ├── index.ts ├── AzureMapsTileLayerOptions.ts ├── AuthenticationOptions.ts └── AzureMaps.ts ├── azure-pipelines.yml ├── .github ├── CODE_OF_CONDUCT.md ├── ISSUE_TEMPLATE.md └── PULL_REQUEST_TEMPLATE.md ├── tsconfig.json ├── LICENSE.md ├── package.json ├── CHANGELOG.md ├── SECURITY.md ├── examples ├── Render Azure Maps in Leaflet.html ├── Show Azure Maps in Leaflet layer control.html └── Azure Maps Leaflet options.html ├── CONTRIBUTING.md ├── typings └── index.d.ts ├── README.md └── dist └── azure-maps-leaflet.min.js /.gitignore: -------------------------------------------------------------------------------- 1 | # Directories to ignore 2 | js/ 3 | node_modules/ 4 | test/lib/ -------------------------------------------------------------------------------- /src/internal/Helpers.ts: -------------------------------------------------------------------------------- 1 | export class Helpers { 2 | /** Generates a unique GUID. */ 3 | public static uuid(): string { 4 | //@ts-ignore 5 | return ([1e7] + -1e3 + -4e3 + -8e3 + -1e11).replace(/[018]/g, c => 6 | (c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> c / 4).toString(16) 7 | ); 8 | } 9 | } -------------------------------------------------------------------------------- /azure-pipelines.yml: -------------------------------------------------------------------------------- 1 | # HTML 2 | # Archive your static HTML project and save it with the build record. 3 | # Add steps that build, run tests, deploy, and more: 4 | # https://aka.ms/yaml 5 | 6 | trigger: 7 | - master 8 | 9 | pool: 10 | vmImage: 'ubuntu-latest' 11 | 12 | steps: 13 | - task: ArchiveFiles@2 14 | inputs: 15 | rootFolderOrFile: '$(build.sourcesDirectory)' 16 | includeRootFolder: false 17 | - task: PublishBuildArtifacts@1 18 | -------------------------------------------------------------------------------- /.github/CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Microsoft Open Source Code of Conduct 2 | 3 | This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). 4 | 5 | Resources: 6 | 7 | - [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/) 8 | - [Microsoft Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) 9 | - Contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with questions or concerns 10 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | /* Build the structure of the SDK */ 2 | import L from 'leaflet'; 3 | import { AzureMaps } from './AzureMaps'; 4 | import { AzureMapsTileLayerOptions } from './AzureMapsTileLayerOptions'; 5 | 6 | /** 7 | * Static function to create a tile layer that connects to the Azure Maps Render V2 service. 8 | * @param options Azure Maps Tile layer options. 9 | */ 10 | const azMapLayer = function(options: AzureMapsTileLayerOptions){ 11 | return new AzureMaps(options); 12 | }; 13 | 14 | /* Extent the Leaflet tile layer functions/classes with static functions for Azure Maps. */ 15 | 16 | //@ts-ignore 17 | L.TileLayer.azureMaps = azMapLayer; 18 | 19 | //@ts-ignore 20 | L.TileLayer.AzureMaps = AzureMaps; 21 | 22 | //@ts-ignore 23 | L.tileLayer.azureMaps = azMapLayer; 24 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "outDir": "js", 4 | "module": "esnext", 5 | "moduleResolution": "node", 6 | "resolveJsonModule": true, 7 | "esModuleInterop": true, 8 | "importHelpers": true, 9 | "alwaysStrict": true, 10 | "target": "es5", 11 | "baseUrl": "./", 12 | "lib": [ 13 | "dom", 14 | "es5", 15 | "es2015", 16 | "es2017.object" 17 | ], 18 | "declaration": true, 19 | "declarationDir": "./js/typings", 20 | "typeRoots": [ "./types", "./node_modules/@types"] 21 | }, 22 | "include": [ 23 | "src/**/*" 24 | ], 25 | "exclude": [ 26 | "node_modules", 27 | "typings", 28 | "types" 29 | ] 30 | } -------------------------------------------------------------------------------- /src/internal/RequestParameters.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This is the object type expected to be returned by the transformRequest callback. 3 | */ 4 | export class RequestParameters { 5 | /** 6 | * Used to specify the cross-origin request (CORs) credentials setting. Can be `'same-origin'` or `'include'`. 7 | */ 8 | public credentials?: string = undefined; 9 | 10 | /** 11 | * The headers to be sent with the request. 12 | */ 13 | public headers?: object = undefined; 14 | 15 | /** 16 | * The url to be requested. 17 | */ 18 | public url?: string = undefined; 19 | 20 | /** 21 | * @internal 22 | */ 23 | constructor(credentials?: string, headers?: object, url?: string) { 24 | this.credentials = credentials; 25 | this.headers = headers; 26 | this.url = url; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/internal/Constants.ts: -------------------------------------------------------------------------------- 1 | export const Constants = { 2 | // Enable localStorage for IE, as sessionStorage does not work for localhost. 3 | preferredCacheLocation: "localStorage", 4 | storage: { 5 | accessTokenKey: "access.token.key", 6 | testStorageKey: "testStorage" 7 | }, 8 | events: { 9 | tokenAcquired: "tokenacquired" 10 | }, 11 | tokenExpiresIn: 3599, 12 | tokenRefreshClockSkew: 300, 13 | errors: { 14 | tokenExpired: "Token Expired, Try again" 15 | }, 16 | AUTHORIZATION: "authorization", 17 | AUTHORIZATION_SCHEME: "Bearer", 18 | AUTHORIZATION_SCHEME_SAS: "jwt-sas", 19 | MAP_AGENT: "Map-Agent", 20 | MS_AM_REQUEST_ORIGIN: "Ms-Am-Request-Origin", 21 | MS_AM_REQUEST_ORIGIN_VALUE: "MapControl", 22 | X_MS_CLIENT_ID: "x-ms-client-id", 23 | SESSION_ID: "Session-Id", 24 | SHORT_DOMAIN: 'atlas.microsoft.com', 25 | DEFAULT_DOMAIN: 'https://atlas.microsoft.com/', 26 | SDK_VERSION: '0.0.1', 27 | TARGET_SDK: 'Leaflet', 28 | RENDERV2_VERSION: '2.1' 29 | }; -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 4 | > Please provide us with the following information: 5 | > --------------------------------------------------------------- 6 | 7 | ### This issue is for a: (mark with an `x`) 8 | ``` 9 | - [ ] bug report -> please search issues before submitting 10 | - [ ] feature request 11 | - [ ] documentation issue or request 12 | - [ ] regression (a behavior that used to work and stopped in a new release) 13 | ``` 14 | 15 | ### Minimal steps to reproduce 16 | > 17 | 18 | ### Any log messages given by the failure 19 | > 20 | 21 | ### Expected/desired behavior 22 | > 23 | 24 | ### OS and Version? 25 | > Windows 7, 8 or 10. Linux (which distribution). macOS (Yosemite? El Capitan? Sierra?) 26 | 27 | ### Versions 28 | > 29 | 30 | ### Mention any other details that might be useful 31 | 32 | > --------------------------------------------------------------- 33 | > Thanks! We'll be in touch soon. 34 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ## Purpose 2 | 3 | * ... 4 | 5 | ## Does this introduce a breaking change? 6 | 7 | ``` 8 | [ ] Yes 9 | [ ] No 10 | ``` 11 | 12 | ## Pull Request Type 13 | What kind of change does this Pull Request introduce? 14 | 15 | 16 | ``` 17 | [ ] Bugfix 18 | [ ] Feature 19 | [ ] Code style update (formatting, local variables) 20 | [ ] Refactoring (no functional changes, no api changes) 21 | [ ] Documentation content changes 22 | [ ] Other... Please describe: 23 | ``` 24 | 25 | ## How to Test 26 | * Get the code 27 | 28 | ``` 29 | git clone [repo-address] 30 | cd [repo-name] 31 | git checkout [branch-name] 32 | npm install 33 | ``` 34 | 35 | * Test the code 36 | 37 | ``` 38 | ``` 39 | 40 | ## What to Check 41 | Verify that the following are valid 42 | * ... 43 | 44 | ## Other Information 45 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) Microsoft Corporation. 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 -------------------------------------------------------------------------------- /src/internal/AuthenticationType.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * An enumeration used to specify the type of authentication mechanism to use. 3 | */ 4 | export enum AuthenticationType { 5 | /** 6 | * The subscription key authentication mechanism. 7 | * Literal value `"subscriptionKey"` 8 | */ 9 | subscriptionKey = "subscriptionKey", 10 | 11 | /** 12 | * The AAD implicit grant mechanism. Recommended for pages protected by a sign-in. 13 | * By default the page will be redirected to the AAD login when the map control initializes. 14 | * Specify a logged-in `AuthenticationContext` in the `AuthenticationOptions` 15 | * for greater control over when/how the users signs in. 16 | * Literal value `"aad"` 17 | */ 18 | aad = "aad", 19 | 20 | /** 21 | * The anonymous authentication mechanism. Recommended for pages. 22 | * Allows a callback responsible for acquiring an authentication token to be provided. 23 | * Literal value `"anonymous"` 24 | */ 25 | anonymous = "anonymous", 26 | 27 | /** 28 | * The anonymous authentication mechanism. Recommended for pages. 29 | * Allows a callback responsible for acquiring a SAS authentication token to be provided. 30 | * Literal value `"sas"` 31 | */ 32 | sas = "sas" 33 | } 34 | -------------------------------------------------------------------------------- /src/AzureMapsTileLayerOptions.ts: -------------------------------------------------------------------------------- 1 | import { AuthenticationOptions } from './AuthenticationOptions'; 2 | 3 | /** Options for an Azure Maps tile layer. */ 4 | export interface AzureMapsTileLayerOptions extends L.TileLayerOptions { 5 | /** Required. Authentication options for connecting to Azure Maps. */ 6 | authOptions: AuthenticationOptions; 7 | 8 | /** The tile set ID layer to load from the Azure Maps Render V2 service. Custom tileset ID's that return raster tiles that are 256x256 pixels in size can also be specified as a string. Default `'microsoft.base.road'` */ 9 | tilesetId?: string; 10 | 11 | /** Geopolitical view of the map. [Supported views](https://docs.microsoft.com/en-us/azure/azure-maps/supported-languages#sdks) Default: `'Auto'` */ 12 | view?: string; 13 | 14 | /** Language code. [Supported languages](https://docs.microsoft.com/azure/azure-maps/supported-languages) Default: `'en-US'` */ 15 | language?: string; 16 | 17 | /** The desired date and time of the requested tile. This parameter must be specified in the standard date-time format (e.g. 2019-11-14T16:03:00-08:00), as defined by ISO 8601. This parameter is only supported when tilesetId parameter is set to `microsoft.weather.infrared.main` or `microsoft.weather.radar.main`. */ 18 | timeStamp?: string | Date; 19 | 20 | /** The thickness of lines when using the traffic flow tilesets. Default: `5` */ 21 | trafficFlowThickness?: number; 22 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "azure-maps-leaflet", 3 | "version": "0.0.4", 4 | "description": "A leafletjs plugin that makes it easy to overlay tile layers from the Azure Maps tile services.", 5 | "keywords": [ 6 | "azure", 7 | "maps", 8 | "animations", 9 | "animate", 10 | "lbs", 11 | "microsoft" 12 | ], 13 | "bugs": { 14 | "url": "https://github.com/Azure-Samples/azure-maps-leaflet/issues" 15 | }, 16 | "homepage": "https://github.com/Azure-Samples/azure-maps-leaflet#readme", 17 | "scripts": { 18 | "build": "node ./build/build.js" 19 | }, 20 | "author": "Microsoft Corporation", 21 | "license": "MIT", 22 | "dependencies": { 23 | "@types/adal-angular": "^1.0.1" 24 | }, 25 | "devDependencies": { 26 | "@types/leaflet": "^1.5.19", 27 | "@types/lodash": "^4.14.136", 28 | "@types/mocha": "^5.2.6", 29 | "@types/puppeteer": "^1.20.4", 30 | "adal-angular": "^1.0.18", 31 | "fs-extra": "^7.0.1", 32 | "leaflet": "^1.7.1", 33 | "lodash": "^4.17.21", 34 | "mocha": "^7.1.1", 35 | "mocha-parallel-tests": "^2.3.0", 36 | "puppeteer": "^2.1.1", 37 | "rollup": "^1.32.1", 38 | "rollup-plugin-commonjs": "^10.0.1", 39 | "rollup-plugin-node-resolve": "^5.2.0", 40 | "rollup-plugin-uglify": "^6.0.2", 41 | "tslint": "^5.15.0", 42 | "typescript": "^3.8.3", 43 | "yargs": "^13.3.2" 44 | }, 45 | "files": [ 46 | "typings/**/*", 47 | "dist/**/*", 48 | "license.md" 49 | ], 50 | "types": "typings/index.d.ts", 51 | "main": "dist/azure-maps-leaflet.min.js" 52 | } 53 | -------------------------------------------------------------------------------- /src/internal/Timers.ts: -------------------------------------------------------------------------------- 1 | type WorkerHandler = {callback: () => void, worker: Worker}; 2 | 3 | const SetTimeoutWorkerCode = `onmessage = function (event) { 4 | var delay = event.data.time; // milliseconds 5 | setTimeout(() => { 6 | postMessage({id: event.data.id}); 7 | }, delay); 8 | };`; 9 | 10 | /** A class that provides a setTimeout function that will work in inactive browser tabs, or during mobile lock screens. */ 11 | export class Timers { 12 | 13 | private static _workerTable: { [key: number]: WorkerHandler } = {}; 14 | 15 | public static clearTimeout(id: number): void { 16 | const w = Timers._workerTable[id]; 17 | if(w) { 18 | w.worker.terminate(); 19 | delete Timers._workerTable[id]; 20 | } 21 | } 22 | 23 | public static setTimeout(callback: () => void, timeout: number): number { 24 | const id = Math.round(Math.random() * 1000000000); 25 | 26 | const blob = new Blob([SetTimeoutWorkerCode]); 27 | const blobURL = window.URL.createObjectURL(blob); 28 | 29 | const worker = new Worker(blobURL); 30 | 31 | worker.addEventListener("message", Timers._receivedSetTimeoutMessage); 32 | 33 | Timers._workerTable[id] = { 34 | callback: callback, 35 | worker: worker 36 | }; 37 | 38 | //Start the worker. 39 | worker.postMessage({id: id, time: timeout}); 40 | 41 | return id; 42 | } 43 | 44 | private static _receivedSetTimeoutMessage(e: any): void { 45 | const w = Timers._workerTable[e.data.id]; 46 | if(w) { 47 | w.callback(); 48 | w.worker.terminate(); 49 | delete Timers._workerTable[e.data.id]; 50 | } 51 | } 52 | } -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## azure-maps-leaflet Changelog 2 | 3 | 4 | # 0.0.2 (2024-06-11) 5 | 6 | - Modified timer class to use `setTimeout` in worker as originaly solution was causing high memory usage. 7 | - Updated packages. 8 | 9 | 10 | # 0.0.3 11 | 12 | - Update packages 13 | 14 | 15 | # 0.0.2 16 | 17 | 18 | # 0.0.1 (2021-01-12) 19 | 20 | Initial release. 21 | 22 | **Features** 23 | 24 | - Authenticate using an Azure Maps subscription key or Azure Active Directory. 25 | - Works with with Azure Public and Government clouds. 26 | - [Supports over 30 languages](https://docs.microsoft.com/azure/azure-maps/supported-languages) 27 | - Supported layers: 28 | - **Road maps** 29 | - Main (`microsoft.base.road`) - All layers with our main style. 30 | - Labels (`microsoft.base.labels.road`) - Label data in our main style. 31 | - Hybrid (`microsoft.base.hybrid.road`) - Road, boundary and label data in our main style. 32 | - Dark grayscale (`microsoft.base.darkgrey`) - All layers with our dark grayscale style. 33 | - **Imagery** (`microsoft.imagery`) 34 | - **Traffic Flow** 35 | - absolute (`microsoft.traffic.flow.absolute`) 36 | - reduced-sensitivity (`microsoft.traffic.flow.reduced-sensitivity`) 37 | - relative (`microsoft.traffic.flow.relative`) 38 | - relative-delay (microsoft.traffic.flow.relative-delay`) 39 | - **Traffic Incident** 40 | - night (`microsoft.traffic.incident.night`) 41 | - s1 (`microsoft.traffic.incident.s1`) 42 | - s2 (`microsoft.traffic.incident.s2`) 43 | - s3 (`microsoft.traffic.incident.s3`) 44 | - **Weather** 45 | - Infrared (`microsoft.weather.infrared.main`) - Latest Infrared Satellite images shows clouds by their temperature. 46 | - Radar (`microsoft.weather.radar.main`) - Latest weather radar images including areas of rain, snow, ice and mixed conditions. 47 | -------------------------------------------------------------------------------- /src/AuthenticationOptions.ts: -------------------------------------------------------------------------------- 1 | import AuthenticationContext from 'adal-angular'; 2 | import { AuthenticationType } from './internal/AuthenticationType'; 3 | 4 | /** 5 | * The callback function used to acquire an authentication token in anonymous authentication mode. 6 | * Resolve with the authentication token or reject with any errors. 7 | */ 8 | export type getAuthTokenCallback = (resolve: (value?: string) => void, reject: (reason?: any) => void) => void; 9 | 10 | /** 11 | * Options for specifying how the map control should authenticate with the Azure Maps services. 12 | */ 13 | export interface AuthenticationOptions { 14 | /** A URL string pointing to the domain of the Azure Maps service, default is `"atlas.microsoft.com"`. */ 15 | azMapsDomain?: string; 16 | 17 | /** 18 | * The authentication mechanism to be used. 19 | */ 20 | authType?: AuthenticationType; 21 | 22 | /** 23 | * Subscription key from your Azure Maps account. 24 | * Must be specified for subscription key authentication type. 25 | */ 26 | subscriptionKey?: string; 27 | 28 | /** 29 | * The Azure Maps client ID, This is an unique identifier used to identify the maps account. 30 | * Preferred to always be specified, but must be specified for AAD and anonymous authentication types. 31 | */ 32 | clientId?: string; 33 | 34 | /** 35 | * The Azure AD registered app ID. This is the app ID of an app registered in your Azure AD tenant. 36 | * Must be specified for AAD authentication type. 37 | */ 38 | aadAppId?: string; 39 | 40 | /** 41 | * The AAD tenant that owns the registered app specified by `aadAppId`. 42 | * Must be specified for AAD authentication type. 43 | */ 44 | aadTenant?: string; 45 | 46 | /** 47 | * The AAD instance to use for logging in. 48 | * Can be optionally specified when using the AAD authentication type. 49 | * By default the `https://login.microsoftonline.com/` instance will be used. 50 | */ 51 | aadInstance?: string; 52 | 53 | /** 54 | * A callback to use with the anonymous authentication mechanism. 55 | * This callback will be responsible for resolving to a authentication token. 56 | * E.g. fetching a CORS protected token from an endpoint. 57 | */ 58 | getToken?: getAuthTokenCallback; 59 | 60 | /** 61 | * Optionally provide an existing `AuthenticationContext` from the ADAL.js library. 62 | * This authentication context will be used to acquire the AAD token. 63 | * Only used with the AAD authentication type. 64 | * This auth context must be configured to use the same AAD app ID as `this.aadAppId`. 65 | * If this is not provided all map instances will share their own private auth context. 66 | */ 67 | authContext?: AuthenticationContext; 68 | } 69 | -------------------------------------------------------------------------------- /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 [Microsoft's definition of a security vulnerability](https://docs.microsoft.com/en-us/previous-versions/tn-archive/cc751383(v=technet.10)) of a security vulnerability, 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://msrc.microsoft.com/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 the [Microsoft Security Response Center PGP Key page](https://www.microsoft.com/en-us/msrc/pgp-key-msrc). 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://www.microsoft.com/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://microsoft.com/msrc/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://www.microsoft.com/en-us/msrc/cvd). 40 | 41 | 42 | -------------------------------------------------------------------------------- /examples/Render Azure Maps in Leaflet.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Render Azure Maps in Leaflet - Azure Maps Web SDK Samples 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 62 | 63 | 64 |
65 | 66 |
67 | 68 |

Render Azure Maps in Leaflet

69 |
70 | This sample shows how to render Azure Maps Raster Tiles in the Leaflet JS map control. 71 | This samples uses the open source Azure Maps Leaflet plugin. 72 |
73 | 74 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to azure-maps-leaflet 2 | 3 | This project welcomes contributions and suggestions. Most contributions require you to agree to a 4 | Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us 5 | the rights to use your contribution. For details, visit https://cla.opensource.microsoft.com. 6 | 7 | When you submit a pull request, a CLA bot will automatically determine whether you need to provide 8 | a CLA and decorate the PR appropriately (e.g., status check, comment). Simply follow the instructions 9 | provided by the bot. You will only need to do this once across all repos using our CLA. 10 | 11 | This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). 12 | For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or 13 | contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. 14 | 15 | - [Code of Conduct](#coc) 16 | - [Issues and Bugs](#issue) 17 | - [Feature Requests](#feature) 18 | - [Submission Guidelines](#submit) 19 | 20 | ## Code of Conduct 21 | Help us keep this project open and inclusive. Please read and follow our [Code of Conduct](https://opensource.microsoft.com/codeofconduct/). 22 | 23 | ## Found an Issue? 24 | If you find a bug in the source code or a mistake in the documentation, you can help us by 25 | [submitting an issue](#submit-issue) to the GitHub Repository. Even better, you can 26 | [submit a Pull Request](#submit-pr) with a fix. 27 | 28 | ## Want a Feature? 29 | You can *request* a new feature by [submitting an issue](#submit-issue) to the GitHub 30 | Repository. If you would like to *implement* a new feature, please submit an issue with 31 | a proposal for your work first, to be sure that we can use it. 32 | 33 | * **Small Features** can be crafted and directly [submitted as a Pull Request](#submit-pr). 34 | 35 | ## Submission Guidelines 36 | 37 | ### Submitting an Issue 38 | Before you submit an issue, search the archive, maybe your question was already answered. 39 | 40 | If your issue appears to be a bug, and hasn't been reported, open a new issue. 41 | Help us to maximize the effort we can spend fixing issues and adding new 42 | features, by not reporting duplicate issues. Providing the following information will increase the 43 | chances of your issue being dealt with quickly: 44 | 45 | * **Overview of the Issue** - if an error is being thrown a non-minified stack trace helps 46 | * **Version** - what version is affected (e.g. 0.1.2) 47 | * **Motivation for or Use Case** - explain what are you trying to do and why the current behavior is a bug for you 48 | * **Browsers and Operating System** - is this a problem with all browsers? 49 | * **Reproduce the Error** - provide a live example or a unambiguous set of steps 50 | * **Related Issues** - has a similar issue been reported before? 51 | * **Suggest a Fix** - if you can't fix the bug yourself, perhaps you can point to what might be 52 | causing the problem (line of code or commit) 53 | 54 | You can file new issues by providing the above information at the corresponding repository's issues link: https://github.com/Azure-Samples/azure-maps-leaflet/issues/new]. 55 | 56 | ### Submitting a Pull Request (PR) 57 | Before you submit your Pull Request (PR) consider the following guidelines: 58 | 59 | * Search the repository (https://github.com/Azure-Samples/azure-maps-leaflet/pulls) for an open or closed PR 60 | that relates to your submission. You don't want to duplicate effort. 61 | 62 | * Make your changes in a new git fork: 63 | 64 | * Commit your changes using a descriptive commit message 65 | * Push your fork to GitHub: 66 | * In GitHub, create a pull request 67 | * If we suggest changes then: 68 | * Make the required updates. 69 | * Rebase your fork and force push to your GitHub repository (this will update your Pull Request): 70 | 71 | ```shell 72 | git rebase master -i 73 | git push -f 74 | ``` 75 | 76 | That's it! Thank you for your contribution! 77 | -------------------------------------------------------------------------------- /examples/Show Azure Maps in Leaflet layer control.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Show Azure Maps in Leaflet layer control - Azure Maps Web SDK Samples 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 85 | 86 | 87 | 88 |
89 | 90 |
91 | 92 |

Show Azure Maps in Leaflet layer control

93 |
94 | This sample shows how to create add all the different Azure Maps tile layers to the layer control in leaflet. 95 | This samples uses the open source Azure Maps Leaflet plugin. 97 |
98 | 99 | 100 | -------------------------------------------------------------------------------- /typings/index.d.ts: -------------------------------------------------------------------------------- 1 | import leaflet from 'leaflet'; 2 | import AuthenticationContext from 'adal-angular'; 3 | 4 | declare namespace L { 5 | 6 | /** 7 | * The callback function used to acquire an authentication token in anonymous authentication mode. 8 | * Resolve with the authentication token or reject with any errors. 9 | */ 10 | export type getAuthTokenCallback = (resolve: (value?: string) => void, reject: (reason?: any) => void) => void; 11 | 12 | /** 13 | * Options for specifying how the map control should authenticate with the Azure Maps services. 14 | */ 15 | export interface AuthenticationOptions { 16 | /** A URL string pointing to the domain of the Azure Maps service, default is `"atlas.microsoft.com"`. */ 17 | azMapsDomain?: string; 18 | 19 | /** 20 | * The authentication mechanism to be used. 21 | */ 22 | authType?: 'subscriptionKey' | 'aad' | 'anonymous'; 23 | 24 | /** 25 | * Subscription key from your Azure Maps account. 26 | * Must be specified for subscription key authentication type. 27 | */ 28 | subscriptionKey?: string; 29 | 30 | /** 31 | * The Azure Maps client ID, This is an unique identifier used to identify the maps account. 32 | * Preferred to always be specified, but must be specified for AAD and anonymous authentication types. 33 | */ 34 | clientId?: string; 35 | 36 | /** 37 | * The Azure AD registered app ID. This is the app ID of an app registered in your Azure AD tenant. 38 | * Must be specified for AAD authentication type. 39 | */ 40 | aadAppId?: string; 41 | 42 | /** 43 | * The AAD tenant that owns the registered app specified by `aadAppId`. 44 | * Must be specified for AAD authentication type. 45 | */ 46 | aadTenant?: string; 47 | 48 | /** 49 | * The AAD instance to use for logging in. 50 | * Can be optionally specified when using the AAD authentication type. 51 | * By default the `https://login.microsoftonline.com/` instance will be used. 52 | */ 53 | aadInstance?: string; 54 | 55 | /** 56 | * A callback to use with the anonymous authentication mechanism. 57 | * This callback will be responsible for resolving to a authentication token. 58 | * E.g. fetching a CORS protected token from an endpoint. 59 | */ 60 | getToken?: getAuthTokenCallback; 61 | 62 | /** 63 | * Optionally provide an existing `AuthenticationContext` from the ADAL.js library. 64 | * This authentication context will be used to acquire the AAD token. 65 | * Only used with the AAD authentication type. 66 | * This auth context must be configured to use the same AAD app ID as `this.aadAppId`. 67 | * If this is not provided all map instances will share their own private auth context. 68 | */ 69 | authContext?: AuthenticationContext; 70 | } 71 | 72 | /** Options for an Azure Maps tile layer. */ 73 | export interface AzureMapsTileLayerOptions extends leaflet.TileLayerOptions { 74 | /** Required. Authentication options for connecting to Azure Maps. */ 75 | authOptions: AuthenticationOptions; 76 | 77 | /** The tile set ID layer to load from the Azure Maps Render V2 service. */ 78 | tilesetId?: string; 79 | 80 | /** Geopolitical view of the map. */ 81 | view?: string; 82 | 83 | /** Language code. [Supported languages](https://docs.microsoft.com/azure/azure-maps/supported-languages) */ 84 | language?: string; 85 | 86 | /** The desired date and time of the requested tile. This parameter must be specified in the standard date-time format (e.g. 2019-11-14T16:03:00-08:00), as defined by ISO 8601. This parameter is only supported when tilesetId parameter is set to `microsoft.weather.infrared.main` or `microsoft.weather.radar.main`. */ 87 | timeStamp?: string | Date; 88 | 89 | /** The thickness of lines when using the traffic flow tilesets. Default: 5 */ 90 | trafficFlowThickness?: number; 91 | } 92 | 93 | export module Tilelayer { 94 | 95 | /** 96 | * A tile layer that connects to the Azure Maps Render V2 service. 97 | */ 98 | export class AzureMaps extends leaflet.TileLayer { 99 | 100 | /************************ 101 | * Constructor 102 | ***********************/ 103 | 104 | /** 105 | * A tile layer that connects to the Azure Maps Render V2 service. 106 | * @param options Azure Maps Tile layer options. 107 | */ 108 | constructor(options: AzureMapsTileLayerOptions); 109 | 110 | /************************ 111 | * Public functions 112 | ***********************/ 113 | 114 | /** 115 | * Gets the attributions for the tile layer. 116 | */ 117 | public getAttribution(): string; 118 | 119 | /** 120 | * Gets the tile URL for the specified map tile coordinates. 121 | * @param coords Map tile coordinates. 122 | */ 123 | public getTileUrl(coords: leaflet.Coords): string; 124 | 125 | /** 126 | * Creates a map tile for the layer. 127 | * @param coords Map tile coordinates. 128 | * @param done Callback function for when the map tile has finished loading. 129 | */ 130 | public createTile(coords: leaflet.Coords, done: leaflet.DoneCallback); 131 | 132 | /** Gets the geopolitical view setting of the layer. */ 133 | public getView(): string; 134 | 135 | /** Sets the geopolitical view setting of the layer. */ 136 | public setView(view: string): void; 137 | 138 | /** Gets the language code used by the layer. */ 139 | public getLanguage(): string; 140 | 141 | /** 142 | * Sets the language code to append to the request. 143 | * @param language The language code to set. 144 | */ 145 | public setLanguage(language: string): void; 146 | 147 | /** Gets the tileset ID of the layer. */ 148 | public getTilesetId(): string; 149 | 150 | /** 151 | * Sets the tileset ID of the layer. 152 | * @param tilesetId The tileset to change to. 153 | */ 154 | public setTilesetId(tilesetId: string): void; 155 | 156 | /** 157 | * Gets the time stamp value setting. 158 | */ 159 | public getTimeStamp(): string | Date; 160 | 161 | /** 162 | * Sets the time stamp option of the request. 163 | * @param timeStamp Time stamp value. 164 | */ 165 | public setTimeStamp(timeStamp: string | Date): void; 166 | 167 | /** 168 | * Gets the traffic flow thickness setting. 169 | */ 170 | public getTrafficFlowThickness(): number; 171 | 172 | /** 173 | * sets the traffic flow thickness setting. 174 | */ 175 | public setTrafficFlowThickness(thickness: number): void; 176 | } 177 | } 178 | 179 | export module tilelayer { 180 | /** 181 | * Static function to create a tile layer that connects to the Azure Maps Render V2 service. 182 | * @param options Azure Maps Tile layer options. 183 | */ 184 | export function azureMaps(options: AzureMapsTileLayerOptions); 185 | } 186 | } 187 | 188 | export = L; -------------------------------------------------------------------------------- /src/AzureMaps.ts: -------------------------------------------------------------------------------- 1 | import L from 'leaflet'; 2 | import { AzureMapsTileLayerOptions } from './AzureMapsTileLayerOptions'; 3 | import { AuthenticationManager } from './internal/AuthenticationManager'; 4 | import { Constants } from './internal/Constants'; 5 | 6 | const _renderV2TileUrl = `https://{azMapsDomain}/map/tile?api-version=${Constants.RENDERV2_VERSION}&tilesetId={tilesetId}&zoom={z}&x={x}&y={y}&tileSize={tileSize}&language={language}&view={view}`; 7 | const _trafficFlowTileUrl = 'https://{azMapsDomain}/traffic/flow/tile/png?api-version=1.0&style={style}&zoom={z}&x={x}&y={y}'; 8 | const _trafficIncidentTileUrl = 'https://{azMapsDomain}/traffic/incident/tile/png?api-version=1.0&style={style}&zoom={z}&x={x}&y={y}'; 9 | 10 | /** 11 | * A tile layer that connects to the Azure Maps Render V2 service. 12 | */ 13 | export class AzureMaps extends L.TileLayer { 14 | 15 | /************************ 16 | * Private properties 17 | ***********************/ 18 | 19 | private _authManager: AuthenticationManager; 20 | private _baseUrl: string = _renderV2TileUrl; 21 | 22 | /************************ 23 | * Constructor 24 | ***********************/ 25 | 26 | /** 27 | * A tile layer that connects to the Azure Maps Render V2 service. 28 | * @param options Azure Maps Tile layer options. 29 | */ 30 | constructor(options: AzureMapsTileLayerOptions) { 31 | super(_renderV2TileUrl, Object.assign({ 32 | tileSize: 256, 33 | language: 'en-US', 34 | view: 'Auto', 35 | tilesetId: 'microsoft.base.road', 36 | trafficFlowThickness: 5 37 | }, options)); 38 | 39 | const self = this; 40 | const opt = self.options; 41 | const au = opt.authOptions || {}; 42 | 43 | if (!au.azMapsDomain) { 44 | au.azMapsDomain = Constants.SHORT_DOMAIN; 45 | } 46 | 47 | const am = AuthenticationManager.getInstance(au); 48 | self._authManager = am; 49 | 50 | if (!am.isInitialized()) { 51 | am.initialize().then(() => { 52 | self.setTilesetId(opt.tilesetId); 53 | }); 54 | } else { 55 | self.setTilesetId(opt.tilesetId); 56 | } 57 | } 58 | 59 | /************************ 60 | * Public functions 61 | ***********************/ 62 | 63 | /** 64 | * Gets the attributions for the tile layer. 65 | */ 66 | public getAttribution(): string { 67 | const self = this; 68 | const ts = (self.options).tilesetId; 69 | var partner: string; 70 | var attr = self.options.attribution; 71 | 72 | if (ts) { 73 | if (ts.startsWith('microsoft.base.') || ts.startsWith('microsoft.traffic.')) { 74 | partner = 'TomTom'; 75 | } else if (ts.startsWith('microsoft.weather.')) { 76 | partner = 'AccuWeather'; 77 | } else if (ts === 'microsoft.imagery') { 78 | partner = 'Airbus'; 79 | } 80 | 81 | if (partner) { 82 | return `© ${new Date().getFullYear()} ${partner}, Microsoft`; 83 | } else if (!attr) { 84 | return `© ${new Date().getFullYear()} Microsoft`; 85 | } 86 | } 87 | 88 | return attr; 89 | } 90 | 91 | /** 92 | * Gets the tile URL for the specified map tile coordinates. 93 | * @param coords Map tile coordinates. 94 | */ 95 | public getTileUrl(coords: L.Coords): string { 96 | const self = this; 97 | return self._getFormattedUrl() 98 | .replace('{x}', coords.x.toString()) 99 | .replace('{y}', coords.y.toString()) 100 | .replace('{z}', self._getZoomForUrl().toString()); 101 | } 102 | 103 | /** 104 | * Creates a map tile for the layer. 105 | * @param coords Map tile coordinates. 106 | * @param done Callback function for when the map tile has finished loading. 107 | */ 108 | public createTile(coords: L.Coords, done: L.DoneCallback) { 109 | const self = this; 110 | 111 | const img = document.createElement("img"); 112 | img.setAttribute("role", "presentation"); 113 | 114 | if ((self.options).tilesetId) { 115 | self._authManager.getRequest(self.getTileUrl(coords)).then(r => { 116 | r.blob().then(blobResponse => { 117 | const reader = new FileReader(); 118 | reader.onload = () => { 119 | img.src = reader.result; 120 | img.style.visibility = 'visible'; 121 | done(); 122 | }; 123 | reader.readAsDataURL(blobResponse); 124 | }); 125 | }); 126 | } 127 | 128 | return img; 129 | } 130 | 131 | /** Gets the geopolitical view setting of the layer. */ 132 | public getView(): string { 133 | return (this.options).view; 134 | } 135 | 136 | /** Sets the geopolitical view setting of the layer. */ 137 | public setView(view: string): void { 138 | (this.options).view = view; 139 | } 140 | 141 | /** Gets the language code used by the layer. */ 142 | public getLanguage(): string { 143 | return (this.options).language; 144 | } 145 | 146 | /** 147 | * Sets the language code to append to the request. 148 | * @param language The language code to set. 149 | */ 150 | public setLanguage(language: string): void { 151 | //@ts-ignore 152 | this.options.language = language; 153 | this._refresh(); 154 | } 155 | 156 | /** Gets the tileset ID of the layer. */ 157 | public getTilesetId(): string { 158 | return (this.options).tilesetId; 159 | } 160 | 161 | /** 162 | * Sets the tileset ID of the layer. 163 | * @param tilesetId The tileset to change to. 164 | */ 165 | public setTilesetId(tilesetId: string): void { 166 | const self = this; 167 | (self.options).tilesetId = tilesetId; 168 | 169 | self._baseUrl = _renderV2TileUrl; 170 | 171 | if(tilesetId){ 172 | if(tilesetId.startsWith('microsoft.traffic.flow')){ 173 | self._baseUrl = _trafficFlowTileUrl; 174 | } else if(tilesetId.startsWith('microsoft.traffic.incident')) { 175 | self._baseUrl = _trafficIncidentTileUrl; 176 | } 177 | } 178 | 179 | self._refresh(); 180 | } 181 | 182 | /** 183 | * Gets the time stamp value setting. 184 | */ 185 | public getTimeStamp(): string | Date { 186 | return (this.options).timeStamp; 187 | } 188 | 189 | /** 190 | * Sets the time stamp option of the request. 191 | * @param timeStamp Time stamp value. 192 | */ 193 | public setTimeStamp(timeStamp: string | Date): void { 194 | (this.options).timeStamp = timeStamp; 195 | this._refresh(); 196 | } 197 | 198 | /** 199 | * Gets the traffic flow thickness setting. 200 | */ 201 | public getTrafficFlowThickness(): number { 202 | return (this.options).trafficFlowThickness; 203 | } 204 | 205 | /** 206 | * sets the traffic flow thickness setting. 207 | */ 208 | public setTrafficFlowThickness(thickness: number): void { 209 | (this.options).trafficFlowThickness = thickness; 210 | this._refresh(); 211 | } 212 | 213 | /************************ 214 | * Private functions 215 | ***********************/ 216 | 217 | private _refresh(): void { 218 | super.setUrl(this._getFormattedUrl()); 219 | } 220 | 221 | private _getFormattedUrl(): string { 222 | const self = this; 223 | const opt = self.options; 224 | 225 | var url = self._baseUrl 226 | .replace('{tileSize}', self._getTileSize().toString()) 227 | .replace('{language}', opt.language) 228 | .replace('{view}', opt.view) 229 | .replace('{tilesetId}', opt.tilesetId); 230 | 231 | if(opt.tilesetId && opt.tilesetId.startsWith('microsoft.traffic')){ 232 | url = url.replace('{style}', self._getTrafficStyle()); 233 | 234 | if(opt.tilesetId.indexOf('flow') > 0) { 235 | url += '&thickness=' + opt.trafficFlowThickness; 236 | } 237 | } 238 | 239 | if (opt.timeStamp) { 240 | var ts = opt.timeStamp; 241 | 242 | if (opt.timeStamp instanceof Date) { 243 | //Create an ISO 8601 timestamp string. 244 | //JavaScripts format for ISO string includes decimal seconds and the letter "Z" at the end that is not supported. Use slice to remove this. 245 | ts = opt.timeStamp.toISOString().slice(0, 19); 246 | } 247 | 248 | url = url.replace('{timeStamp}', ts); 249 | } 250 | 251 | return url; 252 | } 253 | 254 | private _getTileSize(): string { 255 | const ts = this.options.tileSize; 256 | return ((typeof ts === 'number') ? ts : ts.x) + ''; 257 | } 258 | 259 | private _getTrafficStyle(): string { 260 | const ts = (this.options).tilesetId; 261 | 262 | if(ts && ts.indexOf('microsoft.traffic.')> -1){ 263 | return ts.replace('microsoft.traffic.incident.', '').replace('microsoft.traffic.flow.', ''); 264 | } 265 | 266 | return null; 267 | } 268 | } -------------------------------------------------------------------------------- /examples/Azure Maps Leaflet options.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Azure Maps Leaflet options - Azure Maps Web SDK Samples 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 66 | 67 | 68 |
69 | 70 |
71 | 72 | 73 | 74 | 93 | 94 | 95 | 96 | 156 | 157 | 158 | 159 | 165 | 166 |
Tileset ID: 75 | 92 |
Language: 97 | 155 |
Traffic flow thickness: 160 |
161 | 162 | 5 163 |
164 |
167 |

168 | Options not demonstrated: 169 | 170 |
    171 |
  • authOptions
  • 172 |
  • timeStamp
  • 173 |
  • view
  • 174 |
175 |
176 | 177 |
178 | 179 |

Azure Maps Leaflet options

180 |
181 | This sample shows how the different options of Azure Maps tile layer can be used in the Leaflet JS map control. 182 | This samples uses the open source Azure Maps Leaflet plugin. 183 |
184 | 185 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | --- 2 | page_type: sample 3 | description: A leafletjs plugin that makes it easy to overlay tile layers from the Azure Maps tile services. 4 | languages: 5 | - javascript 6 | - typescript 7 | products: 8 | - azure 9 | - azure-maps 10 | --- 11 | 12 | # Azure Maps Leaflet plugin 13 | 14 | A [leafletjs](https://leafletjs.com/) plugin that makes it easy to overlay tile layers from the [Azure Maps tile services](https://docs.microsoft.com/rest/api/maps/renderv2/getmaptilepreview). 15 | 16 | **Features:** 17 | 18 | - Authenticate using an Azure Maps subscription key or Azure Active Directory. 19 | - Works with with Azure Public and Government clouds. 20 | - [Supports over 30 languages](https://docs.microsoft.com/azure/azure-maps/supported-languages) 21 | - Supported layers: 22 | - **Road maps** 23 | - Main (`microsoft.base.road`) - All layers with our main style. 24 | - Labels (`microsoft.base.labels.road`) - Label data in our main style. 25 | - Hybrid (`microsoft.base.hybrid.road`) - Road, boundary and label data in our main style. 26 | - Dark grayscale (`microsoft.base.darkgrey`) - All layers with our dark grayscale style. 27 | - **Imagery** (`microsoft.imagery`) 28 | - **Traffic Flow** 29 | - absolute (`microsoft.traffic.flow.absolute`) 30 | - reduced-sensitivity (`microsoft.traffic.flow.reduced-sensitivity`) 31 | - relative (`microsoft.traffic.flow.relative`) 32 | - relative-delay (microsoft.traffic.flow.relative-delay`) 33 | - **Traffic Incident** 34 | - night (`microsoft.traffic.incident.night`) 35 | - s1 (`microsoft.traffic.incident.s1`) 36 | - s2 (`microsoft.traffic.incident.s2`) 37 | - s3 (`microsoft.traffic.incident.s3`) 38 | - **Weather** 39 | - Infrared (`microsoft.weather.infrared.main`) - Latest Infrared Satellite images shows clouds by their temperature. 40 | - Radar (`microsoft.weather.radar.main`) - Latest weather radar images including areas of rain, snow, ice and mixed conditions. 41 | - Use time stamps with weather layers to get recent and forecast data. 42 | - Adjust the line thickness in traffic flow layers. 43 | 44 | Currently supports raster (i.e PNG) tiles, support for vector tiles is planned. 45 | 46 | **Samples** 47 | 48 | [Render Azure Maps in Leaflet](https://azuremapscodesamples.azurewebsites.net/index.html?sample=Render%20Azure%20Maps%20in%20Leaflet) 49 |
[](https://azuremapscodesamples.azurewebsites.net/index.html?sample=Render%20Azure%20Maps%20in%20Leaflet) 50 | 51 | [Show Azure Maps in Leaflet layer control](https://azuremapscodesamples.azurewebsites.net/index.html?sample=Show%20Azure%20Maps%20in%20Leaflet%20layer%20control) 52 |
[](https://azuremapscodesamples.azurewebsites.net/index.html?sample=Show%20Azure%20Maps%20in%20Leaflet%20layer%20control) 53 | 54 | [Azure Maps Leaflet options](https://azuremapscodesamples.azurewebsites.net/index.html?sample=Azure%20Maps%20Leaflet%20options) 55 |
[](https://azuremapscodesamples.azurewebsites.net/index.html?sample=Azure%20Maps%20Leaflet%20options) 56 | 57 | ## Getting started 58 | 59 | Download the project and copy the `azure-maps-leaflet` JavaScript file from the `dist` folder into your project. 60 | 61 | **Usage** 62 | 63 | ```html 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 121 | 122 | 123 |
124 | 125 | 126 | ``` 127 | 128 | If using Azure Government cloud, set the Azure Maps domain to `'atlas.azure.us'` when creating the layer. 129 | 130 | ```javascript 131 | //When using the static function to create a layer. 132 | L.tileLayer.azureMaps({ 133 | authOptions: { 134 | azMapsDomain: 'atlas.azure.us' 135 | //Your other authentication options. 136 | } 137 | }).addTo(map); 138 | 139 | //When creating an instance of the Azure Maps tile layer class. 140 | var layer = new L.TileLayer.AzureMaps({ 141 | authOptions: { 142 | azMapsDomain: 'atlas.azure.us' 143 | //Your other authentication options. 144 | } 145 | }}); 146 | ``` 147 | 148 | More details on authentication options for Azure Maps is [documented here](https://docs.microsoft.com/azure/azure-maps/how-to-manage-authentication). 149 | 150 | ## API Reference 151 | 152 | ### Static AzureMaps function 153 | 154 | | Name | Return type | Description | 155 | |------|------|-------------| 156 | | `L.tileLayer.AzureMaps(options?: AzureMapsTileLayerOptions)` | `AzureMaps` | Static function to create a tile layer that connects to the Azure Maps Render V2 service. | 157 | 158 | ### AzureMaps class 159 | 160 | **Extends:** `L.TileLayer` 161 | 162 | **Namespace:** `L.TileLayer` 163 | 164 | A tile layer that connects to the Azure Maps Render V2 service. 165 | 166 | **Contstructor** 167 | 168 | > `AzureMaps(options?: AzureMapsTileLayerOptions)` 169 | 170 | **Methods** 171 | 172 | | Name | Return type | Description | 173 | |------|------|-------------| 174 | | `createTile(coords: leaflet.Coords, done: leaflet.DoneCallback)` | | Creates a map tile for the layer. | 175 | | `getAttribution()` | `string` | Gets the attributions for the tile layer. | 176 | | `getLanguage()` | `string` |Gets the language code used by the layer. | 177 | | `getTilesetId()` | `string` | Gets the tileset ID of the layer. | 178 | | `getTileUrl(coords: leaflet.Coords)` | `string` | Gets the tile URL for the specified map tile coordinates. | 179 | | `getTimeStamp()` | `string` \| `Date` | Gets the time stamp value setting. | 180 | | `getTrafficFlowThickness()` | `number` | Gets the traffic flow thickness setting. | 181 | | `getView()` | `string` | Gets the geopolitical view setting of the layer. | 182 | | `setLanguage(language: string)` | | Sets the language code to append to the request. | 183 | | `setTilesetId(tilesetId: string)` | | Sets the tileset ID of the layer. | 184 | | `setTimeStamp(timeStamp: string \| Date)` | | Sets the time stamp option of the request. | 185 | | `setTrafficFlowThickness(thickness: number)` | | Sets the traffic flow thickness setting. | 186 | | `setView(view: string)` | | Sets the geopolitical view setting of the layer. | 187 | 188 | ### AuthenticationOptions interface 189 | 190 | Authentication options for connecting to the Azure Maps tile services. 191 | 192 | **Properties** 193 | 194 | | Name | Type | Description | 195 | |------|------|-------------| 196 | | `aadAppId` | `string` | The Azure AD registered app ID. This is the app ID of an app registered in your Azure AD tenant. Must be specified for AAD authentication type. | 197 | | `aadInstance` | `string` | The AAD instance to use for logging in. Can be optionally specified when using the AAD authentication type. By default the `https://login.microsoftonline.com/` instance will be used. | 198 | | `aadTenant` | `string` | The AAD tenant that owns the registered app specified by `aadAppId`. Must be specified for AAD authentication type. | 199 | | `authContext` | `AuthenticationContext` | Optionally provide an existing `AuthenticationContext` from the ADAL.js library. This authentication context will be used to acquire the AAD token. Only used with the AAD authentication type. This auth context must be configured to use the same AAD app ID as `this.aadAppId`. If this is not provided all map instances will share their own private auth context. | 200 | | `authType` | `'subscriptionKey'` \| `'aad'` \| `'anonymous'` | The authentication mechanism to be used. | 201 | | `azMapsDomain` | `string` | A URL string pointing to the domain of the Azure Maps service, default is `'atlas.microsoft.com'`. Set to `'atlas.azure.us'` if using the US Azure Government cloud. | 202 | | `clientId` | `string` | The Azure Maps client ID, This is an unique identifier used to identify the maps account. Preferred to always be specified, but must be specified for AAD and anonymous authentication types. | 203 | | `getToken` | `(resolve: (value?: string) => void, reject: (reason?: any) => void) => void` | A callback to use with the anonymous authentication mechanism. This callback will be responsible for resolving to a authentication token. E.g. fetching a CORS protected token from an endpoint. | 204 | | `subscriptionKey` | `string` | Subscription key from your Azure Maps account. Must be specified for subscription key authentication type. | 205 | 206 | ### AzureMapsTileLayerOptions interface 207 | 208 | **Extends:** `L.TileLayerOptions` 209 | 210 | Options for an Azure Maps tile layer. 211 | 212 | **Properties** 213 | 214 | | Name | Type | Description | 215 | |------|------|-------------| 216 | | `authOptions` | `AuthenticationOptions` | **Required.** Authentication options for connecting to Azure Maps. | 217 | | `language` | `string` | Language code. [Supported languages](https://docs.microsoft.com/azure/azure-maps/supported-languages) Default: `'en-US'` | 218 | | `tilesetId` | `string` | The tile set ID layer to load from the Azure Maps Render V2 service. Supported values:

`'microsoft.base.road',`
`'microsoft.base.darkgrey'`
`'microsoft.imagery'`
`'microsoft.weather.infrared.main'`
`'microsoft.weather.radar.main'`
`'microsoft.base.hybrid.road'`
`'microsoft.base.labels.road'`
`'microsoft.traffic.incident.night'`
`'microsoft.traffic.incident.s1'`
`'microsoft.traffic.incident.s2'`
`'microsoft.traffic.incident.s3'`
`'microsoft.traffic.flow.absolute'`
`'microsoft.traffic.flow.reduced-sensitivity'`
`'microsoft.traffic.flow.relative'`
`'microsoft.traffic.flow.relative-delay'` Custom tileset ID's that return raster tiles that are 256x256 pixels in size can also be specified as a string. Default `'microsoft.base.road'` | 219 | | `timeStamp` | `string` \| `Date` | The desired date and time of the requested tile. This parameter must be specified in the standard date-time format (e.g. 2019-11-14T16:03:00-08:00), as defined by ISO 8601. This parameter is only supported when tilesetId parameter is set to `microsoft.weather.infrared.main` or `microsoft.weather.radar.main`. | 220 | | `trafficFlowThickness` | `number` | The thickness of lines when using the traffic flow tilesets. Default: `5` | 221 | | `view` | `string` | Geopolitical view of the map. [Supported views](https://docs.microsoft.com/en-us/azure/azure-maps/supported-languages#sdks) Default: `'Auto'` | 222 | 223 | ### Alternative Option for Leaflet 224 | 225 | This Leaflet plugin makes it easy to overlay tile layers from Azure Maps using any of the supported authentication methods available in Azure Maps; subscription key or Azure Active Directory (recommended). If you are only using a subscription key and don't plan to use Azure Active Directory, the following code can be used instead to easily overlay Azure Maps tile layers on a leaflet map without having to use this plugin. 226 | 227 | ```html 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 241 | 242 | 245 | 246 | 281 | 282 | 283 |
284 | 285 | 286 | ``` 287 | 288 | ## Related Projects 289 | 290 | * [Azure Maps Web SDK Open modules](https://github.com/microsoft/Maps/blob/master/AzureMaps.md#open-web-sdk-modules) - A collection of open source modules that extend the Azure Maps Web SDK. 291 | * [Azure Maps Web SDK Samples](https://github.com/Azure-Samples/AzureMapsCodeSamples) 292 | * [Azure Maps Gov Cloud Web SDK Samples](https://github.com/Azure-Samples/AzureMapsGovCloudCodeSamples) 293 | * [Azure Maps & Azure Active Directory Samples](https://github.com/Azure-Samples/Azure-Maps-AzureAD-Samples) 294 | * [List of open-source Azure Maps projects](https://github.com/microsoft/Maps/blob/master/AzureMaps.md) 295 | 296 | ## Additional Resources 297 | 298 | * [Azure Maps (main site)](https://azure.com/maps) 299 | * [Azure Maps Documentation](https://docs.microsoft.com/azure/azure-maps/index) 300 | * [Azure Maps Blog](https://azure.microsoft.com/blog/topics/azure-maps/) 301 | * [Microsoft Q&A](https://docs.microsoft.com/answers/topics/azure-maps.html) 302 | * [Azure Maps feedback](https://feedback.azure.com/forums/909172-azure-maps) 303 | 304 | ## Contributing 305 | 306 | We welcome contributions. Feel free to submit code samples, file issues and pull requests on the repo and we'll address them as we can. 307 | Learn more about how you can help on our [Contribution Rules & Guidelines](https://github.com/Azure-Samples/azure-maps-leaflet/blob/main/CONTRIBUTING.md). 308 | 309 | You can reach out to us anytime with questions and suggestions using our communities below: 310 | * [Microsoft Q&A](https://docs.microsoft.com/answers/topics/azure-maps.html) 311 | * [Azure Maps feedback](https://feedback.azure.com/forums/909172-azure-maps) 312 | 313 | This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). 314 | For more information, see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or 315 | contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. 316 | 317 | ## License 318 | 319 | MIT 320 | 321 | See [License](https://github.com/Azure-Samples/azure-maps-leaflet/blob/main/LICENSE.md) for full license text. -------------------------------------------------------------------------------- /src/internal/AuthenticationManager.ts: -------------------------------------------------------------------------------- 1 | import { AuthenticationOptions } from './../AuthenticationOptions'; 2 | import AuthenticationContext from 'adal-angular'; 3 | import { RequestParameters } from './RequestParameters'; 4 | import { AuthenticationType } from './AuthenticationType'; 5 | import { Constants } from './Constants'; 6 | import { Helpers } from './Helpers'; 7 | import { Timers } from './Timers'; 8 | 9 | /** 10 | * A manager for the map control's authentication. 11 | * Exposed through the authentication property of the atlas.Map class. 12 | * Cannot be instantiated by the user. 13 | */ 14 | export class AuthenticationManager { 15 | private readonly options: AuthenticationOptions; 16 | private tokenTimeOutHandle: number; // Anon auth token refresh timeout 17 | private initPromise: Promise; 18 | private _initialized = false; 19 | 20 | private static fallbackStorage: Record = {}; 21 | 22 | private static instance: AuthenticationManager; 23 | public static readonly sessionId = Helpers.uuid(); 24 | 25 | public static getInstance(authOptions: AuthenticationOptions): AuthenticationManager { 26 | if (authOptions && authOptions.authType) { 27 | const domain = authOptions.azMapsDomain; 28 | //Remove any domain that might be in the domain. 29 | if (domain && /^\w+:\/\//.test(domain)) { 30 | // If the provided url includes a protocol don't change it. 31 | authOptions.azMapsDomain = domain.replace(/^\w+:\/\//, ''); 32 | } 33 | 34 | if (AuthenticationManager.instance && AuthenticationManager.instance.compareOptions(authOptions)) { 35 | return AuthenticationManager.instance; 36 | } 37 | 38 | const au = new AuthenticationManager(authOptions); 39 | 40 | //Cache the instance for faster processing of additional layers and allow reuse of the same instance. 41 | if (!AuthenticationManager.instance){ 42 | AuthenticationManager.instance = au; 43 | } 44 | 45 | return au; 46 | } 47 | 48 | if (AuthenticationManager.instance) { 49 | return AuthenticationManager.instance; 50 | } 51 | 52 | throw 'Azure Maps credentials not specified.'; 53 | } 54 | 55 | public compareOptions(authOptions: AuthenticationOptions): boolean { 56 | const opt = this.options; 57 | 58 | return authOptions.azMapsDomain === opt.azMapsDomain && 59 | authOptions.aadAppId === opt.aadAppId && 60 | authOptions.aadInstance === opt.aadInstance && 61 | authOptions.aadTenant === opt.aadTenant && 62 | authOptions.authType === opt.authType && 63 | authOptions.clientId === opt.clientId && 64 | authOptions.getToken === opt.getToken && 65 | authOptions.subscriptionKey === opt.subscriptionKey; 66 | } 67 | 68 | /** 69 | * A static auth context shared between maps that don't have one specified to them. 70 | */ 71 | private static defaultAuthContext: AuthenticationContext; 72 | 73 | /** 74 | * @internal 75 | */ 76 | constructor(authOptions: AuthenticationOptions) { 77 | this.options = authOptions; 78 | } 79 | 80 | public isInitialized(): boolean { 81 | return this._initialized; 82 | } 83 | 84 | /** 85 | * Initializes the authentication mechanism specified in AuthenticationOptions. 86 | * If this method has been called before the original initialize promise is returned. 87 | */ 88 | public initialize(): Promise { 89 | const self = this; 90 | const opt = self.options; 91 | 92 | if (!self.initPromise) { 93 | // If an init promise hasn't been created this is the first initialize call. 94 | self.initPromise = new Promise((resolve, reject) => { 95 | if (opt.authType === AuthenticationType.subscriptionKey) { 96 | self._initialized = true; 97 | resolve(); 98 | } else if (opt.authType === AuthenticationType.aad) { 99 | // If a specific auth context was provided to the map use that. 100 | // If not use/create a default auth context shared between maps. 101 | opt.authContext = opt.authContext || 102 | AuthenticationManager.getDefaultAuthContext(opt); 103 | 104 | // If this window is a callback then it is the hidden iframe created by ADAL. 105 | // The map doesn't need to finish constructing, so we can dispose it. 106 | opt.authContext.handleWindowCallback(); 107 | if (opt.authContext.getLoginError()) { 108 | reject(new Error("Error logging in the AAD users: " + 109 | opt.authContext.getLoginError())); 110 | return; 111 | } 112 | 113 | if (opt.authContext.isCallback(window.location.hash)) { 114 | return; 115 | } 116 | 117 | // Login and acquire a token. 118 | // Fire it async so that users can add any listeners for token acquire events first. 119 | Timers.setTimeout(() => self._loginAndAcquire(resolve, reject), 0); 120 | } else if (opt.authType === AuthenticationType.anonymous || opt.authType === AuthenticationType.sas) { 121 | // Anonymous authentication, just call the users provided callback. 122 | self._initialized = true; 123 | resolve(self._triggerTokenFetch()); 124 | } else { 125 | reject(new Error("An invalid authentication type was specified.")); 126 | } 127 | }); 128 | } 129 | 130 | return this.initPromise; 131 | } 132 | 133 | /** 134 | * Gets the default auth context to be shared between maps without one specified to them. 135 | */ 136 | private static getDefaultAuthContext(options: AuthenticationOptions): AuthenticationContext { 137 | const self = this; 138 | if (!options.aadAppId) { 139 | throw new Error("No AAD app ID was specified."); 140 | } 141 | 142 | if (!options.aadTenant) { 143 | throw new Error("No AAD tenant was specified."); 144 | } 145 | 146 | // Create a new auth context if one doesn't already exist. 147 | if (!self.defaultAuthContext) { 148 | self.defaultAuthContext = new AuthenticationContext({ 149 | instance: options.aadInstance || 'https://login.windows-ppe.net/', 150 | tenant: options.aadTenant, 151 | clientId: options.aadAppId, 152 | cacheLocation: Constants.preferredCacheLocation as ("localStorage" | "sessionStorage") 153 | }); 154 | } 155 | 156 | // Return either a reused auth context or the one created just above. 157 | return self.defaultAuthContext; 158 | } 159 | 160 | /** 161 | * The login callback function, called after user interactive login session is completed 162 | * @param resolve the resolve callback for the promise created from the initialize call 163 | */ 164 | private _loginAndAcquire(resolve: () => void, reject: (reason?: any) => void) { 165 | const self = this; 166 | const opt = self.options; 167 | 168 | const acquireAndResolve = () => { 169 | // Check that we can acquire a token and then resolve the promise. 170 | // Reject if an error occurs when acquiring the token. 171 | opt.authContext.acquireToken(Constants.DEFAULT_DOMAIN, (error: string) => { 172 | if (error) { 173 | reject(new Error(error)); 174 | } else { 175 | self._initialized = true; 176 | resolve(); 177 | } 178 | }); 179 | }; 180 | 181 | const cachedToken = opt.authContext.getCachedToken(opt.aadAppId); 182 | const cachedUser = opt.authContext.getCachedUser(); 183 | 184 | if (cachedToken && cachedUser) { 185 | // If a cached token and user are available we should be able to 186 | // acquire the access token and then resolve the promise. 187 | acquireAndResolve(); 188 | } else { 189 | // If a login isn't already in progress start a new one. 190 | if (!opt.authContext.loginInProgress()) { 191 | opt.authContext.login(); 192 | } 193 | 194 | // Poll for when the login done and then use the cached token. 195 | const loginPoll = setInterval(() => { 196 | if (!opt.authContext.loginInProgress()) { 197 | // Stop polling for login done. 198 | clearInterval(loginPoll); 199 | if (opt.authContext.getCachedToken(opt.aadAppId)) { 200 | // If a token for the specified AAD app id is available we are ready 201 | // to acquire the access token and resolve the init promise. 202 | acquireAndResolve(); 203 | } else { 204 | // If done logging in but no token for the specified AAD app ID is cached 205 | // then there is a mistake in the auth context config. 206 | reject(new Error(opt.authContext.getLoginError() || 207 | "The AAD authentication context is not logged-in for the specified app ID: " + 208 | opt.aadAppId)); 209 | } 210 | } 211 | }, 25); 212 | } 213 | } 214 | 215 | /** 216 | * Returns the current authentication type in use. 217 | */ 218 | public getAuthType(): AuthenticationType { 219 | return this.options.authType; 220 | } 221 | 222 | /** 223 | * Returns the current client ID in use. 224 | */ 225 | public getClientId(): string { 226 | return this.options.clientId; 227 | } 228 | 229 | /** 230 | * Returns the access token with an audience URI of https://atlas.microsoft.com. 231 | */ 232 | public getToken(): string { 233 | const self = this; 234 | const opt = self.options; 235 | 236 | if (opt.authType === AuthenticationType.aad) { 237 | let token = opt.authContext.getCachedToken(Constants.DEFAULT_DOMAIN); 238 | if (!token) { 239 | if (!opt.authContext.getCachedUser()) { 240 | // Login if a user isn't cached. This shouldn't typically happen. 241 | opt.authContext.login(); 242 | } 243 | 244 | opt.authContext.acquireToken(Constants.DEFAULT_DOMAIN, (error, renewedToken) => { 245 | if (!error) { 246 | token = renewedToken; 247 | } 248 | }); 249 | } 250 | 251 | return token; 252 | } else if (opt.authType === AuthenticationType.anonymous || opt.authType === AuthenticationType.sas) { 253 | const token = self._getItem(Constants.storage.accessTokenKey); 254 | if (!token) { 255 | // Cached Token not present, invoke the user provided callback function to fetch function 256 | self._triggerTokenFetch(); 257 | } else { 258 | // check for cached token validity 259 | const expiresIn = self._getTokenExpiry(token); 260 | if (expiresIn < 300 && expiresIn > 0) { 261 | // We are within a window for the token expiry, 262 | // trigger a new token fetch, but still return the current token 263 | self._triggerTokenFetch(); 264 | } else if (expiresIn <= 0) { 265 | // Token renew failed and don't have a token. 266 | // Try fetching a new token. 267 | self._triggerTokenFetch(); 268 | // self._saveItem(Constants.storage.accessTokenKey, ""); 269 | // throw new Error(Constants.errors.tokenExpired); 270 | } else { 271 | //Add a timeout to renew the cached token. 272 | // Try to get the timeout first as this will guarantee the token is correctly formatted. 273 | const timeout = expiresIn - Constants.tokenRefreshClockSkew; 274 | 275 | Timers.clearTimeout(self.tokenTimeOutHandle); // Clear the previous refresh timeout in case it hadn't triggered yet. 276 | //@ts-ignore 277 | self.tokenTimeOutHandle = Timers.setTimeout(self._triggerTokenFetch, timeout); 278 | } 279 | } 280 | 281 | return token; 282 | } else if (opt.authType === AuthenticationType.subscriptionKey) { 283 | return opt.subscriptionKey; 284 | } 285 | } 286 | 287 | /** 288 | * Triggers the user provided function to fetch the token and stores it. 289 | * @internal 290 | */ 291 | private _triggerTokenFetch = () => { 292 | const self = this; 293 | return new Promise((resolve, reject) => { 294 | self.options.getToken((token) => { 295 | try { 296 | // Try to get the timeout first as this will guarantee the token is correctly formatted. 297 | const timeout = self._getTokenExpiry(token) - Constants.tokenRefreshClockSkew; 298 | 299 | self._storeAccessToken(token); 300 | Timers.clearTimeout(self.tokenTimeOutHandle); // Clear the previous refresh timeout in case it hadn't triggered yet. 301 | //@ts-ignore 302 | self.tokenTimeOutHandle = Timers.setTimeout(self._triggerTokenFetch, timeout); 303 | 304 | resolve(); 305 | } catch { 306 | reject(new Error(`Invalid token returned by getToken function`)); 307 | } 308 | }, (error) => { 309 | reject(error); 310 | }); 311 | }); 312 | } 313 | 314 | /** 315 | * Given a token, calculate the time left for token expiry in ms. 316 | * @param token 317 | * @internal 318 | */ 319 | private _getTokenExpiry(token: string): number { 320 | /* const decodedToken = jwt_decode<{ exp: number }>(token); 321 | const expiresIn = decodedToken.exp; 322 | const now = this._getCurrentTime(); 323 | return expiresIn - now > 0 ? expiresIn - now : -1;*/ 324 | // Decode the JWT token to get the expiration timestamp 325 | const json = atob(token.split(".")[1]); 326 | const decode = JSON.parse(json); 327 | 328 | // Return the milliseconds until the token needs renewed 329 | // Reduce the time until renew by 5 minutes to avoid using an expired token 330 | // The exp property is the timestamp of the expiration in seconds 331 | const renewSkew = 300000; 332 | return (1000 * decode.exp) - Date.now() - renewSkew; 333 | } 334 | 335 | /** 336 | * stores the token 337 | * @param token token fetched from the user's server endpoint 338 | * @internal 339 | */ 340 | private _storeAccessToken(token: string) { 341 | // Store the value 342 | this._saveItem(Constants.storage.accessTokenKey, token); 343 | } 344 | 345 | /** 346 | * Saves the item to storage 347 | * @param key key/identifier 348 | * @param value value to be stored 349 | */ 350 | private _saveItem(key: string, value: any): boolean { 351 | if (this._supportsLocalStorage()) { 352 | localStorage.setItem(key, value); 353 | return true; 354 | } else if (this._supportsSessionStorage()) { 355 | sessionStorage.setItem(key, value); 356 | return true; 357 | } else { 358 | AuthenticationManager.fallbackStorage[key] = value; 359 | return true; 360 | } 361 | 362 | return false; 363 | } 364 | 365 | /** 366 | * Gets an item saved in storage 367 | * @param key Key/Identifier to be used for lookup 368 | */ 369 | private _getItem(key: string): string { 370 | if (this._supportsLocalStorage()) { 371 | return localStorage.getItem(key); 372 | } else if (this._supportsSessionStorage()) { 373 | return sessionStorage.getItem(key); 374 | } else { 375 | return AuthenticationManager.fallbackStorage[key]; 376 | } 377 | 378 | 379 | return null; 380 | } 381 | 382 | /** 383 | * Returns true if browser supports localStorage, false otherwise. 384 | * @ignore 385 | */ 386 | private _supportsLocalStorage(): boolean { 387 | try { 388 | const wls = window.localStorage; 389 | const testStorageKey = Constants.storage.testStorageKey; 390 | if (!wls) { return false; } // Test availability 391 | wls.setItem(testStorageKey, "A"); // Try write 392 | if (wls.getItem(testStorageKey) !== "A") { return false; } // Test read/write 393 | wls.removeItem(testStorageKey); // Try delete 394 | if (wls.getItem(testStorageKey)) { return false; } // Test delete 395 | return true; // Success 396 | } catch (e) { 397 | return false; 398 | } 399 | } 400 | 401 | /** 402 | * Returns true if browser supports sessionStorage, false otherwise. 403 | * @ignore 404 | */ 405 | private _supportsSessionStorage(): boolean { 406 | try { 407 | const wss = window.sessionStorage; 408 | const testStorageKey = Constants.storage.testStorageKey; 409 | if (!wss) { return false; } // Test availability 410 | wss.setItem(testStorageKey, "A"); // Try write 411 | if (wss.getItem(testStorageKey) !== "A") { return false; } // Test read/write 412 | wss.removeItem(testStorageKey); // Try delete 413 | if (wss.getItem(testStorageKey)) { return false; } // Test delete 414 | return true; // Success 415 | } catch (e) { 416 | return false; 417 | } 418 | } 419 | 420 | public signRequest(request: RequestParameters): RequestParameters { 421 | const self = this; 422 | const opt = self.options; 423 | const h = Constants; 424 | 425 | request.url = request.url.replace('{azMapsDomain}', opt.azMapsDomain); 426 | 427 | // Add the headers used for identifying a request is from the map control. 428 | var headers = request.headers || {}; 429 | headers[h.SESSION_ID] = AuthenticationManager.sessionId; 430 | headers[h.MS_AM_REQUEST_ORIGIN] = h.MS_AM_REQUEST_ORIGIN_VALUE; 431 | headers[h.MAP_AGENT] = `MapControl/${h.SDK_VERSION} (${h.TARGET_SDK})`; 432 | 433 | const token = self.getToken(); 434 | switch (opt.authType) { 435 | case AuthenticationType.aad: 436 | case AuthenticationType.anonymous: 437 | headers[h.X_MS_CLIENT_ID] = opt.clientId; 438 | headers[h.AUTHORIZATION] = `${h.AUTHORIZATION_SCHEME} ${token}`; 439 | break; 440 | case AuthenticationType.sas: 441 | headers[h.X_MS_CLIENT_ID] = opt.clientId; 442 | headers[h.AUTHORIZATION] = `${h.AUTHORIZATION_SCHEME_SAS} ${token}`; 443 | break; 444 | case AuthenticationType.subscriptionKey: 445 | if ("url" in request) { 446 | var prefix = '?'; 447 | 448 | if (request.url.indexOf("?") !== -1) { 449 | prefix = '&'; 450 | } 451 | 452 | request.url += `${prefix}subscription-key=${token}`; 453 | } else { 454 | throw new Error("No URL specified in request."); 455 | } 456 | break; 457 | default: 458 | throw new Error("An invalid authentication type was specified"); 459 | } 460 | 461 | request.headers = headers; 462 | 463 | return request; 464 | } 465 | 466 | public getRequest(url: string): Promise { 467 | const request = this.signRequest({ url: url }); 468 | 469 | //Proces the request. 470 | return fetch(request.url, { 471 | method: 'GET', 472 | mode: 'cors', 473 | headers: new Headers(>request.headers) 474 | }); 475 | } 476 | } 477 | -------------------------------------------------------------------------------- /dist/azure-maps-leaflet.min.js: -------------------------------------------------------------------------------- 1 | /* 2 | azure-maps-leaflet Version: 0.0.4 3 | 4 | MIT License - Copyright (c) Microsoft Corporation. 5 | */ 6 | 7 | 8 | !function(e){"use strict";e=e&&Object.prototype.hasOwnProperty.call(e,"default")?e.default:e;var i=function(e,t){return(i=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(e,t){e.__proto__=t}||function(e,t){for(var i in t)t.hasOwnProperty(i)&&(e[i]=t[i])})(e,t)};var t,s,o,n=(function(e){var t=(t=function(e){if(this.REQUEST_TYPE={LOGIN:"LOGIN",RENEW_TOKEN:"RENEW_TOKEN",UNKNOWN:"UNKNOWN"},this.RESPONSE_TYPE={ID_TOKEN_TOKEN:"id_token token",TOKEN:"token"},this.CONSTANTS={ACCESS_TOKEN:"access_token",EXPIRES_IN:"expires_in",ID_TOKEN:"id_token",ERROR_DESCRIPTION:"error_description",SESSION_STATE:"session_state",ERROR:"error",STORAGE:{TOKEN_KEYS:"adal.token.keys",ACCESS_TOKEN_KEY:"adal.access.token.key",EXPIRATION_KEY:"adal.expiration.key",STATE_LOGIN:"adal.state.login",STATE_RENEW:"adal.state.renew",NONCE_IDTOKEN:"adal.nonce.idtoken",SESSION_STATE:"adal.session.state",USERNAME:"adal.username",IDTOKEN:"adal.idtoken",ERROR:"adal.error",ERROR_DESCRIPTION:"adal.error.description",LOGIN_REQUEST:"adal.login.request",LOGIN_ERROR:"adal.login.error",RENEW_STATUS:"adal.token.renew.status",ANGULAR_LOGIN_REQUEST:"adal.angular.login.request"},RESOURCE_DELIMETER:"|",CACHE_DELIMETER:"||",LOADFRAME_TIMEOUT:6e3,TOKEN_RENEW_STATUS_CANCELED:"Canceled",TOKEN_RENEW_STATUS_COMPLETED:"Completed",TOKEN_RENEW_STATUS_IN_PROGRESS:"In Progress",LOGGING_LEVEL:{ERROR:0,WARN:1,INFO:2,VERBOSE:3},LEVEL_STRING_MAP:{0:"ERROR:",1:"WARNING:",2:"INFO:",3:"VERBOSE:"},POPUP_WIDTH:483,POPUP_HEIGHT:600},t.prototype._singletonInstance)return t.prototype._singletonInstance;if((t.prototype._singletonInstance=this).instance="https://login.microsoftonline.com/",this.config={},this.callback=null,this.popUp=!1,this.isAngular=!1,this._user=null,this._activeRenewals={},this._loginInProgress=!1,this._acquireTokenInProgress=!1,this._renewStates=[],this._callBackMappedToRenewStates={},this._callBacksMappedToRenewStates={},this._openedWindows=[],this._requestType=this.REQUEST_TYPE.LOGIN,window._adalInstance=this,e.displayCall&&"function"!=typeof e.displayCall)throw new Error("displayCall is not a function");if(!e.clientId)throw new Error("clientId is required");this.config=this._cloneConfig(e),void 0===this.config.navigateToLoginRequestUrl&&(this.config.navigateToLoginRequestUrl=!0),this.config.popUp&&(this.popUp=!0),this.config.callback&&"function"==typeof this.config.callback&&(this.callback=this.config.callback),this.config.instance&&(this.instance=this.config.instance),this.config.loginResource||(this.config.loginResource=this.config.clientId),this.config.redirectUri||(this.config.redirectUri=window.location.href.split("?")[0].split("#")[0]),this.config.postLogoutRedirectUri||(this.config.postLogoutRedirectUri=window.location.href.split("?")[0].split("#")[0]),this.config.anonymousEndpoints||(this.config.anonymousEndpoints=[]),this.config.isAngular&&(this.isAngular=this.config.isAngular),this.config.loadFrameTimeout&&(this.CONSTANTS.LOADFRAME_TIMEOUT=this.config.loadFrameTimeout)},"undefined"!=typeof window&&(window.Logging={piiLoggingEnabled:!1,level:0,log:function(e){}}),t.prototype.login=function(){if(this._loginInProgress)this.info("Login in progress");else{this._loginInProgress=!0;var e=this._guid();this.config.state=e,this._idTokenNonce=this._guid();var t=this._getItem(this.CONSTANTS.STORAGE.ANGULAR_LOGIN_REQUEST);t&&""!==t?this._saveItem(this.CONSTANTS.STORAGE.ANGULAR_LOGIN_REQUEST,""):t=window.location.href,this.verbose("Expected state: "+e+" startPage:"+t),this._saveItem(this.CONSTANTS.STORAGE.LOGIN_REQUEST,t),this._saveItem(this.CONSTANTS.STORAGE.LOGIN_ERROR,""),this._saveItem(this.CONSTANTS.STORAGE.STATE_LOGIN,e,!0),this._saveItem(this.CONSTANTS.STORAGE.NONCE_IDTOKEN,this._idTokenNonce,!0),this._saveItem(this.CONSTANTS.STORAGE.ERROR,""),this._saveItem(this.CONSTANTS.STORAGE.ERROR_DESCRIPTION,"");var i=this._getNavigateUrl("id_token",null)+"&nonce="+encodeURIComponent(this._idTokenNonce);this.config.displayCall?this.config.displayCall(i):this.popUp?(this._saveItem(this.CONSTANTS.STORAGE.STATE_LOGIN,""),this._renewStates.push(e),this.registerCallback(e,this.config.clientId,this.callback),this._loginPopup(i)):this.promptUser(i)}},t.prototype._openPopup=function(e,t,i,o){try{var n=window.screenLeft?window.screenLeft:window.screenX,r=window.screenTop?window.screenTop:window.screenY,s=window.innerWidth||document.documentElement.clientWidth||document.body.clientWidth,a=window.innerHeight||document.documentElement.clientHeight||document.body.clientHeight,c=s/2-i/2+n,h=a/2-o/2+r,l=window.open(e,t,"width="+i+", height="+o+", top="+h+", left="+c);return l.focus&&l.focus(),l}catch(e){return this.warn("Error opening popup, "+e.message),this._loginInProgress=!1,this._acquireTokenInProgress=!1,null}},t.prototype._handlePopupError=function(e,t,i,o,n){this.warn(o),this._saveItem(this.CONSTANTS.STORAGE.ERROR,i),this._saveItem(this.CONSTANTS.STORAGE.ERROR_DESCRIPTION,o),this._saveItem(this.CONSTANTS.STORAGE.LOGIN_ERROR,n),t&&this._activeRenewals[t]&&(this._activeRenewals[t]=null),this._loginInProgress=!1,this._acquireTokenInProgress=!1,e&&e(o,null,i)},t.prototype._loginPopup=function(e,o,t){var n=this._openPopup(e,"login",this.CONSTANTS.POPUP_WIDTH,this.CONSTANTS.POPUP_HEIGHT),r=t||this.callback;if(null!=n){if(this._openedWindows.push(n),-1!=this.config.redirectUri.indexOf("#"))var s=this.config.redirectUri.split("#")[0];else s=this.config.redirectUri;var a=this,c=window.setInterval(function(){if(!n||n.closed||void 0===n.closed){var e="Popup Window closed",t="Popup Window closed by UI action/ Popup Window handle destroyed due to cross zone navigation in IE/Edge";return a.isAngular&&a._broadcast("adal:popUpClosed",t+a.CONSTANTS.RESOURCE_DELIMETER+e),a._handlePopupError(r,o,e,t,t),void window.clearInterval(c)}try{var i=n.location;if(-1!=encodeURI(i.href).indexOf(encodeURI(s)))return a.isAngular?a._broadcast("adal:popUpHashChanged",i.hash):a.handleWindowCallback(i.hash),window.clearInterval(c),a._loginInProgress=!1,a._acquireTokenInProgress=!1,a.info("Closing popup window"),a._openedWindows=[],void n.close()}catch(e){}},1)}else{var i="Popup Window is null. This can happen if you are using IE";this._handlePopupError(r,o,"Error opening popup",i,i)}},t.prototype._broadcast=function(e,t){function i(e,t){t=t||{bubbles:!1,cancelable:!1,detail:void 0};var i=document.createEvent("CustomEvent");return i.initCustomEvent(e,t.bubbles,t.cancelable,t.detail),i}"function"!=typeof window.CustomEvent&&(i.prototype=window.Event.prototype,window.CustomEvent=i);var o=new CustomEvent(e,{detail:t});window.dispatchEvent(o)},t.prototype.loginInProgress=function(){return this._loginInProgress},t.prototype._hasResource=function(e){var t=this._getItem(this.CONSTANTS.STORAGE.TOKEN_KEYS);return t&&!this._isEmpty(t)&&-1this._now()+o?t:(this._saveItem(this.CONSTANTS.STORAGE.ACCESS_TOKEN_KEY+e,""),this._saveItem(this.CONSTANTS.STORAGE.EXPIRATION_KEY+e,0),null)},t.prototype.getCachedUser=function(){if(this._user)return this._user;var e=this._getItem(this.CONSTANTS.STORAGE.IDTOKEN);return this._user=this._createUser(e),this._user},t.prototype.registerCallback=function(r,s,e){this._activeRenewals[s]=r,this._callBacksMappedToRenewStates[r]||(this._callBacksMappedToRenewStates[r]=[]);var a=this;this._callBacksMappedToRenewStates[r].push(e),this._callBackMappedToRenewStates[r]||(this._callBackMappedToRenewStates[r]=function(e,t,i,o){a._activeRenewals[s]=null;for(var n=0;n>16&255,h=a>>8&255,T+=String.fromCharCode(c,h);break}if(p+1===i-1){c=(a=o<<18|n<<12)>>16&255,T+=String.fromCharCode(c);break}c=(a=o<<18|n<<12|r<<6|s)>>16&255,h=a>>8&255,l=255&a,T+=String.fromCharCode(c,h,l)}return T},t.prototype._decodeJwt=function(e){if(this._isEmpty(e))return null;var t=/^([^\.\s]*)\.([^\.\s]+)\.([^\.\s]*)$/.exec(e);return!t||t.length<4?(this.warn("The returned id_token is not parseable."),null):{header:t[1],JWSPayload:t[2],JWSSig:t[3]}},t.prototype._convertUrlSafeToRegularBase64EncodedString=function(e){return e.replace("-","+").replace("_","/")},t.prototype._serialize=function(e,t,i){var o=[];if(null!==t){o.push("?response_type="+e),o.push("client_id="+encodeURIComponent(t.clientId)),i&&o.push("resource="+encodeURIComponent(i)),o.push("redirect_uri="+encodeURIComponent(t.redirectUri)),o.push("state="+encodeURIComponent(t.state)),t.hasOwnProperty("slice")&&o.push("slice="+encodeURIComponent(t.slice)),t.hasOwnProperty("extraQueryParameter")&&o.push(t.extraQueryParameter);var n=t.correlationId?t.correlationId:this._guid();o.push("client-request-id="+encodeURIComponent(n))}return o.join("&")},t.prototype._deserialize=function(e){function t(e){return decodeURIComponent(e.replace(o," "))}var i,o=/\+/g,n=/([^&=]+)=([^&]*)/g,r={};for(i=n.exec(e);i;)r[t(i[1])]=t(i[2]),i=n.exec(e);return r},t.prototype._decimalToHex=function(e){for(var t=e.toString(16);t.length<2;)t="0"+t;return t},t.prototype._guid=function(){var e=window.crypto||window.msCrypto;if(e&&e.getRandomValues){var t=new Uint8Array(16);return e.getRandomValues(t),t[6]|=64,t[6]&=79,t[8]|=128,t[8]&=191,this._decimalToHex(t[0])+this._decimalToHex(t[1])+this._decimalToHex(t[2])+this._decimalToHex(t[3])+"-"+this._decimalToHex(t[4])+this._decimalToHex(t[5])+"-"+this._decimalToHex(t[6])+this._decimalToHex(t[7])+"-"+this._decimalToHex(t[8])+this._decimalToHex(t[9])+"-"+this._decimalToHex(t[10])+this._decimalToHex(t[11])+this._decimalToHex(t[12])+this._decimalToHex(t[13])+this._decimalToHex(t[14])+this._decimalToHex(t[15])}for(var i="xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx",o="0123456789abcdef",n=0,r="",s=0;s<36;s++)"-"!==i[s]&&"4"!==i[s]&&(n=16*Math.random()|0),"x"===i[s]?r+=o[n]:"y"===i[s]?(n&=3,r+=o[n|=8]):r+=i[s];return r},t.prototype._expiresIn=function(e){return e=e||3599,this._now()+parseInt(e,10)},t.prototype._now=function(){return Math.round((new Date).getTime()/1e3)},t.prototype._addAdalFrame=function(e){if(void 0!==e){this.info("Add adal frame to document:"+e);var t=document.getElementById(e);if(!t){if(document.createElement&&document.documentElement&&(window.opera||-1===window.navigator.userAgent.indexOf("MSIE 5.0"))){var i=document.createElement("iframe");i.setAttribute("id",e),i.setAttribute("aria-hidden","true"),i.style.visibility="hidden",i.style.position="absolute",i.style.width=i.style.height=i.borderWidth="0px",t=document.getElementsByTagName("body")[0].appendChild(i)}else document.body&&document.body.insertAdjacentHTML&&document.body.insertAdjacentHTML("beforeEnd",'');window.frames&&window.frames[e]&&(t=window.frames[e])}return t}},t.prototype._saveItem=function(e,t,i){if(this.config&&this.config.cacheLocation&&"localStorage"===this.config.cacheLocation){if(!this._supportsLocalStorage())return this.info("Local storage is not supported"),!1;if(i){var o=this._getItem(e)||"";localStorage.setItem(e,o+t+this.CONSTANTS.CACHE_DELIMETER)}else localStorage.setItem(e,t);return!0}return this._supportsSessionStorage()?(sessionStorage.setItem(e,t),!0):(this.info("Session storage is not supported"),!1)},t.prototype._getItem=function(e){return this.config&&this.config.cacheLocation&&"localStorage"===this.config.cacheLocation?this._supportsLocalStorage()?localStorage.getItem(e):(this.info("Local storage is not supported"),null):this._supportsSessionStorage()?sessionStorage.getItem(e):(this.info("Session storage is not supported"),null)},t.prototype._supportsLocalStorage=function(){try{return!!window.localStorage&&(window.localStorage.setItem("storageTest","A"),"A"==window.localStorage.getItem("storageTest")&&(window.localStorage.removeItem("storageTest"),!window.localStorage.getItem("storageTest")))}catch(e){return!1}},t.prototype._supportsSessionStorage=function(){try{return!!window.sessionStorage&&(window.sessionStorage.setItem("storageTest","A"),"A"==window.sessionStorage.getItem("storageTest")&&(window.sessionStorage.removeItem("storageTest"),!window.sessionStorage.getItem("storageTest")))}catch(e){return!1}},t.prototype._cloneConfig=function(e){if(null===e||"object"!=typeof e)return e;var t={};for(var i in e)e.hasOwnProperty(i)&&(t[i]=e[i]);return t},t.prototype._addLibMetadata=function(){return"&x-client-SKU=Js&x-client-Ver="+this._libVersion()},t.prototype.log=function(e,t,i,o){if(e<=Logging.level){if(!Logging.piiLoggingEnabled&&o)return;var n=(new Date).toUTCString(),r="";r=this.config.correlationId?n+":"+this.config.correlationId+"-"+this._libVersion()+"-"+this.CONSTANTS.LEVEL_STRING_MAP[e]+" "+t:n+":"+this._libVersion()+"-"+this.CONSTANTS.LEVEL_STRING_MAP[e]+" "+t,i&&(r+="\nstack:\n"+i.stack),Logging.log(r)}},t.prototype.error=function(e,t){this.log(this.CONSTANTS.LOGGING_LEVEL.ERROR,e,t)},t.prototype.warn=function(e){this.log(this.CONSTANTS.LOGGING_LEVEL.WARN,e,null)},t.prototype.info=function(e){this.log(this.CONSTANTS.LOGGING_LEVEL.INFO,e,null)},t.prototype.verbose=function(e){this.log(this.CONSTANTS.LOGGING_LEVEL.VERBOSE,e,null)},t.prototype.errorPii=function(e,t){this.log(this.CONSTANTS.LOGGING_LEVEL.ERROR,e,t,!0)},t.prototype.warnPii=function(e){this.log(this.CONSTANTS.LOGGING_LEVEL.WARN,e,null,!0)},t.prototype.infoPii=function(e){this.log(this.CONSTANTS.LOGGING_LEVEL.INFO,e,null,!0)},t.prototype.verbosePii=function(e){this.log(this.CONSTANTS.LOGGING_LEVEL.VERBOSE,e,null,!0)},t.prototype._libVersion=function(){return"1.0.18"},e.exports&&(e.exports=t,e.exports.inject=function(e){return new t(e)}),t)}(t={exports:{}}),t.exports);n.inject;(o=s=s||{}).subscriptionKey="subscriptionKey",o.aad="aad",o.anonymous="anonymous",o.sas="sas";var c={preferredCacheLocation:"localStorage",storage:{accessTokenKey:"access.token.key",testStorageKey:"testStorage"},events:{tokenAcquired:"tokenacquired"},tokenExpiresIn:3599,tokenRefreshClockSkew:300,errors:{tokenExpired:"Token Expired, Try again"},AUTHORIZATION:"authorization",AUTHORIZATION_SCHEME:"Bearer",AUTHORIZATION_SCHEME_SAS:"jwt-sas",MAP_AGENT:"Map-Agent",MS_AM_REQUEST_ORIGIN:"Ms-Am-Request-Origin",MS_AM_REQUEST_ORIGIN_VALUE:"MapControl",X_MS_CLIENT_ID:"x-ms-client-id",SESSION_ID:"Session-Id",SHORT_DOMAIN:"atlas.microsoft.com",DEFAULT_DOMAIN:"https://atlas.microsoft.com/",SDK_VERSION:"0.0.1",TARGET_SDK:"Leaflet",RENDERV2_VERSION:"2.1"},r=(a.uuid=function(){return([1e7]+-1e3+-4e3+-8e3+-1e11).replace(/[018]/g,function(e){return(e^crypto.getRandomValues(new Uint8Array(1))[0]&15>>e/4).toString(16)})},a);function a(){}var h=(l.clearTimeout=function(e){var t=l._workerTable[e];t&&(t.worker.terminate(),delete l._workerTable[e])},l.setTimeout=function(e,t){var i=Math.round(1e9*Math.random()),o=new Blob(["onmessage = function (event) {\n var delay = event.data.time; // milliseconds\n setTimeout(() => {\n postMessage({id: event.data.id});\n }, delay);\n};"]),n=window.URL.createObjectURL(o),r=new Worker(n);return r.addEventListener("message",l._receivedSetTimeoutMessage),l._workerTable[i]={callback:e,worker:r},r.postMessage({id:i,time:t}),i},l._receivedSetTimeoutMessage=function(e){var t=l._workerTable[e.data.id];t&&(t.callback(),t.worker.terminate(),delete l._workerTable[e.data.id])},l._workerTable={},l);function l(){}var T=(p.getInstance=function(e){if(e&&e.authType){var t=e.azMapsDomain;if(t&&/^\w+:\/\//.test(t)&&(e.azMapsDomain=t.replace(/^\w+:\/\//,"")),p.instance&&p.instance.compareOptions(e))return p.instance;var i=new p(e);return p.instance||(p.instance=i),i}if(p.instance)return p.instance;throw"Azure Maps credentials not specified."},p.prototype.compareOptions=function(e){var t=this.options;return e.azMapsDomain===t.azMapsDomain&&e.aadAppId===t.aadAppId&&e.aadInstance===t.aadInstance&&e.aadTenant===t.aadTenant&&e.authType===t.authType&&e.clientId===t.clientId&&e.getToken===t.getToken&&e.subscriptionKey===t.subscriptionKey},p.prototype.isInitialized=function(){return this._initialized},p.prototype.initialize=function(){var i=this,o=i.options;return i.initPromise||(i.initPromise=new Promise(function(e,t){if(o.authType===s.subscriptionKey)i._initialized=!0,e();else if(o.authType===s.aad){if(o.authContext=o.authContext||p.getDefaultAuthContext(o),o.authContext.handleWindowCallback(),o.authContext.getLoginError())return void t(new Error("Error logging in the AAD users: "+o.authContext.getLoginError()));if(o.authContext.isCallback(window.location.hash))return;h.setTimeout(function(){return i._loginAndAcquire(e,t)},0)}else o.authType===s.anonymous||o.authType===s.sas?(i._initialized=!0,e(i._triggerTokenFetch())):t(new Error("An invalid authentication type was specified."))})),this.initPromise},p.getDefaultAuthContext=function(e){if(!e.aadAppId)throw new Error("No AAD app ID was specified.");if(!e.aadTenant)throw new Error("No AAD tenant was specified.");return this.defaultAuthContext||(this.defaultAuthContext=new n({instance:e.aadInstance||"https://login.windows-ppe.net/",tenant:e.aadTenant,clientId:e.aadAppId,cacheLocation:c.preferredCacheLocation})),this.defaultAuthContext},p.prototype._loginAndAcquire=function(t,i){function e(){n.authContext.acquireToken(c.DEFAULT_DOMAIN,function(e){e?i(new Error(e)):(o._initialized=!0,t())})}var o=this,n=o.options,r=n.authContext.getCachedToken(n.aadAppId),s=n.authContext.getCachedUser();if(r&&s)e();else{n.authContext.loginInProgress()||n.authContext.login();var a=setInterval(function(){n.authContext.loginInProgress()||(clearInterval(a),n.authContext.getCachedToken(n.aadAppId)?e():i(new Error(n.authContext.getLoginError()||"The AAD authentication context is not logged-in for the specified app ID: "+n.aadAppId)))},25)}},p.prototype.getAuthType=function(){return this.options.authType},p.prototype.getClientId=function(){return this.options.clientId},p.prototype.getToken=function(){var e=this,t=e.options;if(t.authType===s.aad){var i=t.authContext.getCachedToken(c.DEFAULT_DOMAIN);return i||(t.authContext.getCachedUser()||t.authContext.login(),t.authContext.acquireToken(c.DEFAULT_DOMAIN,function(e,t){e||(i=t)})),i}if(t.authType===s.anonymous||t.authType===s.sas){var o=e._getItem(c.storage.accessTokenKey);if(o){var n=e._getTokenExpiry(o);if(n<300&&0