/** * @license * Copyright Google LLC 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 */ const xhr2: any = require('xhr2'); import {Injectable, Injector, Provider} from '@angular/core'; import {PlatformLocation} from '@angular/common'; import {HttpEvent, HttpRequest, HttpHandler, HttpBackend, XhrFactory, ɵHttpInterceptingHandler as HttpInterceptingHandler} from '@angular/common/http'; import {Observable, Observer, Subscription} from 'rxjs'; // @see https://www.w3.org/Protocols/HTTP/1.1/draft-ietf-http-v11-spec-01#URI-syntax const isAbsoluteUrl = /^[a-zA-Z\-\+.]+:\/\//; @Injectable() export class ServerXhr implements XhrFactory { build(): XMLHttpRequest { return new xhr2.XMLHttpRequest(); } } export abstract class ZoneMacroTaskWrapper { wrap(request: S): Observable { return new Observable((observer: Observer) => { let task: Task = null!; let scheduled: boolean = false; let sub: Subscription|null = null; let savedResult: any = null; let savedError: any = null; const scheduleTask = (_task: Task) => { task = _task; scheduled = true; const delegate = this.delegate(request); sub = delegate.subscribe( res => savedResult = res, err => { if (!scheduled) { throw new Error( 'An http observable was completed twice. This shouldn\'t happen, please file a bug.'); } savedError = err; scheduled = false; task.invoke(); }, () => { if (!scheduled) { throw new Error( 'An http observable was completed twice. This shouldn\'t happen, please file a bug.'); } scheduled = false; task.invoke(); }); }; const cancelTask = (_task: Task) => { if (!scheduled) { return; } scheduled = false; if (sub) { sub.unsubscribe(); sub = null; } }; const onComplete = () => { if (savedError !== null) { observer.error(savedError); } else { observer.next(savedResult); observer.complete(); } }; // MockBackend for Http is synchronous, which means that if scheduleTask is by // scheduleMacroTask, the request will hit MockBackend and the response will be // sent, causing task.invoke() to be called. const _task = Zone.current.scheduleMacroTask( 'ZoneMacroTaskWrapper.subscribe', onComplete, {}, () => null, cancelTask); scheduleTask(_task); return () => { if (scheduled && task) { task.zone.cancelTask(task); scheduled = false; } if (sub) { sub.unsubscribe(); sub = null; } }; }); } protected abstract delegate(request: S): Observable; } export class ZoneClientBackend extends ZoneMacroTaskWrapper, HttpEvent> implements HttpBackend { constructor(private backend: HttpBackend, private platformLocation: PlatformLocation) { super(); } handle(request: HttpRequest): Observable> { const {href, protocol, hostname} = this.platformLocation; if (!isAbsoluteUrl.test(request.url) && href !== '/') { const baseHref = this.platformLocation.getBaseHrefFromDOM() || href; const urlPrefix = `${protocol}//${hostname}`; const baseUrl = new URL(baseHref, urlPrefix); const url = new URL(request.url, baseUrl); return this.wrap(request.clone({url: url.toString()})); } return this.wrap(request); } protected delegate(request: HttpRequest): Observable> { return this.backend.handle(request); } } export function zoneWrappedInterceptingHandler( backend: HttpBackend, injector: Injector, platformLocation: PlatformLocation) { const realBackend: HttpBackend = new HttpInterceptingHandler(backend, injector); return new ZoneClientBackend(realBackend, platformLocation); } export const SERVER_HTTP_PROVIDERS: Provider[] = [ {provide: XhrFactory, useClass: ServerXhr}, { provide: HttpHandler, useFactory: zoneWrappedInterceptingHandler, deps: [HttpBackend, Injector, PlatformLocation] } ];