upgrade.mjs 34 KB

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