2017-02-10 17:00:27 -08:00
|
|
|
/**
|
|
|
|
* @license
|
2020-05-19 12:08:49 -07:00
|
|
|
* Copyright Google LLC All Rights Reserved.
|
2017-02-10 17:00:27 -08:00
|
|
|
*
|
|
|
|
* 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
|
|
|
|
*/
|
|
|
|
|
2019-03-19 19:41:12 -05:00
|
|
|
|
2017-02-10 17:00:27 -08:00
|
|
|
const xhr2: any = require('xhr2');
|
|
|
|
|
2019-03-19 19:41:12 -05:00
|
|
|
import {Injectable, Injector, Provider} from '@angular/core';
|
2020-05-12 10:05:18 -05:00
|
|
|
import {DOCUMENT} from '@angular/common';
|
2019-03-19 19:41:12 -05:00
|
|
|
import {HttpEvent, HttpRequest, HttpHandler, HttpBackend, XhrFactory, ɵHttpInterceptingHandler as HttpInterceptingHandler} from '@angular/common/http';
|
2018-02-27 17:06:06 -05:00
|
|
|
import {Observable, Observer, Subscription} from 'rxjs';
|
2017-02-10 17:00:27 -08:00
|
|
|
|
2020-05-12 10:05:18 -05:00
|
|
|
// @see https://www.w3.org/Protocols/HTTP/1.1/draft-ietf-http-v11-spec-01#URI-syntax
|
|
|
|
const isAbsoluteUrl = /^[a-zA-Z\-\+.]+:\/\//;
|
|
|
|
const FORWARD_SLASH = '/';
|
|
|
|
|
2017-02-10 17:00:27 -08:00
|
|
|
@Injectable()
|
2019-03-19 19:41:12 -05:00
|
|
|
export class ServerXhr implements XhrFactory {
|
2020-04-13 16:40:21 -07:00
|
|
|
build(): XMLHttpRequest {
|
|
|
|
return new xhr2.XMLHttpRequest();
|
|
|
|
}
|
2017-02-10 17:00:27 -08:00
|
|
|
}
|
|
|
|
|
2017-03-22 17:13:24 -07:00
|
|
|
export abstract class ZoneMacroTaskWrapper<S, R> {
|
|
|
|
wrap(request: S): Observable<R> {
|
|
|
|
return new Observable((observer: Observer<R>) => {
|
2020-04-13 16:40:21 -07:00
|
|
|
let task: Task = null!;
|
2017-02-10 17:00:27 -08:00
|
|
|
let scheduled: boolean = false;
|
2017-03-24 09:59:41 -07:00
|
|
|
let sub: Subscription|null = null;
|
2017-02-10 17:00:27 -08:00
|
|
|
let savedResult: any = null;
|
|
|
|
let savedError: any = null;
|
|
|
|
|
|
|
|
const scheduleTask = (_task: Task) => {
|
|
|
|
task = _task;
|
|
|
|
scheduled = true;
|
|
|
|
|
2017-03-22 17:13:24 -07:00
|
|
|
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();
|
|
|
|
});
|
2017-02-10 17:00:27 -08:00
|
|
|
};
|
|
|
|
|
|
|
|
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();
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2017-03-22 17:13:24 -07:00
|
|
|
// MockBackend for Http is synchronous, which means that if scheduleTask is by
|
2017-02-10 17:00:27 -08:00
|
|
|
// scheduleMacroTask, the request will hit MockBackend and the response will be
|
|
|
|
// sent, causing task.invoke() to be called.
|
|
|
|
const _task = Zone.current.scheduleMacroTask(
|
2017-03-22 17:13:24 -07:00
|
|
|
'ZoneMacroTaskWrapper.subscribe', onComplete, {}, () => null, cancelTask);
|
2017-02-10 17:00:27 -08:00
|
|
|
scheduleTask(_task);
|
|
|
|
|
|
|
|
return () => {
|
|
|
|
if (scheduled && task) {
|
|
|
|
task.zone.cancelTask(task);
|
|
|
|
scheduled = false;
|
|
|
|
}
|
|
|
|
if (sub) {
|
|
|
|
sub.unsubscribe();
|
|
|
|
sub = null;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2017-03-22 17:13:24 -07:00
|
|
|
protected abstract delegate(request: S): Observable<R>;
|
|
|
|
}
|
|
|
|
|
|
|
|
export class ZoneClientBackend extends
|
2017-07-18 12:45:47 -07:00
|
|
|
ZoneMacroTaskWrapper<HttpRequest<any>, HttpEvent<any>> implements HttpBackend {
|
2020-05-12 10:05:18 -05:00
|
|
|
constructor(private backend: HttpBackend, private doc: Document) {
|
2020-04-13 16:40:21 -07:00
|
|
|
super();
|
|
|
|
}
|
2017-03-22 17:13:24 -07:00
|
|
|
|
2020-04-13 16:40:21 -07:00
|
|
|
handle(request: HttpRequest<any>): Observable<HttpEvent<any>> {
|
2020-05-12 10:05:18 -05:00
|
|
|
const href = this.doc.location.href;
|
|
|
|
if (!isAbsoluteUrl.test(request.url) && href) {
|
|
|
|
const urlParts = Array.from(request.url);
|
|
|
|
if (request.url[0] === FORWARD_SLASH && href[href.length - 1] === FORWARD_SLASH) {
|
|
|
|
urlParts.shift();
|
|
|
|
} else if (request.url[0] !== FORWARD_SLASH && href[href.length - 1] !== FORWARD_SLASH) {
|
|
|
|
urlParts.splice(0, 0, FORWARD_SLASH);
|
|
|
|
}
|
|
|
|
return this.wrap(request.clone({url: href + urlParts.join('')}));
|
|
|
|
}
|
2020-04-13 16:40:21 -07:00
|
|
|
return this.wrap(request);
|
|
|
|
}
|
2017-03-22 17:13:24 -07:00
|
|
|
|
2017-07-18 12:45:47 -07:00
|
|
|
protected delegate(request: HttpRequest<any>): Observable<HttpEvent<any>> {
|
2017-03-22 17:13:24 -07:00
|
|
|
return this.backend.handle(request);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-05-12 10:05:18 -05:00
|
|
|
export function zoneWrappedInterceptingHandler(
|
|
|
|
backend: HttpBackend, injector: Injector, doc: Document) {
|
2018-05-31 10:35:51 -07:00
|
|
|
const realBackend: HttpBackend = new HttpInterceptingHandler(backend, injector);
|
2020-05-12 10:05:18 -05:00
|
|
|
return new ZoneClientBackend(realBackend, doc);
|
2017-03-22 17:13:24 -07:00
|
|
|
}
|
|
|
|
|
2017-02-10 17:00:27 -08:00
|
|
|
export const SERVER_HTTP_PROVIDERS: Provider[] = [
|
2020-05-12 10:05:18 -05:00
|
|
|
{provide: XhrFactory, useClass: ServerXhr}, {
|
|
|
|
provide: HttpHandler,
|
|
|
|
useFactory: zoneWrappedInterceptingHandler,
|
|
|
|
deps: [HttpBackend, Injector, DOCUMENT]
|
|
|
|
}
|
2017-02-10 17:00:27 -08:00
|
|
|
];
|