/** * @license Angular v9.0.2 * (c) 2010-2020 Google LLC. https://angular.io/ * License: MIT */ import { Injectable, InjectionToken, Inject, PLATFORM_ID, Injector, NgModule } from '@angular/core'; import { of, Observable } from 'rxjs'; import { concatMap, filter, map } from 'rxjs/operators'; import { DOCUMENT, ɵparseCookieValue } from '@angular/common'; /** * @fileoverview added by tsickle * Generated from: packages/common/http/src/backend.ts * @suppress {checkTypes,constantProperty,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc */ /** * @license * Copyright Google Inc. All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ /** * Transforms an `HttpRequest` into a stream of `HttpEvent`s, one of which will likely be a * `HttpResponse`. * * `HttpHandler` is injectable. When injected, the handler instance dispatches requests to the * first interceptor in the chain, which dispatches to the second, etc, eventually reaching the * `HttpBackend`. * * In an `HttpInterceptor`, the `HttpHandler` parameter is the next interceptor in the chain. * * \@publicApi * @abstract */ class HttpHandler { } if (false) { /** * @abstract * @param {?} req * @return {?} */ HttpHandler.prototype.handle = function (req) { }; } /** * A final `HttpHandler` which will dispatch the request via browser HTTP APIs to a backend. * * Interceptors sit between the `HttpClient` interface and the `HttpBackend`. * * When injected, `HttpBackend` dispatches requests directly to the backend, without going * through the interceptor chain. * * \@publicApi * @abstract */ class HttpBackend { } if (false) { /** * @abstract * @param {?} req * @return {?} */ HttpBackend.prototype.handle = function (req) { }; } /** * @fileoverview added by tsickle * Generated from: packages/common/http/src/headers.ts * @suppress {checkTypes,constantProperty,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc */ /** * @license * Copyright Google Inc. All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ /** * @record */ function Update() { } if (false) { /** @type {?} */ Update.prototype.name; /** @type {?|undefined} */ Update.prototype.value; /** @type {?} */ Update.prototype.op; } /** * Represents the header configuration options for an HTTP request. * Instances are immutable. Modifying methods return a cloned * instance with the change. The original object is never changed. * * \@publicApi */ class HttpHeaders { /** * Constructs a new HTTP header object with the given values. * @param {?=} headers */ constructor(headers) { /** * Internal map of lowercased header names to the normalized * form of the name (the form seen first). */ this.normalizedNames = new Map(); /** * Queued updates to be materialized the next initialization. */ this.lazyUpdate = null; if (!headers) { this.headers = new Map(); } else if (typeof headers === 'string') { this.lazyInit = (/** * @return {?} */ () => { this.headers = new Map(); headers.split('\n').forEach((/** * @param {?} line * @return {?} */ line => { /** @type {?} */ const index = line.indexOf(':'); if (index > 0) { /** @type {?} */ const name = line.slice(0, index); /** @type {?} */ const key = name.toLowerCase(); /** @type {?} */ const value = line.slice(index + 1).trim(); this.maybeSetNormalizedName(name, key); if (this.headers.has(key)) { (/** @type {?} */ (this.headers.get(key))).push(value); } else { this.headers.set(key, [value]); } } })); }); } else { this.lazyInit = (/** * @return {?} */ () => { this.headers = new Map(); Object.keys(headers).forEach((/** * @param {?} name * @return {?} */ name => { /** @type {?} */ let values = headers[name]; /** @type {?} */ const key = name.toLowerCase(); if (typeof values === 'string') { values = [values]; } if (values.length > 0) { this.headers.set(key, values); this.maybeSetNormalizedName(name, key); } })); }); } } /** * Checks for existence of a given header. * * @param {?} name The header name to check for existence. * * @return {?} True if the header exists, false otherwise. */ has(name) { this.init(); return this.headers.has(name.toLowerCase()); } /** * Retrieves the first value of a given header. * * @param {?} name The header name. * * @return {?} The value string if the header exists, null otherwise */ get(name) { this.init(); /** @type {?} */ const values = this.headers.get(name.toLowerCase()); return values && values.length > 0 ? values[0] : null; } /** * Retrieves the names of the headers. * * @return {?} A list of header names. */ keys() { this.init(); return Array.from(this.normalizedNames.values()); } /** * Retrieves a list of values for a given header. * * @param {?} name The header name from which to retrieve values. * * @return {?} A string of values if the header exists, null otherwise. */ getAll(name) { this.init(); return this.headers.get(name.toLowerCase()) || null; } /** * Appends a new value to the existing set of values for a header * and returns them in a clone of the original instance. * * @param {?} name The header name for which to append the values. * @param {?} value The value to append. * * @return {?} A clone of the HTTP headers object with the value appended to the given header. */ append(name, value) { return this.clone({ name, value, op: 'a' }); } /** * Sets or modifies a value for a given header in a clone of the original instance. * If the header already exists, its value is replaced with the given value * in the returned object. * * @param {?} name The header name. * @param {?} value The value or values to set or overide for the given header. * * @return {?} A clone of the HTTP headers object with the newly set header value. */ set(name, value) { return this.clone({ name, value, op: 's' }); } /** * Deletes values for a given header in a clone of the original instance. * * @param {?} name The header name. * @param {?=} value The value or values to delete for the given header. * * @return {?} A clone of the HTTP headers object with the given value deleted. */ delete(name, value) { return this.clone({ name, value, op: 'd' }); } /** * @private * @param {?} name * @param {?} lcName * @return {?} */ maybeSetNormalizedName(name, lcName) { if (!this.normalizedNames.has(lcName)) { this.normalizedNames.set(lcName, name); } } /** * @private * @return {?} */ init() { if (!!this.lazyInit) { if (this.lazyInit instanceof HttpHeaders) { this.copyFrom(this.lazyInit); } else { this.lazyInit(); } this.lazyInit = null; if (!!this.lazyUpdate) { this.lazyUpdate.forEach((/** * @param {?} update * @return {?} */ update => this.applyUpdate(update))); this.lazyUpdate = null; } } } /** * @private * @param {?} other * @return {?} */ copyFrom(other) { other.init(); Array.from(other.headers.keys()).forEach((/** * @param {?} key * @return {?} */ key => { this.headers.set(key, (/** @type {?} */ (other.headers.get(key)))); this.normalizedNames.set(key, (/** @type {?} */ (other.normalizedNames.get(key)))); })); } /** * @private * @param {?} update * @return {?} */ clone(update) { /** @type {?} */ const clone = new HttpHeaders(); clone.lazyInit = (!!this.lazyInit && this.lazyInit instanceof HttpHeaders) ? this.lazyInit : this; clone.lazyUpdate = (this.lazyUpdate || []).concat([update]); return clone; } /** * @private * @param {?} update * @return {?} */ applyUpdate(update) { /** @type {?} */ const key = update.name.toLowerCase(); switch (update.op) { case 'a': case 's': /** @type {?} */ let value = (/** @type {?} */ (update.value)); if (typeof value === 'string') { value = [value]; } if (value.length === 0) { return; } this.maybeSetNormalizedName(update.name, key); /** @type {?} */ const base = (update.op === 'a' ? this.headers.get(key) : undefined) || []; base.push(...value); this.headers.set(key, base); break; case 'd': /** @type {?} */ const toDelete = (/** @type {?} */ (update.value)); if (!toDelete) { this.headers.delete(key); this.normalizedNames.delete(key); } else { /** @type {?} */ let existing = this.headers.get(key); if (!existing) { return; } existing = existing.filter((/** * @param {?} value * @return {?} */ value => toDelete.indexOf(value) === -1)); if (existing.length === 0) { this.headers.delete(key); this.normalizedNames.delete(key); } else { this.headers.set(key, existing); } } break; } } /** * \@internal * @param {?} fn * @return {?} */ forEach(fn) { this.init(); Array.from(this.normalizedNames.keys()) .forEach((/** * @param {?} key * @return {?} */ key => fn((/** @type {?} */ (this.normalizedNames.get(key))), (/** @type {?} */ (this.headers.get(key)))))); } } if (false) { /** * Internal map of lowercase header names to values. * @type {?} * @private */ HttpHeaders.prototype.headers; /** * Internal map of lowercased header names to the normalized * form of the name (the form seen first). * @type {?} * @private */ HttpHeaders.prototype.normalizedNames; /** * Complete the lazy initialization of this object (needed before reading). * @type {?} * @private */ HttpHeaders.prototype.lazyInit; /** * Queued updates to be materialized the next initialization. * @type {?} * @private */ HttpHeaders.prototype.lazyUpdate; } /** * @fileoverview added by tsickle * Generated from: packages/common/http/src/params.ts * @suppress {checkTypes,constantProperty,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc */ /** * @license * Copyright Google Inc. All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ /** * A codec for encoding and decoding parameters in URLs. * * Used by `HttpParams`. * * \@publicApi * * @record */ function HttpParameterCodec() { } if (false) { /** * @param {?} key * @return {?} */ HttpParameterCodec.prototype.encodeKey = function (key) { }; /** * @param {?} value * @return {?} */ HttpParameterCodec.prototype.encodeValue = function (value) { }; /** * @param {?} key * @return {?} */ HttpParameterCodec.prototype.decodeKey = function (key) { }; /** * @param {?} value * @return {?} */ HttpParameterCodec.prototype.decodeValue = function (value) { }; } /** * Provides encoding and decoding of URL parameter and query-string values. * * Serializes and parses URL parameter keys and values to encode and decode them. * If you pass URL query parameters without encoding, * the query parameters can be misinterpreted at the receiving end. * * * \@publicApi */ class HttpUrlEncodingCodec { /** * Encodes a key name for a URL parameter or query-string. * @param {?} key The key name. * @return {?} The encoded key name. */ encodeKey(key) { return standardEncoding(key); } /** * Encodes the value of a URL parameter or query-string. * @param {?} value The value. * @return {?} The encoded value. */ encodeValue(value) { return standardEncoding(value); } /** * Decodes an encoded URL parameter or query-string key. * @param {?} key The encoded key name. * @return {?} The decoded key name. */ decodeKey(key) { return decodeURIComponent(key); } /** * Decodes an encoded URL parameter or query-string value. * @param {?} value The encoded value. * @return {?} The decoded value. */ decodeValue(value) { return decodeURIComponent(value); } } /** * @param {?} rawParams * @param {?} codec * @return {?} */ function paramParser(rawParams, codec) { /** @type {?} */ const map = new Map(); if (rawParams.length > 0) { /** @type {?} */ const params = rawParams.split('&'); params.forEach((/** * @param {?} param * @return {?} */ (param) => { /** @type {?} */ const eqIdx = param.indexOf('='); const [key, val] = eqIdx == -1 ? [codec.decodeKey(param), ''] : [codec.decodeKey(param.slice(0, eqIdx)), codec.decodeValue(param.slice(eqIdx + 1))]; /** @type {?} */ const list = map.get(key) || []; list.push(val); map.set(key, list); })); } return map; } /** * @param {?} v * @return {?} */ function standardEncoding(v) { return encodeURIComponent(v) .replace(/%40/gi, '@') .replace(/%3A/gi, ':') .replace(/%24/gi, '$') .replace(/%2C/gi, ',') .replace(/%3B/gi, ';') .replace(/%2B/gi, '+') .replace(/%3D/gi, '=') .replace(/%3F/gi, '?') .replace(/%2F/gi, '/'); } /** * @record */ function Update$1() { } if (false) { /** @type {?} */ Update$1.prototype.param; /** @type {?|undefined} */ Update$1.prototype.value; /** @type {?} */ Update$1.prototype.op; } /** * Options used to construct an `HttpParams` instance. * * \@publicApi * @record */ function HttpParamsOptions() { } if (false) { /** * String representation of the HTTP parameters in URL-query-string format. * Mutually exclusive with `fromObject`. * @type {?|undefined} */ HttpParamsOptions.prototype.fromString; /** * Object map of the HTTP parameters. Mutually exclusive with `fromString`. * @type {?|undefined} */ HttpParamsOptions.prototype.fromObject; /** * Encoding codec used to parse and serialize the parameters. * @type {?|undefined} */ HttpParamsOptions.prototype.encoder; } /** * An HTTP request/response body that represents serialized parameters, * per the MIME type `application/x-www-form-urlencoded`. * * This class is immutable; all mutation operations return a new instance. * * \@publicApi */ class HttpParams { /** * @param {?=} options */ constructor(options = (/** @type {?} */ ({}))) { this.updates = null; this.cloneFrom = null; this.encoder = options.encoder || new HttpUrlEncodingCodec(); if (!!options.fromString) { if (!!options.fromObject) { throw new Error(`Cannot specify both fromString and fromObject.`); } this.map = paramParser(options.fromString, this.encoder); } else if (!!options.fromObject) { this.map = new Map(); Object.keys(options.fromObject).forEach((/** * @param {?} key * @return {?} */ key => { /** @type {?} */ const value = ((/** @type {?} */ (options.fromObject)))[key]; (/** @type {?} */ (this.map)).set(key, Array.isArray(value) ? value : [value]); })); } else { this.map = null; } } /** * Reports whether the body includes one or more values for a given parameter. * @param {?} param The parameter name. * @return {?} True if the parameter has one or more values, * false if it has no value or is not present. */ has(param) { this.init(); return (/** @type {?} */ (this.map)).has(param); } /** * Retrieves the first value for a parameter. * @param {?} param The parameter name. * @return {?} The first value of the given parameter, * or `null` if the parameter is not present. */ get(param) { this.init(); /** @type {?} */ const res = (/** @type {?} */ (this.map)).get(param); return !!res ? res[0] : null; } /** * Retrieves all values for a parameter. * @param {?} param The parameter name. * @return {?} All values in a string array, * or `null` if the parameter not present. */ getAll(param) { this.init(); return (/** @type {?} */ (this.map)).get(param) || null; } /** * Retrieves all the parameters for this body. * @return {?} The parameter names in a string array. */ keys() { this.init(); return Array.from((/** @type {?} */ (this.map)).keys()); } /** * Appends a new value to existing values for a parameter. * @param {?} param The parameter name. * @param {?} value The new value to add. * @return {?} A new body with the appended value. */ append(param, value) { return this.clone({ param, value, op: 'a' }); } /** * Replaces the value for a parameter. * @param {?} param The parameter name. * @param {?} value The new value. * @return {?} A new body with the new value. */ set(param, value) { return this.clone({ param, value, op: 's' }); } /** * Removes a given value or all values from a parameter. * @param {?} param The parameter name. * @param {?=} value The value to remove, if provided. * @return {?} A new body with the given value removed, or with all values * removed if no value is specified. */ delete(param, value) { return this.clone({ param, value, op: 'd' }); } /** * Serializes the body to an encoded string, where key-value pairs (separated by `=`) are * separated by `&`s. * @return {?} */ toString() { this.init(); return this.keys() .map((/** * @param {?} key * @return {?} */ key => { /** @type {?} */ const eKey = this.encoder.encodeKey(key); // `a: ['1']` produces `'a=1'` // `b: []` produces `''` // `c: ['1', '2']` produces `'c=1&c=2'` return (/** @type {?} */ ((/** @type {?} */ (this.map)).get(key))).map((/** * @param {?} value * @return {?} */ value => eKey + '=' + this.encoder.encodeValue(value))) .join('&'); })) // filter out empty values because `b: []` produces `''` // which results in `a=1&&c=1&c=2` instead of `a=1&c=1&c=2` if we don't .filter((/** * @param {?} param * @return {?} */ param => param !== '')) .join('&'); } /** * @private * @param {?} update * @return {?} */ clone(update) { /** @type {?} */ const clone = new HttpParams((/** @type {?} */ ({ encoder: this.encoder }))); clone.cloneFrom = this.cloneFrom || this; clone.updates = (this.updates || []).concat([update]); return clone; } /** * @private * @return {?} */ init() { if (this.map === null) { this.map = new Map(); } if (this.cloneFrom !== null) { this.cloneFrom.init(); this.cloneFrom.keys().forEach((/** * @param {?} key * @return {?} */ key => (/** @type {?} */ (this.map)).set(key, (/** @type {?} */ ((/** @type {?} */ ((/** @type {?} */ (this.cloneFrom)).map)).get(key)))))); (/** @type {?} */ (this.updates)).forEach((/** * @param {?} update * @return {?} */ update => { switch (update.op) { case 'a': case 's': /** @type {?} */ const base = (update.op === 'a' ? (/** @type {?} */ (this.map)).get(update.param) : undefined) || []; base.push((/** @type {?} */ (update.value))); (/** @type {?} */ (this.map)).set(update.param, base); break; case 'd': if (update.value !== undefined) { /** @type {?} */ let base = (/** @type {?} */ (this.map)).get(update.param) || []; /** @type {?} */ const idx = base.indexOf(update.value); if (idx !== -1) { base.splice(idx, 1); } if (base.length > 0) { (/** @type {?} */ (this.map)).set(update.param, base); } else { (/** @type {?} */ (this.map)).delete(update.param); } } else { (/** @type {?} */ (this.map)).delete(update.param); break; } } })); this.cloneFrom = this.updates = null; } } } if (false) { /** * @type {?} * @private */ HttpParams.prototype.map; /** * @type {?} * @private */ HttpParams.prototype.encoder; /** * @type {?} * @private */ HttpParams.prototype.updates; /** * @type {?} * @private */ HttpParams.prototype.cloneFrom; } /** * @fileoverview added by tsickle * Generated from: packages/common/http/src/request.ts * @suppress {checkTypes,constantProperty,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc */ /** * Construction interface for `HttpRequest`s. * * All values are optional and will override default values if provided. * @record */ function HttpRequestInit() { } if (false) { /** @type {?|undefined} */ HttpRequestInit.prototype.headers; /** @type {?|undefined} */ HttpRequestInit.prototype.reportProgress; /** @type {?|undefined} */ HttpRequestInit.prototype.params; /** @type {?|undefined} */ HttpRequestInit.prototype.responseType; /** @type {?|undefined} */ HttpRequestInit.prototype.withCredentials; } /** * Determine whether the given HTTP method may include a body. * @param {?} method * @return {?} */ function mightHaveBody(method) { switch (method) { case 'DELETE': case 'GET': case 'HEAD': case 'OPTIONS': case 'JSONP': return false; default: return true; } } /** * Safely assert whether the given value is an ArrayBuffer. * * In some execution environments ArrayBuffer is not defined. * @param {?} value * @return {?} */ function isArrayBuffer(value) { return typeof ArrayBuffer !== 'undefined' && value instanceof ArrayBuffer; } /** * Safely assert whether the given value is a Blob. * * In some execution environments Blob is not defined. * @param {?} value * @return {?} */ function isBlob(value) { return typeof Blob !== 'undefined' && value instanceof Blob; } /** * Safely assert whether the given value is a FormData instance. * * In some execution environments FormData is not defined. * @param {?} value * @return {?} */ function isFormData(value) { return typeof FormData !== 'undefined' && value instanceof FormData; } /** * An outgoing HTTP request with an optional typed body. * * `HttpRequest` represents an outgoing request, including URL, method, * headers, body, and other request configuration options. Instances should be * assumed to be immutable. To modify a `HttpRequest`, the `clone` * method should be used. * * \@publicApi * @template T */ class HttpRequest { /** * @param {?} method * @param {?} url * @param {?=} third * @param {?=} fourth */ constructor(method, url, third, fourth) { this.url = url; /** * The request body, or `null` if one isn't set. * * Bodies are not enforced to be immutable, as they can include a reference to any * user-defined data type. However, interceptors should take care to preserve * idempotence by treating them as such. */ this.body = null; /** * Whether this request should be made in a way that exposes progress events. * * Progress events are expensive (change detection runs on each event) and so * they should only be requested if the consumer intends to monitor them. */ this.reportProgress = false; /** * Whether this request should be sent with outgoing credentials (cookies). */ this.withCredentials = false; /** * The expected response type of the server. * * This is used to parse the response appropriately before returning it to * the requestee. */ this.responseType = 'json'; this.method = method.toUpperCase(); // Next, need to figure out which argument holds the HttpRequestInit // options, if any. /** @type {?} */ let options; // Check whether a body argument is expected. The only valid way to omit // the body argument is to use a known no-body method like GET. if (mightHaveBody(this.method) || !!fourth) { // Body is the third argument, options are the fourth. this.body = (third !== undefined) ? (/** @type {?} */ (third)) : null; options = fourth; } else { // No body required, options are the third argument. The body stays null. options = (/** @type {?} */ (third)); } // If options have been passed, interpret them. if (options) { // Normalize reportProgress and withCredentials. this.reportProgress = !!options.reportProgress; this.withCredentials = !!options.withCredentials; // Override default response type of 'json' if one is provided. if (!!options.responseType) { this.responseType = options.responseType; } // Override headers if they're provided. if (!!options.headers) { this.headers = options.headers; } if (!!options.params) { this.params = options.params; } } // If no headers have been passed in, construct a new HttpHeaders instance. if (!this.headers) { this.headers = new HttpHeaders(); } // If no parameters have been passed in, construct a new HttpUrlEncodedParams instance. if (!this.params) { this.params = new HttpParams(); this.urlWithParams = url; } else { // Encode the parameters to a string in preparation for inclusion in the URL. /** @type {?} */ const params = this.params.toString(); if (params.length === 0) { // No parameters, the visible URL is just the URL given at creation time. this.urlWithParams = url; } else { // Does the URL already have query parameters? Look for '?'. /** @type {?} */ const qIdx = url.indexOf('?'); // There are 3 cases to handle: // 1) No existing parameters -> append '?' followed by params. // 2) '?' exists and is followed by existing query string -> // append '&' followed by params. // 3) '?' exists at the end of the url -> append params directly. // This basically amounts to determining the character, if any, with // which to join the URL and parameters. /** @type {?} */ const sep = qIdx === -1 ? '?' : (qIdx < url.length - 1 ? '&' : ''); this.urlWithParams = url + sep + params; } } } /** * Transform the free-form body into a serialized format suitable for * transmission to the server. * @return {?} */ serializeBody() { // If no body is present, no need to serialize it. if (this.body === null) { return null; } // Check whether the body is already in a serialized form. If so, // it can just be returned directly. if (isArrayBuffer(this.body) || isBlob(this.body) || isFormData(this.body) || typeof this.body === 'string') { return this.body; } // Check whether the body is an instance of HttpUrlEncodedParams. if (this.body instanceof HttpParams) { return this.body.toString(); } // Check whether the body is an object or array, and serialize with JSON if so. if (typeof this.body === 'object' || typeof this.body === 'boolean' || Array.isArray(this.body)) { return JSON.stringify(this.body); } // Fall back on toString() for everything else. return ((/** @type {?} */ (this.body))).toString(); } /** * Examine the body and attempt to infer an appropriate MIME type * for it. * * If no such type can be inferred, this method will return `null`. * @return {?} */ detectContentTypeHeader() { // An empty body has no content type. if (this.body === null) { return null; } // FormData bodies rely on the browser's content type assignment. if (isFormData(this.body)) { return null; } // Blobs usually have their own content type. If it doesn't, then // no type can be inferred. if (isBlob(this.body)) { return this.body.type || null; } // Array buffers have unknown contents and thus no type can be inferred. if (isArrayBuffer(this.body)) { return null; } // Technically, strings could be a form of JSON data, but it's safe enough // to assume they're plain strings. if (typeof this.body === 'string') { return 'text/plain'; } // `HttpUrlEncodedParams` has its own content-type. if (this.body instanceof HttpParams) { return 'application/x-www-form-urlencoded;charset=UTF-8'; } // Arrays, objects, and numbers will be encoded as JSON. if (typeof this.body === 'object' || typeof this.body === 'number' || Array.isArray(this.body)) { return 'application/json'; } // No type could be inferred. return null; } /** * @param {?=} update * @return {?} */ clone(update = {}) { // For method, url, and responseType, take the current value unless // it is overridden in the update hash. /** @type {?} */ const method = update.method || this.method; /** @type {?} */ const url = update.url || this.url; /** @type {?} */ const responseType = update.responseType || this.responseType; // The body is somewhat special - a `null` value in update.body means // whatever current body is present is being overridden with an empty // body, whereas an `undefined` value in update.body implies no // override. /** @type {?} */ const body = (update.body !== undefined) ? update.body : this.body; // Carefully handle the boolean options to differentiate between // `false` and `undefined` in the update args. /** @type {?} */ const withCredentials = (update.withCredentials !== undefined) ? update.withCredentials : this.withCredentials; /** @type {?} */ const reportProgress = (update.reportProgress !== undefined) ? update.reportProgress : this.reportProgress; // Headers and params may be appended to if `setHeaders` or // `setParams` are used. /** @type {?} */ let headers = update.headers || this.headers; /** @type {?} */ let params = update.params || this.params; // Check whether the caller has asked to add headers. if (update.setHeaders !== undefined) { // Set every requested header. headers = Object.keys(update.setHeaders) .reduce((/** * @param {?} headers * @param {?} name * @return {?} */ (headers, name) => headers.set(name, (/** @type {?} */ (update.setHeaders))[name])), headers); } // Check whether the caller has asked to set params. if (update.setParams) { // Set every requested param. params = Object.keys(update.setParams) .reduce((/** * @param {?} params * @param {?} param * @return {?} */ (params, param) => params.set(param, (/** @type {?} */ (update.setParams))[param])), params); } // Finally, construct the new HttpRequest using the pieces from above. return new HttpRequest(method, url, body, { params, headers, reportProgress, responseType, withCredentials, }); } } if (false) { /** * The request body, or `null` if one isn't set. * * Bodies are not enforced to be immutable, as they can include a reference to any * user-defined data type. However, interceptors should take care to preserve * idempotence by treating them as such. * @type {?} */ HttpRequest.prototype.body; /** * Outgoing headers for this request. * @type {?} */ HttpRequest.prototype.headers; /** * Whether this request should be made in a way that exposes progress events. * * Progress events are expensive (change detection runs on each event) and so * they should only be requested if the consumer intends to monitor them. * @type {?} */ HttpRequest.prototype.reportProgress; /** * Whether this request should be sent with outgoing credentials (cookies). * @type {?} */ HttpRequest.prototype.withCredentials; /** * The expected response type of the server. * * This is used to parse the response appropriately before returning it to * the requestee. * @type {?} */ HttpRequest.prototype.responseType; /** * The outgoing HTTP request method. * @type {?} */ HttpRequest.prototype.method; /** * Outgoing URL parameters. * @type {?} */ HttpRequest.prototype.params; /** * The outgoing URL with all URL parameters set. * @type {?} */ HttpRequest.prototype.urlWithParams; /** @type {?} */ HttpRequest.prototype.url; } /** * @fileoverview added by tsickle * Generated from: packages/common/http/src/response.ts * @suppress {checkTypes,constantProperty,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc */ /** @enum {number} */ const HttpEventType = { /** * The request was sent out over the wire. */ Sent: 0, /** * An upload progress event was received. */ UploadProgress: 1, /** * The response status code and headers were received. */ ResponseHeader: 2, /** * A download progress event was received. */ DownloadProgress: 3, /** * The full response including the body was received. */ Response: 4, /** * A custom event from an interceptor or a backend. */ User: 5, }; HttpEventType[HttpEventType.Sent] = 'Sent'; HttpEventType[HttpEventType.UploadProgress] = 'UploadProgress'; HttpEventType[HttpEventType.ResponseHeader] = 'ResponseHeader'; HttpEventType[HttpEventType.DownloadProgress] = 'DownloadProgress'; HttpEventType[HttpEventType.Response] = 'Response'; HttpEventType[HttpEventType.User] = 'User'; /** * Base interface for progress events. * * \@publicApi * @record */ function HttpProgressEvent() { } if (false) { /** * Progress event type is either upload or download. * @type {?} */ HttpProgressEvent.prototype.type; /** * Number of bytes uploaded or downloaded. * @type {?} */ HttpProgressEvent.prototype.loaded; /** * Total number of bytes to upload or download. Depending on the request or * response, this may not be computable and thus may not be present. * @type {?|undefined} */ HttpProgressEvent.prototype.total; } /** * A download progress event. * * \@publicApi * @record */ function HttpDownloadProgressEvent() { } if (false) { /** @type {?} */ HttpDownloadProgressEvent.prototype.type; /** * The partial response body as downloaded so far. * * Only present if the responseType was `text`. * @type {?|undefined} */ HttpDownloadProgressEvent.prototype.partialText; } /** * An upload progress event. * * \@publicApi * @record */ function HttpUploadProgressEvent() { } if (false) { /** @type {?} */ HttpUploadProgressEvent.prototype.type; } /** * An event indicating that the request was sent to the server. Useful * when a request may be retried multiple times, to distinguish between * retries on the final event stream. * * \@publicApi * @record */ function HttpSentEvent() { } if (false) { /** @type {?} */ HttpSentEvent.prototype.type; } /** * A user-defined event. * * Grouping all custom events under this type ensures they will be handled * and forwarded by all implementations of interceptors. * * \@publicApi * @record * @template T */ function HttpUserEvent() { } if (false) { /** @type {?} */ HttpUserEvent.prototype.type; } /** * An error that represents a failed attempt to JSON.parse text coming back * from the server. * * It bundles the Error object with the actual response body that failed to parse. * * * @record */ function HttpJsonParseError() { } if (false) { /** @type {?} */ HttpJsonParseError.prototype.error; /** @type {?} */ HttpJsonParseError.prototype.text; } /** * Base class for both `HttpResponse` and `HttpHeaderResponse`. * * \@publicApi * @abstract */ class HttpResponseBase { /** * Super-constructor for all responses. * * The single parameter accepted is an initialization hash. Any properties * of the response passed there will override the default values. * @param {?} init * @param {?=} defaultStatus * @param {?=} defaultStatusText */ constructor(init, defaultStatus = 200, defaultStatusText = 'OK') { // If the hash has values passed, use them to initialize the response. // Otherwise use the default values. this.headers = init.headers || new HttpHeaders(); this.status = init.status !== undefined ? init.status : defaultStatus; this.statusText = init.statusText || defaultStatusText; this.url = init.url || null; // Cache the ok value to avoid defining a getter. this.ok = this.status >= 200 && this.status < 300; } } if (false) { /** * All response headers. * @type {?} */ HttpResponseBase.prototype.headers; /** * Response status code. * @type {?} */ HttpResponseBase.prototype.status; /** * Textual description of response status code. * * Do not depend on this. * @type {?} */ HttpResponseBase.prototype.statusText; /** * URL of the resource retrieved, or null if not available. * @type {?} */ HttpResponseBase.prototype.url; /** * Whether the status code falls in the 2xx range. * @type {?} */ HttpResponseBase.prototype.ok; /** * Type of the response, narrowed to either the full response or the header. * @type {?} */ HttpResponseBase.prototype.type; } /** * A partial HTTP response which only includes the status and header data, * but no response body. * * `HttpHeaderResponse` is a `HttpEvent` available on the response * event stream, only when progress events are requested. * * \@publicApi */ class HttpHeaderResponse extends HttpResponseBase { /** * Create a new `HttpHeaderResponse` with the given parameters. * @param {?=} init */ constructor(init = {}) { super(init); this.type = HttpEventType.ResponseHeader; } /** * Copy this `HttpHeaderResponse`, overriding its contents with the * given parameter hash. * @param {?=} update * @return {?} */ clone(update = {}) { // Perform a straightforward initialization of the new HttpHeaderResponse, // overriding the current parameters with new ones if given. return new HttpHeaderResponse({ headers: update.headers || this.headers, status: update.status !== undefined ? update.status : this.status, statusText: update.statusText || this.statusText, url: update.url || this.url || undefined, }); } } if (false) { /** @type {?} */ HttpHeaderResponse.prototype.type; } /** * A full HTTP response, including a typed response body (which may be `null` * if one was not returned). * * `HttpResponse` is a `HttpEvent` available on the response event * stream. * * \@publicApi * @template T */ class HttpResponse extends HttpResponseBase { /** * Construct a new `HttpResponse`. * @param {?=} init */ constructor(init = {}) { super(init); this.type = HttpEventType.Response; this.body = init.body !== undefined ? init.body : null; } /** * @param {?=} update * @return {?} */ clone(update = {}) { return new HttpResponse({ body: (update.body !== undefined) ? update.body : this.body, headers: update.headers || this.headers, status: (update.status !== undefined) ? update.status : this.status, statusText: update.statusText || this.statusText, url: update.url || this.url || undefined, }); } } if (false) { /** * The response body, or `null` if one was not returned. * @type {?} */ HttpResponse.prototype.body; /** @type {?} */ HttpResponse.prototype.type; } /** * A response that represents an error or failure, either from a * non-successful HTTP status, an error while executing the request, * or some other failure which occurred during the parsing of the response. * * Any error returned on the `Observable` response stream will be * wrapped in an `HttpErrorResponse` to provide additional context about * the state of the HTTP layer when the error occurred. The error property * will contain either a wrapped Error object or the error response returned * from the server. * * \@publicApi */ class HttpErrorResponse extends HttpResponseBase { /** * @param {?} init */ constructor(init) { // Initialize with a default status of 0 / Unknown Error. super(init, 0, 'Unknown Error'); this.name = 'HttpErrorResponse'; /** * Errors are never okay, even when the status code is in the 2xx success range. */ this.ok = false; // If the response was successful, then this was a parse error. Otherwise, it was // a protocol-level failure of some sort. Either the request failed in transit // or the server returned an unsuccessful status code. if (this.status >= 200 && this.status < 300) { this.message = `Http failure during parsing for ${init.url || '(unknown url)'}`; } else { this.message = `Http failure response for ${init.url || '(unknown url)'}: ${init.status} ${init.statusText}`; } this.error = init.error || null; } } if (false) { /** @type {?} */ HttpErrorResponse.prototype.name; /** @type {?} */ HttpErrorResponse.prototype.message; /** @type {?} */ HttpErrorResponse.prototype.error; /** * Errors are never okay, even when the status code is in the 2xx success range. * @type {?} */ HttpErrorResponse.prototype.ok; } /** * @fileoverview added by tsickle * Generated from: packages/common/http/src/client.ts * @suppress {checkTypes,constantProperty,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc */ /** * Constructs an instance of `HttpRequestOptions` from a source `HttpMethodOptions` and * the given `body`. This function clones the object and adds the body. * * Note that the `responseType` *options* value is a String that identifies the * single data type of the response. * A single overload version of the method handles each response type. * The value of `responseType` cannot be a union, as the combined signature could imply. * * @template T * @param {?} options * @param {?} body * @return {?} */ function addBody(options, body) { return { body, headers: options.headers, observe: options.observe, params: options.params, reportProgress: options.reportProgress, responseType: options.responseType, withCredentials: options.withCredentials, }; } /** * Performs HTTP requests. * This service is available as an injectable class, with methods to perform HTTP requests. * Each request method has multiple signatures, and the return type varies based on * the signature that is called (mainly the values of `observe` and `responseType`). * * Note that the `responseType` *options* value is a String that identifies the * single data type of the response. * A single overload version of the method handles each response type. * The value of `responseType` cannot be a union, as the combined signature could imply. * * \@usageNotes * Sample HTTP requests for the [Tour of Heroes](/tutorial/toh-pt0) application. * * ### HTTP Request Example * * ``` * // GET heroes whose name contains search term * searchHeroes(term: string): observable{ * * const params = new HttpParams({fromString: 'name=term'}); * return this.httpClient.request('GET', this.heroesUrl, {responseType:'json', params}); * } * ``` * ### JSONP Example * ``` * requestJsonp(url, callback = 'callback') { * return this.httpClient.jsonp(this.heroesURL, callback); * } * ``` * * ### PATCH Example * ``` * // PATCH one of the heroes' name * patchHero (id: number, heroName: string): Observable<{}> { * const url = `${this.heroesUrl}/${id}`; // PATCH api/heroes/42 * return this.httpClient.patch(url, {name: heroName}, httpOptions) * .pipe(catchError(this.handleError('patchHero'))); * } * ``` * * @see [HTTP Guide](guide/http) * * \@publicApi */ class HttpClient { /** * @param {?} handler */ constructor(handler) { this.handler = handler; } /** * Constructs an observable for a generic HTTP request that, when subscribed, * fires the request through the chain of registered interceptors and on to the * server. * * You can pass an `HttpRequest` directly as the only parameter. In this case, * the call returns an observable of the raw `HttpEvent` stream. * * Alternatively you can pass an HTTP method as the first parameter, * a URL string as the second, and an options hash containing the request body as the third. * See `addBody()`. In this case, the specified `responseType` and `observe` options determine the * type of returned observable. * * The `responseType` value determines how a successful response body is parsed. * * If `responseType` is the default `json`, you can pass a type interface for the resulting * object as a type parameter to the call. * * The `observe` value determines the return type, according to what you are interested in * observing. * * An `observe` value of events returns an observable of the raw `HttpEvent` stream, including * progress events by default. * * An `observe` value of response returns an observable of `HttpResponse`, * where the `T` parameter depends on the `responseType` and any optionally provided type * parameter. * * An `observe` value of body returns an observable of `` with the same `T` body type. * * @param {?} first * @param {?=} url * @param {?=} options * @return {?} */ request(first, url, options = {}) { /** @type {?} */ let req; // First, check whether the primary argument is an instance of `HttpRequest`. if (first instanceof HttpRequest) { // It is. The other arguments must be undefined (per the signatures) and can be // ignored. req = first; } else { // It's a string, so it represents a URL. Construct a request based on it, // and incorporate the remaining arguments (assuming `GET` unless a method is // provided. // Figure out the headers. /** @type {?} */ let headers = undefined; if (options.headers instanceof HttpHeaders) { headers = options.headers; } else { headers = new HttpHeaders(options.headers); } // Sort out parameters. /** @type {?} */ let params = undefined; if (!!options.params) { if (options.params instanceof HttpParams) { params = options.params; } else { params = new HttpParams((/** @type {?} */ ({ fromObject: options.params }))); } } // Construct the request. req = new HttpRequest(first, (/** @type {?} */ (url)), (options.body !== undefined ? options.body : null), { headers, params, reportProgress: options.reportProgress, // By default, JSON is assumed to be returned for all calls. responseType: options.responseType || 'json', withCredentials: options.withCredentials, }); } // Start with an Observable.of() the initial request, and run the handler (which // includes all interceptors) inside a concatMap(). This way, the handler runs // inside an Observable chain, which causes interceptors to be re-run on every // subscription (this also makes retries re-run the handler, including interceptors). /** @type {?} */ const events$ = of(req).pipe(concatMap((/** * @param {?} req * @return {?} */ (req) => this.handler.handle(req)))); // If coming via the API signature which accepts a previously constructed HttpRequest, // the only option is to get the event stream. Otherwise, return the event stream if // that is what was requested. if (first instanceof HttpRequest || options.observe === 'events') { return events$; } // The requested stream contains either the full response or the body. In either // case, the first step is to filter the event stream to extract a stream of // responses(s). /** @type {?} */ const res$ = (/** @type {?} */ (events$.pipe(filter((/** * @param {?} event * @return {?} */ (event) => event instanceof HttpResponse))))); // Decide which stream to return. switch (options.observe || 'body') { case 'body': // The requested stream is the body. Map the response stream to the response // body. This could be done more simply, but a misbehaving interceptor might // transform the response body into a different format and ignore the requested // responseType. Guard against this by validating that the response is of the // requested type. switch (req.responseType) { case 'arraybuffer': return res$.pipe(map((/** * @param {?} res * @return {?} */ (res) => { // Validate that the body is an ArrayBuffer. if (res.body !== null && !(res.body instanceof ArrayBuffer)) { throw new Error('Response is not an ArrayBuffer.'); } return res.body; }))); case 'blob': return res$.pipe(map((/** * @param {?} res * @return {?} */ (res) => { // Validate that the body is a Blob. if (res.body !== null && !(res.body instanceof Blob)) { throw new Error('Response is not a Blob.'); } return res.body; }))); case 'text': return res$.pipe(map((/** * @param {?} res * @return {?} */ (res) => { // Validate that the body is a string. if (res.body !== null && typeof res.body !== 'string') { throw new Error('Response is not a string.'); } return res.body; }))); case 'json': default: // No validation needed for JSON responses, as they can be of any type. return res$.pipe(map((/** * @param {?} res * @return {?} */ (res) => res.body))); } case 'response': // The response stream was requested directly, so return it. return res$; default: // Guard against new future observe types being added. throw new Error(`Unreachable: unhandled observe type ${options.observe}}`); } } /** * Constructs an observable that, when subscribed, causes the configured * `DELETE` request to execute on the server. See the individual overloads for * details on the return type. * * @param {?} url The endpoint URL. * @param {?=} options The HTTP options to send with the request. * * @return {?} */ delete(url, options = {}) { return this.request('DELETE', url, (/** @type {?} */ (options))); } /** * Constructs an observable that, when subscribed, causes the configured * `GET` request to execute on the server. See the individual overloads for * details on the return type. * @param {?} url * @param {?=} options * @return {?} */ get(url, options = {}) { return this.request('GET', url, (/** @type {?} */ (options))); } /** * Constructs an observable that, when subscribed, causes the configured * `HEAD` request to execute on the server. The `HEAD` method returns * meta information about the resource without transferring the * resource itself. See the individual overloads for * details on the return type. * @param {?} url * @param {?=} options * @return {?} */ head(url, options = {}) { return this.request('HEAD', url, (/** @type {?} */ (options))); } /** * Constructs an `Observable` that, when subscribed, causes a request with the special method * `JSONP` to be dispatched via the interceptor pipeline. * The [JSONP pattern](https://en.wikipedia.org/wiki/JSONP) works around limitations of certain * API endpoints that don't support newer, * and preferable [CORS](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS) protocol. * JSONP treats the endpoint API as a JavaScript file and tricks the browser to process the * requests even if the API endpoint is not located on the same domain (origin) as the client-side * application making the request. * The endpoint API must support JSONP callback for JSONP requests to work. * The resource API returns the JSON response wrapped in a callback function. * You can pass the callback function name as one of the query parameters. * Note that JSONP requests can only be used with `GET` requests. * * @template T * @param {?} url The resource URL. * @param {?} callbackParam The callback function name. * * @return {?} */ jsonp(url, callbackParam) { return this.request('JSONP', url, { params: new HttpParams().append(callbackParam, 'JSONP_CALLBACK'), observe: 'body', responseType: 'json', }); } /** * Constructs an `Observable` that, when subscribed, causes the configured * `OPTIONS` request to execute on the server. This method allows the client * to determine the supported HTTP methods and other capabilites of an endpoint, * without implying a resource action. See the individual overloads for * details on the return type. * @param {?} url * @param {?=} options * @return {?} */ options(url, options = {}) { return this.request('OPTIONS', url, (/** @type {?} */ (options))); } /** * Constructs an observable that, when subscribed, causes the configured * `PATCH` request to execute on the server. See the individual overloads for * details on the return type. * @param {?} url * @param {?} body * @param {?=} options * @return {?} */ patch(url, body, options = {}) { return this.request('PATCH', url, addBody(options, body)); } /** * Constructs an observable that, when subscribed, causes the configured * `POST` request to execute on the server. The server responds with the location of * the replaced resource. See the individual overloads for * details on the return type. * @param {?} url * @param {?} body * @param {?=} options * @return {?} */ post(url, body, options = {}) { return this.request('POST', url, addBody(options, body)); } /** * Constructs an observable that, when subscribed, causes the configured * `PUT` request to execute on the server. The `PUT` method replaces an existing resource * with a new set of values. * See the individual overloads for details on the return type. * @param {?} url * @param {?} body * @param {?=} options * @return {?} */ put(url, body, options = {}) { return this.request('PUT', url, addBody(options, body)); } } HttpClient.decorators = [ { type: Injectable } ]; /** @nocollapse */ HttpClient.ctorParameters = () => [ { type: HttpHandler } ]; if (false) { /** * @type {?} * @private */ HttpClient.prototype.handler; } /** * @fileoverview added by tsickle * Generated from: packages/common/http/src/interceptor.ts * @suppress {checkTypes,constantProperty,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc */ /** * Intercepts and handles an `HttpRequest` or `HttpResponse`. * * Most interceptors transform the outgoing request before passing it to the * next interceptor in the chain, by calling `next.handle(transformedReq)`. * An interceptor may transform the * response event stream as well, by applying additional RxJS operators on the stream * returned by `next.handle()`. * * More rarely, an interceptor may handle the request entirely, * and compose a new event stream instead of invoking `next.handle()`. This is an * acceptable behavior, but keep in mind that further interceptors will be skipped entirely. * * It is also rare but valid for an interceptor to return multiple responses on the * event stream for a single request. * * \@publicApi * * @see [HTTP Guide](guide/http#intercepting-requests-and-responses) * * \@usageNotes * * To use the same instance of `HttpInterceptors` for the entire app, import the `HttpClientModule` * only in your `AppModule`, and add the interceptors to the root application injector . * If you import `HttpClientModule` multiple times across different modules (for example, in lazy * loading modules), each import creates a new copy of the `HttpClientModule`, which overwrites the interceptors * provided in the root module. * * @record */ function HttpInterceptor() { } if (false) { /** * Identifies and handles a given HTTP request. * @param {?} req The outgoing request object to handle. * @param {?} next The next interceptor in the chain, or the backend * if no interceptors remain in the chain. * @return {?} An observable of the event stream. */ HttpInterceptor.prototype.intercept = function (req, next) { }; } /** * `HttpHandler` which applies an `HttpInterceptor` to an `HttpRequest`. * * */ class HttpInterceptorHandler { /** * @param {?} next * @param {?} interceptor */ constructor(next, interceptor) { this.next = next; this.interceptor = interceptor; } /** * @param {?} req * @return {?} */ handle(req) { return this.interceptor.intercept(req, this.next); } } if (false) { /** * @type {?} * @private */ HttpInterceptorHandler.prototype.next; /** * @type {?} * @private */ HttpInterceptorHandler.prototype.interceptor; } /** * A multi-provider token that represents the array of registered * `HttpInterceptor` objects. * * \@publicApi * @type {?} */ const HTTP_INTERCEPTORS = new InjectionToken('HTTP_INTERCEPTORS'); class NoopInterceptor { /** * @param {?} req * @param {?} next * @return {?} */ intercept(req, next) { return next.handle(req); } } NoopInterceptor.decorators = [ { type: Injectable } ]; /** * @fileoverview added by tsickle * Generated from: packages/common/http/src/jsonp.ts * @suppress {checkTypes,constantProperty,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc */ // Every request made through JSONP needs a callback name that's unique across the // whole page. Each request is assigned an id and the callback name is constructed // from that. The next id to be assigned is tracked in a global variable here that // is shared among all applications on the page. /** @type {?} */ let nextRequestId = 0; // Error text given when a JSONP script is injected, but doesn't invoke the callback // passed in its URL. /** @type {?} */ const JSONP_ERR_NO_CALLBACK = 'JSONP injected script did not invoke callback.'; // Error text given when a request is passed to the JsonpClientBackend that doesn't // have a request method JSONP. /** @type {?} */ const JSONP_ERR_WRONG_METHOD = 'JSONP requests must use JSONP request method.'; /** @type {?} */ const JSONP_ERR_WRONG_RESPONSE_TYPE = 'JSONP requests must use Json response type.'; /** * DI token/abstract type representing a map of JSONP callbacks. * * In the browser, this should always be the `window` object. * * * @abstract */ class JsonpCallbackContext { } /** * Processes an `HttpRequest` with the JSONP method, * by performing JSONP style requests. * @see `HttpHandler` * @see `HttpXhrBackend` * * \@publicApi */ class JsonpClientBackend { /** * @param {?} callbackMap * @param {?} document */ constructor(callbackMap, document) { this.callbackMap = callbackMap; this.document = document; } /** * Get the name of the next callback method, by incrementing the global `nextRequestId`. * @private * @return {?} */ nextCallback() { return `ng_jsonp_callback_${nextRequestId++}`; } /** * Processes a JSONP request and returns an event stream of the results. * @param {?} req The request object. * @return {?} An observable of the response events. * */ handle(req) { // Firstly, check both the method and response type. If either doesn't match // then the request was improperly routed here and cannot be handled. if (req.method !== 'JSONP') { throw new Error(JSONP_ERR_WRONG_METHOD); } else if (req.responseType !== 'json') { throw new Error(JSONP_ERR_WRONG_RESPONSE_TYPE); } // Everything else happens inside the Observable boundary. return new Observable((/** * @param {?} observer * @return {?} */ (observer) => { // The first step to make a request is to generate the callback name, and replace the // callback placeholder in the URL with the name. Care has to be taken here to ensure // a trailing &, if matched, gets inserted back into the URL in the correct place. /** @type {?} */ const callback = this.nextCallback(); /** @type {?} */ const url = req.urlWithParams.replace(/=JSONP_CALLBACK(&|$)/, `=${callback}$1`); // Construct the