/** * @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 {HttpEvent, HttpRequest, HttpHandler, HttpBackend, XhrFactory, ɵHttpInterceptingHandler as HttpInterceptingHandler} from '@angular/common/http'; import {Observable, Observer, Subscription} from 'rxjs'; @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) { super(); } handle(request: HttpRequest): Observable> { return this.wrap(request); } protected delegate(request: HttpRequest): Observable> { return this.backend.handle(request); } } export function zoneWrappedInterceptingHandler(backend: HttpBackend, injector: Injector) { const realBackend: HttpBackend = new HttpInterceptingHandler(backend, injector); return new ZoneClientBackend(realBackend); } export const SERVER_HTTP_PROVIDERS: Provider[] = [ {provide: XhrFactory, useClass: ServerXhr}, {provide: HttpHandler, useFactory: zoneWrappedInterceptingHandler, deps: [HttpBackend, Injector]} ];