upgrade.mjs 34 KB

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