UNPKG

36 kBJavaScriptView Raw
1/**
2 * @license Angular v14.0.4
3 * (c) 2010-2022 Google LLC. https://angular.io/
4 * License: MIT
5 */
6
7import { ReplaySubject } from 'rxjs';
8import { Location, PlatformLocation, LocationStrategy, APP_BASE_HREF, CommonModule, HashLocationStrategy, PathLocationStrategy } from '@angular/common';
9import * as i0 from '@angular/core';
10import { InjectionToken, Inject, Optional, NgModule } from '@angular/core';
11import { UpgradeModule } from '@angular/upgrade/static';
12
13/**
14 * @license
15 * Copyright Google LLC All Rights Reserved.
16 *
17 * Use of this source code is governed by an MIT-style license that can be
18 * found in the LICENSE file at https://angular.io/license
19 */
20function stripPrefix(val, prefix) {
21 return val.startsWith(prefix) ? val.substring(prefix.length) : val;
22}
23function deepEqual(a, b) {
24 if (a === b) {
25 return true;
26 }
27 else if (!a || !b) {
28 return false;
29 }
30 else {
31 try {
32 if ((a.prototype !== b.prototype) || (Array.isArray(a) && Array.isArray(b))) {
33 return false;
34 }
35 return JSON.stringify(a) === JSON.stringify(b);
36 }
37 catch (e) {
38 return false;
39 }
40 }
41}
42function isAnchor(el) {
43 return el.href !== undefined;
44}
45function isPromise(obj) {
46 // allow any Promise/A+ compliant thenable.
47 // It's up to the caller to ensure that obj.then conforms to the spec
48 return !!obj && typeof obj.then === 'function';
49}
50
51/**
52 * @license
53 * Copyright Google LLC All Rights Reserved.
54 *
55 * Use of this source code is governed by an MIT-style license that can be
56 * found in the LICENSE file at https://angular.io/license
57 */
58const PATH_MATCH = /^([^?#]*)(\?([^#]*))?(#(.*))?$/;
59const DOUBLE_SLASH_REGEX = /^\s*[\\/]{2,}/;
60const IGNORE_URI_REGEXP = /^\s*(javascript|mailto):/i;
61const DEFAULT_PORTS = {
62 'http:': 80,
63 'https:': 443,
64 'ftp:': 21
65};
66/**
67 * Location service that provides a drop-in replacement for the $location service
68 * provided in AngularJS.
69 *
70 * @see [Using the Angular Unified Location Service](guide/upgrade#using-the-unified-angular-location-service)
71 *
72 * @publicApi
73 */
74class $locationShim {
75 constructor($injector, location, platformLocation, urlCodec, locationStrategy) {
76 this.location = location;
77 this.platformLocation = platformLocation;
78 this.urlCodec = urlCodec;
79 this.locationStrategy = locationStrategy;
80 this.initalizing = true;
81 this.updateBrowser = false;
82 this.$$absUrl = '';
83 this.$$url = '';
84 this.$$host = '';
85 this.$$replace = false;
86 this.$$path = '';
87 this.$$search = '';
88 this.$$hash = '';
89 this.$$changeListeners = [];
90 this.cachedState = null;
91 this.urlChanges = new ReplaySubject(1);
92 this.lastBrowserUrl = '';
93 // This variable should be used *only* inside the cacheState function.
94 this.lastCachedState = null;
95 const initialUrl = this.browserUrl();
96 let parsedUrl = this.urlCodec.parse(initialUrl);
97 if (typeof parsedUrl === 'string') {
98 throw 'Invalid URL';
99 }
100 this.$$protocol = parsedUrl.protocol;
101 this.$$host = parsedUrl.hostname;
102 this.$$port = parseInt(parsedUrl.port) || DEFAULT_PORTS[parsedUrl.protocol] || null;
103 this.$$parseLinkUrl(initialUrl, initialUrl);
104 this.cacheState();
105 this.$$state = this.browserState();
106 this.location.onUrlChange((newUrl, newState) => {
107 this.urlChanges.next({ newUrl, newState });
108 });
109 if (isPromise($injector)) {
110 $injector.then($i => this.initialize($i));
111 }
112 else {
113 this.initialize($injector);
114 }
115 }
116 initialize($injector) {
117 const $rootScope = $injector.get('$rootScope');
118 const $rootElement = $injector.get('$rootElement');
119 $rootElement.on('click', (event) => {
120 if (event.ctrlKey || event.metaKey || event.shiftKey || event.which === 2 ||
121 event.button === 2) {
122 return;
123 }
124 let elm = event.target;
125 // traverse the DOM up to find first A tag
126 while (elm && elm.nodeName.toLowerCase() !== 'a') {
127 // ignore rewriting if no A tag (reached root element, or no parent - removed from document)
128 if (elm === $rootElement[0] || !(elm = elm.parentNode)) {
129 return;
130 }
131 }
132 if (!isAnchor(elm)) {
133 return;
134 }
135 const absHref = elm.href;
136 const relHref = elm.getAttribute('href');
137 // Ignore when url is started with javascript: or mailto:
138 if (IGNORE_URI_REGEXP.test(absHref)) {
139 return;
140 }
141 if (absHref && !elm.getAttribute('target') && !event.isDefaultPrevented()) {
142 if (this.$$parseLinkUrl(absHref, relHref)) {
143 // We do a preventDefault for all urls that are part of the AngularJS application,
144 // in html5mode and also without, so that we are able to abort navigation without
145 // getting double entries in the location history.
146 event.preventDefault();
147 // update location manually
148 if (this.absUrl() !== this.browserUrl()) {
149 $rootScope.$apply();
150 }
151 }
152 }
153 });
154 this.urlChanges.subscribe(({ newUrl, newState }) => {
155 const oldUrl = this.absUrl();
156 const oldState = this.$$state;
157 this.$$parse(newUrl);
158 newUrl = this.absUrl();
159 this.$$state = newState;
160 const defaultPrevented = $rootScope.$broadcast('$locationChangeStart', newUrl, oldUrl, newState, oldState)
161 .defaultPrevented;
162 // if the location was changed by a `$locationChangeStart` handler then stop
163 // processing this location change
164 if (this.absUrl() !== newUrl)
165 return;
166 // If default was prevented, set back to old state. This is the state that was locally
167 // cached in the $location service.
168 if (defaultPrevented) {
169 this.$$parse(oldUrl);
170 this.state(oldState);
171 this.setBrowserUrlWithFallback(oldUrl, false, oldState);
172 this.$$notifyChangeListeners(this.url(), this.$$state, oldUrl, oldState);
173 }
174 else {
175 this.initalizing = false;
176 $rootScope.$broadcast('$locationChangeSuccess', newUrl, oldUrl, newState, oldState);
177 this.resetBrowserUpdate();
178 }
179 if (!$rootScope.$$phase) {
180 $rootScope.$digest();
181 }
182 });
183 // update browser
184 $rootScope.$watch(() => {
185 if (this.initalizing || this.updateBrowser) {
186 this.updateBrowser = false;
187 const oldUrl = this.browserUrl();
188 const newUrl = this.absUrl();
189 const oldState = this.browserState();
190 let currentReplace = this.$$replace;
191 const urlOrStateChanged = !this.urlCodec.areEqual(oldUrl, newUrl) || oldState !== this.$$state;
192 // Fire location changes one time to on initialization. This must be done on the
193 // next tick (thus inside $evalAsync()) in order for listeners to be registered
194 // before the event fires. Mimicing behavior from $locationWatch:
195 // https://github.com/angular/angular.js/blob/master/src/ng/location.js#L983
196 if (this.initalizing || urlOrStateChanged) {
197 this.initalizing = false;
198 $rootScope.$evalAsync(() => {
199 // Get the new URL again since it could have changed due to async update
200 const newUrl = this.absUrl();
201 const defaultPrevented = $rootScope
202 .$broadcast('$locationChangeStart', newUrl, oldUrl, this.$$state, oldState)
203 .defaultPrevented;
204 // if the location was changed by a `$locationChangeStart` handler then stop
205 // processing this location change
206 if (this.absUrl() !== newUrl)
207 return;
208 if (defaultPrevented) {
209 this.$$parse(oldUrl);
210 this.$$state = oldState;
211 }
212 else {
213 // This block doesn't run when initalizing because it's going to perform the update to
214 // the URL which shouldn't be needed when initalizing.
215 if (urlOrStateChanged) {
216 this.setBrowserUrlWithFallback(newUrl, currentReplace, oldState === this.$$state ? null : this.$$state);
217 this.$$replace = false;
218 }
219 $rootScope.$broadcast('$locationChangeSuccess', newUrl, oldUrl, this.$$state, oldState);
220 if (urlOrStateChanged) {
221 this.$$notifyChangeListeners(this.url(), this.$$state, oldUrl, oldState);
222 }
223 }
224 });
225 }
226 }
227 this.$$replace = false;
228 });
229 }
230 resetBrowserUpdate() {
231 this.$$replace = false;
232 this.$$state = this.browserState();
233 this.updateBrowser = false;
234 this.lastBrowserUrl = this.browserUrl();
235 }
236 browserUrl(url, replace, state) {
237 // In modern browsers `history.state` is `null` by default; treating it separately
238 // from `undefined` would cause `$browser.url('/foo')` to change `history.state`
239 // to undefined via `pushState`. Instead, let's change `undefined` to `null` here.
240 if (typeof state === 'undefined') {
241 state = null;
242 }
243 // setter
244 if (url) {
245 let sameState = this.lastHistoryState === state;
246 // Normalize the inputted URL
247 url = this.urlCodec.parse(url).href;
248 // Don't change anything if previous and current URLs and states match.
249 if (this.lastBrowserUrl === url && sameState) {
250 return this;
251 }
252 this.lastBrowserUrl = url;
253 this.lastHistoryState = state;
254 // Remove server base from URL as the Angular APIs for updating URL require
255 // it to be the path+.
256 url = this.stripBaseUrl(this.getServerBase(), url) || url;
257 // Set the URL
258 if (replace) {
259 this.locationStrategy.replaceState(state, '', url, '');
260 }
261 else {
262 this.locationStrategy.pushState(state, '', url, '');
263 }
264 this.cacheState();
265 return this;
266 // getter
267 }
268 else {
269 return this.platformLocation.href;
270 }
271 }
272 cacheState() {
273 // This should be the only place in $browser where `history.state` is read.
274 this.cachedState = this.platformLocation.getState();
275 if (typeof this.cachedState === 'undefined') {
276 this.cachedState = null;
277 }
278 // Prevent callbacks fo fire twice if both hashchange & popstate were fired.
279 if (deepEqual(this.cachedState, this.lastCachedState)) {
280 this.cachedState = this.lastCachedState;
281 }
282 this.lastCachedState = this.cachedState;
283 this.lastHistoryState = this.cachedState;
284 }
285 /**
286 * This function emulates the $browser.state() function from AngularJS. It will cause
287 * history.state to be cached unless changed with deep equality check.
288 */
289 browserState() {
290 return this.cachedState;
291 }
292 stripBaseUrl(base, url) {
293 if (url.startsWith(base)) {
294 return url.slice(base.length);
295 }
296 return undefined;
297 }
298 getServerBase() {
299 const { protocol, hostname, port } = this.platformLocation;
300 const baseHref = this.locationStrategy.getBaseHref();
301 let url = `${protocol}//${hostname}${port ? ':' + port : ''}${baseHref || '/'}`;
302 return url.endsWith('/') ? url : url + '/';
303 }
304 parseAppUrl(url) {
305 if (DOUBLE_SLASH_REGEX.test(url)) {
306 throw new Error(`Bad Path - URL cannot start with double slashes: ${url}`);
307 }
308 let prefixed = (url.charAt(0) !== '/');
309 if (prefixed) {
310 url = '/' + url;
311 }
312 let match = this.urlCodec.parse(url, this.getServerBase());
313 if (typeof match === 'string') {
314 throw new Error(`Bad URL - Cannot parse URL: ${url}`);
315 }
316 let path = prefixed && match.pathname.charAt(0) === '/' ? match.pathname.substring(1) : match.pathname;
317 this.$$path = this.urlCodec.decodePath(path);
318 this.$$search = this.urlCodec.decodeSearch(match.search);
319 this.$$hash = this.urlCodec.decodeHash(match.hash);
320 // make sure path starts with '/';
321 if (this.$$path && this.$$path.charAt(0) !== '/') {
322 this.$$path = '/' + this.$$path;
323 }
324 }
325 /**
326 * Registers listeners for URL changes. This API is used to catch updates performed by the
327 * AngularJS framework. These changes are a subset of the `$locationChangeStart` and
328 * `$locationChangeSuccess` events which fire when AngularJS updates its internally-referenced
329 * version of the browser URL.
330 *
331 * It's possible for `$locationChange` events to happen, but for the browser URL
332 * (window.location) to remain unchanged. This `onChange` callback will fire only when AngularJS
333 * actually updates the browser URL (window.location).
334 *
335 * @param fn The callback function that is triggered for the listener when the URL changes.
336 * @param err The callback function that is triggered when an error occurs.
337 */
338 onChange(fn, err = (e) => { }) {
339 this.$$changeListeners.push([fn, err]);
340 }
341 /** @internal */
342 $$notifyChangeListeners(url = '', state, oldUrl = '', oldState) {
343 this.$$changeListeners.forEach(([fn, err]) => {
344 try {
345 fn(url, state, oldUrl, oldState);
346 }
347 catch (e) {
348 err(e);
349 }
350 });
351 }
352 /**
353 * Parses the provided URL, and sets the current URL to the parsed result.
354 *
355 * @param url The URL string.
356 */
357 $$parse(url) {
358 let pathUrl;
359 if (url.startsWith('/')) {
360 pathUrl = url;
361 }
362 else {
363 // Remove protocol & hostname if URL starts with it
364 pathUrl = this.stripBaseUrl(this.getServerBase(), url);
365 }
366 if (typeof pathUrl === 'undefined') {
367 throw new Error(`Invalid url "${url}", missing path prefix "${this.getServerBase()}".`);
368 }
369 this.parseAppUrl(pathUrl);
370 if (!this.$$path) {
371 this.$$path = '/';
372 }
373 this.composeUrls();
374 }
375 /**
376 * Parses the provided URL and its relative URL.
377 *
378 * @param url The full URL string.
379 * @param relHref A URL string relative to the full URL string.
380 */
381 $$parseLinkUrl(url, relHref) {
382 // When relHref is passed, it should be a hash and is handled separately
383 if (relHref && relHref[0] === '#') {
384 this.hash(relHref.slice(1));
385 return true;
386 }
387 let rewrittenUrl;
388 let appUrl = this.stripBaseUrl(this.getServerBase(), url);
389 if (typeof appUrl !== 'undefined') {
390 rewrittenUrl = this.getServerBase() + appUrl;
391 }
392 else if (this.getServerBase() === url + '/') {
393 rewrittenUrl = this.getServerBase();
394 }
395 // Set the URL
396 if (rewrittenUrl) {
397 this.$$parse(rewrittenUrl);
398 }
399 return !!rewrittenUrl;
400 }
401 setBrowserUrlWithFallback(url, replace, state) {
402 const oldUrl = this.url();
403 const oldState = this.$$state;
404 try {
405 this.browserUrl(url, replace, state);
406 // Make sure $location.state() returns referentially identical (not just deeply equal)
407 // state object; this makes possible quick checking if the state changed in the digest
408 // loop. Checking deep equality would be too expensive.
409 this.$$state = this.browserState();
410 }
411 catch (e) {
412 // Restore old values if pushState fails
413 this.url(oldUrl);
414 this.$$state = oldState;
415 throw e;
416 }
417 }
418 composeUrls() {
419 this.$$url = this.urlCodec.normalize(this.$$path, this.$$search, this.$$hash);
420 this.$$absUrl = this.getServerBase() + this.$$url.slice(1); // remove '/' from front of URL
421 this.updateBrowser = true;
422 }
423 /**
424 * Retrieves the full URL representation with all segments encoded according to
425 * rules specified in
426 * [RFC 3986](https://tools.ietf.org/html/rfc3986).
427 *
428 *
429 * ```js
430 * // given URL http://example.com/#/some/path?foo=bar&baz=xoxo
431 * let absUrl = $location.absUrl();
432 * // => "http://example.com/#/some/path?foo=bar&baz=xoxo"
433 * ```
434 */
435 absUrl() {
436 return this.$$absUrl;
437 }
438 url(url) {
439 if (typeof url === 'string') {
440 if (!url.length) {
441 url = '/';
442 }
443 const match = PATH_MATCH.exec(url);
444 if (!match)
445 return this;
446 if (match[1] || url === '')
447 this.path(this.urlCodec.decodePath(match[1]));
448 if (match[2] || match[1] || url === '')
449 this.search(match[3] || '');
450 this.hash(match[5] || '');
451 // Chainable method
452 return this;
453 }
454 return this.$$url;
455 }
456 /**
457 * Retrieves the protocol of the current URL.
458 *
459 * ```js
460 * // given URL http://example.com/#/some/path?foo=bar&baz=xoxo
461 * let protocol = $location.protocol();
462 * // => "http"
463 * ```
464 */
465 protocol() {
466 return this.$$protocol;
467 }
468 /**
469 * Retrieves the protocol of the current URL.
470 *
471 * In contrast to the non-AngularJS version `location.host` which returns `hostname:port`, this
472 * returns the `hostname` portion only.
473 *
474 *
475 * ```js
476 * // given URL http://example.com/#/some/path?foo=bar&baz=xoxo
477 * let host = $location.host();
478 * // => "example.com"
479 *
480 * // given URL http://user:password@example.com:8080/#/some/path?foo=bar&baz=xoxo
481 * host = $location.host();
482 * // => "example.com"
483 * host = location.host;
484 * // => "example.com:8080"
485 * ```
486 */
487 host() {
488 return this.$$host;
489 }
490 /**
491 * Retrieves the port of the current URL.
492 *
493 * ```js
494 * // given URL http://example.com/#/some/path?foo=bar&baz=xoxo
495 * let port = $location.port();
496 * // => 80
497 * ```
498 */
499 port() {
500 return this.$$port;
501 }
502 path(path) {
503 if (typeof path === 'undefined') {
504 return this.$$path;
505 }
506 // null path converts to empty string. Prepend with "/" if needed.
507 path = path !== null ? path.toString() : '';
508 path = path.charAt(0) === '/' ? path : '/' + path;
509 this.$$path = path;
510 this.composeUrls();
511 return this;
512 }
513 search(search, paramValue) {
514 switch (arguments.length) {
515 case 0:
516 return this.$$search;
517 case 1:
518 if (typeof search === 'string' || typeof search === 'number') {
519 this.$$search = this.urlCodec.decodeSearch(search.toString());
520 }
521 else if (typeof search === 'object' && search !== null) {
522 // Copy the object so it's never mutated
523 search = { ...search };
524 // remove object undefined or null properties
525 for (const key in search) {
526 if (search[key] == null)
527 delete search[key];
528 }
529 this.$$search = search;
530 }
531 else {
532 throw new Error('LocationProvider.search(): First argument must be a string or an object.');
533 }
534 break;
535 default:
536 if (typeof search === 'string') {
537 const currentSearch = this.search();
538 if (typeof paramValue === 'undefined' || paramValue === null) {
539 delete currentSearch[search];
540 return this.search(currentSearch);
541 }
542 else {
543 currentSearch[search] = paramValue;
544 return this.search(currentSearch);
545 }
546 }
547 }
548 this.composeUrls();
549 return this;
550 }
551 hash(hash) {
552 if (typeof hash === 'undefined') {
553 return this.$$hash;
554 }
555 this.$$hash = hash !== null ? hash.toString() : '';
556 this.composeUrls();
557 return this;
558 }
559 /**
560 * Changes to `$location` during the current `$digest` will replace the current
561 * history record, instead of adding a new one.
562 */
563 replace() {
564 this.$$replace = true;
565 return this;
566 }
567 state(state) {
568 if (typeof state === 'undefined') {
569 return this.$$state;
570 }
571 this.$$state = state;
572 return this;
573 }
574}
575/**
576 * The factory function used to create an instance of the `$locationShim` in Angular,
577 * and provides an API-compatiable `$locationProvider` for AngularJS.
578 *
579 * @publicApi
580 */
581class $locationShimProvider {
582 constructor(ngUpgrade, location, platformLocation, urlCodec, locationStrategy) {
583 this.ngUpgrade = ngUpgrade;
584 this.location = location;
585 this.platformLocation = platformLocation;
586 this.urlCodec = urlCodec;
587 this.locationStrategy = locationStrategy;
588 }
589 /**
590 * Factory method that returns an instance of the $locationShim
591 */
592 $get() {
593 return new $locationShim(this.ngUpgrade.$injector, this.location, this.platformLocation, this.urlCodec, this.locationStrategy);
594 }
595 /**
596 * Stub method used to keep API compatible with AngularJS. This setting is configured through
597 * the LocationUpgradeModule's `config` method in your Angular app.
598 */
599 hashPrefix(prefix) {
600 throw new Error('Configure LocationUpgrade through LocationUpgradeModule.config method.');
601 }
602 /**
603 * Stub method used to keep API compatible with AngularJS. This setting is configured through
604 * the LocationUpgradeModule's `config` method in your Angular app.
605 */
606 html5Mode(mode) {
607 throw new Error('Configure LocationUpgrade through LocationUpgradeModule.config method.');
608 }
609}
610
611/**
612 * @license
613 * Copyright Google LLC All Rights Reserved.
614 *
615 * Use of this source code is governed by an MIT-style license that can be
616 * found in the LICENSE file at https://angular.io/license
617 */
618/**
619 * A codec for encoding and decoding URL parts.
620 *
621 * @publicApi
622 **/
623class UrlCodec {
624}
625/**
626 * A `UrlCodec` that uses logic from AngularJS to serialize and parse URLs
627 * and URL parameters.
628 *
629 * @publicApi
630 */
631class AngularJSUrlCodec {
632 // https://github.com/angular/angular.js/blob/864c7f0/src/ng/location.js#L15
633 encodePath(path) {
634 const segments = path.split('/');
635 let i = segments.length;
636 while (i--) {
637 // decode forward slashes to prevent them from being double encoded
638 segments[i] = encodeUriSegment(segments[i].replace(/%2F/g, '/'));
639 }
640 path = segments.join('/');
641 return _stripIndexHtml((path && path[0] !== '/' && '/' || '') + path);
642 }
643 // https://github.com/angular/angular.js/blob/864c7f0/src/ng/location.js#L42
644 encodeSearch(search) {
645 if (typeof search === 'string') {
646 search = parseKeyValue(search);
647 }
648 search = toKeyValue(search);
649 return search ? '?' + search : '';
650 }
651 // https://github.com/angular/angular.js/blob/864c7f0/src/ng/location.js#L44
652 encodeHash(hash) {
653 hash = encodeUriSegment(hash);
654 return hash ? '#' + hash : '';
655 }
656 // https://github.com/angular/angular.js/blob/864c7f0/src/ng/location.js#L27
657 decodePath(path, html5Mode = true) {
658 const segments = path.split('/');
659 let i = segments.length;
660 while (i--) {
661 segments[i] = decodeURIComponent(segments[i]);
662 if (html5Mode) {
663 // encode forward slashes to prevent them from being mistaken for path separators
664 segments[i] = segments[i].replace(/\//g, '%2F');
665 }
666 }
667 return segments.join('/');
668 }
669 // https://github.com/angular/angular.js/blob/864c7f0/src/ng/location.js#L72
670 decodeSearch(search) {
671 return parseKeyValue(search);
672 }
673 // https://github.com/angular/angular.js/blob/864c7f0/src/ng/location.js#L73
674 decodeHash(hash) {
675 hash = decodeURIComponent(hash);
676 return hash[0] === '#' ? hash.substring(1) : hash;
677 }
678 normalize(pathOrHref, search, hash, baseUrl) {
679 if (arguments.length === 1) {
680 const parsed = this.parse(pathOrHref, baseUrl);
681 if (typeof parsed === 'string') {
682 return parsed;
683 }
684 const serverUrl = `${parsed.protocol}://${parsed.hostname}${parsed.port ? ':' + parsed.port : ''}`;
685 return this.normalize(this.decodePath(parsed.pathname), this.decodeSearch(parsed.search), this.decodeHash(parsed.hash), serverUrl);
686 }
687 else {
688 const encPath = this.encodePath(pathOrHref);
689 const encSearch = search && this.encodeSearch(search) || '';
690 const encHash = hash && this.encodeHash(hash) || '';
691 let joinedPath = (baseUrl || '') + encPath;
692 if (!joinedPath.length || joinedPath[0] !== '/') {
693 joinedPath = '/' + joinedPath;
694 }
695 return joinedPath + encSearch + encHash;
696 }
697 }
698 areEqual(valA, valB) {
699 return this.normalize(valA) === this.normalize(valB);
700 }
701 // https://github.com/angular/angular.js/blob/864c7f0/src/ng/urlUtils.js#L60
702 parse(url, base) {
703 try {
704 // Safari 12 throws an error when the URL constructor is called with an undefined base.
705 const parsed = !base ? new URL(url) : new URL(url, base);
706 return {
707 href: parsed.href,
708 protocol: parsed.protocol ? parsed.protocol.replace(/:$/, '') : '',
709 host: parsed.host,
710 search: parsed.search ? parsed.search.replace(/^\?/, '') : '',
711 hash: parsed.hash ? parsed.hash.replace(/^#/, '') : '',
712 hostname: parsed.hostname,
713 port: parsed.port,
714 pathname: (parsed.pathname.charAt(0) === '/') ? parsed.pathname : '/' + parsed.pathname
715 };
716 }
717 catch (e) {
718 throw new Error(`Invalid URL (${url}) with base (${base})`);
719 }
720 }
721}
722function _stripIndexHtml(url) {
723 return url.replace(/\/index.html$/, '');
724}
725/**
726 * Tries to decode the URI component without throwing an exception.
727 *
728 * @param str value potential URI component to check.
729 * @returns the decoded URI if it can be decoded or else `undefined`.
730 */
731function tryDecodeURIComponent(value) {
732 try {
733 return decodeURIComponent(value);
734 }
735 catch (e) {
736 // Ignore any invalid uri component.
737 return undefined;
738 }
739}
740/**
741 * Parses an escaped url query string into key-value pairs. Logic taken from
742 * https://github.com/angular/angular.js/blob/864c7f0/src/Angular.js#L1382
743 */
744function parseKeyValue(keyValue) {
745 const obj = {};
746 (keyValue || '').split('&').forEach((keyValue) => {
747 let splitPoint, key, val;
748 if (keyValue) {
749 key = keyValue = keyValue.replace(/\+/g, '%20');
750 splitPoint = keyValue.indexOf('=');
751 if (splitPoint !== -1) {
752 key = keyValue.substring(0, splitPoint);
753 val = keyValue.substring(splitPoint + 1);
754 }
755 key = tryDecodeURIComponent(key);
756 if (typeof key !== 'undefined') {
757 val = typeof val !== 'undefined' ? tryDecodeURIComponent(val) : true;
758 if (!obj.hasOwnProperty(key)) {
759 obj[key] = val;
760 }
761 else if (Array.isArray(obj[key])) {
762 obj[key].push(val);
763 }
764 else {
765 obj[key] = [obj[key], val];
766 }
767 }
768 }
769 });
770 return obj;
771}
772/**
773 * Serializes into key-value pairs. Logic taken from
774 * https://github.com/angular/angular.js/blob/864c7f0/src/Angular.js#L1409
775 */
776function toKeyValue(obj) {
777 const parts = [];
778 for (const key in obj) {
779 let value = obj[key];
780 if (Array.isArray(value)) {
781 value.forEach((arrayValue) => {
782 parts.push(encodeUriQuery(key, true) +
783 (arrayValue === true ? '' : '=' + encodeUriQuery(arrayValue, true)));
784 });
785 }
786 else {
787 parts.push(encodeUriQuery(key, true) +
788 (value === true ? '' : '=' + encodeUriQuery(value, true)));
789 }
790 }
791 return parts.length ? parts.join('&') : '';
792}
793/**
794 * We need our custom method because encodeURIComponent is too aggressive and doesn't follow
795 * https://tools.ietf.org/html/rfc3986 with regards to the character set (pchar) allowed in path
796 * segments:
797 * segment = *pchar
798 * pchar = unreserved / pct-encoded / sub-delims / ":" / "@"
799 * pct-encoded = "%" HEXDIG HEXDIG
800 * unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
801 * sub-delims = "!" / "$" / "&" / "'" / "(" / ")"
802 * / "*" / "+" / "," / ";" / "="
803 *
804 * Logic from https://github.com/angular/angular.js/blob/864c7f0/src/Angular.js#L1437
805 */
806function encodeUriSegment(val) {
807 return encodeUriQuery(val, true).replace(/%26/g, '&').replace(/%3D/gi, '=').replace(/%2B/gi, '+');
808}
809/**
810 * This method is intended for encoding *key* or *value* parts of query component. We need a custom
811 * method because encodeURIComponent is too aggressive and encodes stuff that doesn't have to be
812 * encoded per https://tools.ietf.org/html/rfc3986:
813 * query = *( pchar / "/" / "?" )
814 * pchar = unreserved / pct-encoded / sub-delims / ":" / "@"
815 * unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
816 * pct-encoded = "%" HEXDIG HEXDIG
817 * sub-delims = "!" / "$" / "&" / "'" / "(" / ")"
818 * / "*" / "+" / "," / ";" / "="
819 *
820 * Logic from https://github.com/angular/angular.js/blob/864c7f0/src/Angular.js#L1456
821 */
822function encodeUriQuery(val, pctEncodeSpaces = false) {
823 return encodeURIComponent(val)
824 .replace(/%40/g, '@')
825 .replace(/%3A/gi, ':')
826 .replace(/%24/g, '$')
827 .replace(/%2C/gi, ',')
828 .replace(/%3B/gi, ';')
829 .replace(/%20/g, (pctEncodeSpaces ? '%20' : '+'));
830}
831
832/**
833 * @license
834 * Copyright Google LLC All Rights Reserved.
835 *
836 * Use of this source code is governed by an MIT-style license that can be
837 * found in the LICENSE file at https://angular.io/license
838 */
839/**
840 * A provider token used to configure the location upgrade module.
841 *
842 * @publicApi
843 */
844const LOCATION_UPGRADE_CONFIGURATION = new InjectionToken('LOCATION_UPGRADE_CONFIGURATION');
845const APP_BASE_HREF_RESOLVED = new InjectionToken('APP_BASE_HREF_RESOLVED');
846/**
847 * `NgModule` used for providing and configuring Angular's Unified Location Service for upgrading.
848 *
849 * @see [Using the Unified Angular Location Service](guide/upgrade#using-the-unified-angular-location-service)
850 *
851 * @publicApi
852 */
853class LocationUpgradeModule {
854 static config(config) {
855 return {
856 ngModule: LocationUpgradeModule,
857 providers: [
858 Location,
859 {
860 provide: $locationShim,
861 useFactory: provide$location,
862 deps: [UpgradeModule, Location, PlatformLocation, UrlCodec, LocationStrategy]
863 },
864 { provide: LOCATION_UPGRADE_CONFIGURATION, useValue: config ? config : {} },
865 { provide: UrlCodec, useFactory: provideUrlCodec, deps: [LOCATION_UPGRADE_CONFIGURATION] },
866 {
867 provide: APP_BASE_HREF_RESOLVED,
868 useFactory: provideAppBaseHref,
869 deps: [LOCATION_UPGRADE_CONFIGURATION, [new Inject(APP_BASE_HREF), new Optional()]]
870 },
871 {
872 provide: LocationStrategy,
873 useFactory: provideLocationStrategy,
874 deps: [
875 PlatformLocation,
876 APP_BASE_HREF_RESOLVED,
877 LOCATION_UPGRADE_CONFIGURATION,
878 ]
879 },
880 ],
881 };
882 }
883}
884LocationUpgradeModule.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.0.4", ngImport: i0, type: LocationUpgradeModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
885LocationUpgradeModule.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "14.0.4", ngImport: i0, type: LocationUpgradeModule, imports: [CommonModule] });
886LocationUpgradeModule.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "14.0.4", ngImport: i0, type: LocationUpgradeModule, imports: [CommonModule] });
887i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.0.4", ngImport: i0, type: LocationUpgradeModule, decorators: [{
888 type: NgModule,
889 args: [{ imports: [CommonModule] }]
890 }] });
891function provideAppBaseHref(config, appBaseHref) {
892 if (config && config.appBaseHref != null) {
893 return config.appBaseHref;
894 }
895 else if (appBaseHref != null) {
896 return appBaseHref;
897 }
898 return '';
899}
900function provideUrlCodec(config) {
901 const codec = config && config.urlCodec || AngularJSUrlCodec;
902 return new codec();
903}
904function provideLocationStrategy(platformLocation, baseHref, options = {}) {
905 return options.useHash ? new HashLocationStrategy(platformLocation, baseHref) :
906 new PathLocationStrategy(platformLocation, baseHref);
907}
908function provide$location(ngUpgrade, location, platformLocation, urlCodec, locationStrategy) {
909 const $locationProvider = new $locationShimProvider(ngUpgrade, location, platformLocation, urlCodec, locationStrategy);
910 return $locationProvider.$get();
911}
912
913/**
914 * @license
915 * Copyright Google LLC All Rights Reserved.
916 *
917 * Use of this source code is governed by an MIT-style license that can be
918 * found in the LICENSE file at https://angular.io/license
919 */
920
921/**
922 * @license
923 * Copyright Google LLC All Rights Reserved.
924 *
925 * Use of this source code is governed by an MIT-style license that can be
926 * found in the LICENSE file at https://angular.io/license
927 */
928// This file only reexports content of the `src` folder. Keep it that way.
929
930/**
931 * @license
932 * Copyright Google LLC All Rights Reserved.
933 *
934 * Use of this source code is governed by an MIT-style license that can be
935 * found in the LICENSE file at https://angular.io/license
936 */
937
938/**
939 * Generated bundle index. Do not edit.
940 */
941
942export { $locationShim, $locationShimProvider, AngularJSUrlCodec, LOCATION_UPGRADE_CONFIGURATION, LocationUpgradeModule, UrlCodec };
943//# sourceMappingURL=upgrade.mjs.map