176 |
177 |
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