import Task from './async/Task'; import has from './has'; import { Handle } from './interfaces'; import Promise from './promise'; import Registry, { Test } from './registry'; import load from './load'; import { ParamList } from './urlsearchparams'; declare var require: any; export class FilterRegistry extends Registry { register(test: string | RegExp | RequestFilterTest, value: RequestFilter, first?: boolean): Handle { let entryTest: Test; if (typeof test === 'string') { entryTest = (response, url, options) => { return test === url; }; } else if (test instanceof RegExp) { entryTest = (response, url, options) => { return test.test(url); }; } else { entryTest = test; } return super.register(entryTest, value, first); } } let defaultProvider: string = './request/xhr'; if (has('host-node')) { defaultProvider = './request/node'; } export class ProviderRegistry extends Registry { private _providerPromise: Promise; constructor() { super(); const deferRequest = (url: string, options?: RequestOptions): ResponsePromise => { let canceled = false; let actualResponse: ResponsePromise; return new Task>((resolve, reject) => { this._providerPromise.then(function (provider) { if (canceled) { return; } actualResponse = provider(url, options); actualResponse.then(resolve, reject); }); }, function () { if (!canceled) { canceled = true; } if (actualResponse) { actualResponse.cancel(); } }); }; // The first request to hit the default value will kick off the import of the default // provider. While that import is in-flight, subsequent requests will queue up while // waiting for the provider to be fulfilled. this._defaultValue = (url: string, options?: RequestOptions): ResponsePromise => { this._providerPromise = load(require, defaultProvider).then(([ providerModule ]: [ { default: RequestProvider } ]): RequestProvider => { this._defaultValue = providerModule.default; return providerModule.default; }); this._defaultValue = deferRequest; return deferRequest(url, options); }; } register(test: string | RegExp | RequestProviderTest, value: RequestProvider, first?: boolean): Handle { let entryTest: Test; if (typeof test === 'string') { entryTest = (url, options) => { return test === url; }; } else if (test instanceof RegExp) { entryTest = (url, options) => { return test.test(url); }; } else { entryTest = test; } return super.register(entryTest, value, first); } } /** * Request filters, which filter or modify responses. The default filter simply passes a response through unchanged. */ export const filterRegistry = new FilterRegistry(function (response: Response): Response { return response; }); /** * Request providers, which fulfill requests. */ export const providerRegistry = new ProviderRegistry(); export interface RequestError extends Error { response: Response; } export interface RequestFilter { (response: Response, url: string, options?: RequestOptions): T; } export interface RequestFilterTest extends Test { (response: Response, url: string, options?: RequestOptions): boolean; } export interface RequestOptions { auth?: string; cacheBust?: any; data?: any; handleAs?: string; headers?: { [name: string]: string; }; method?: string; password?: string; query?: string | ParamList; responseType?: string; timeout?: number; user?: string; } export interface RequestProvider { (url: string, options?: RequestOptions): ResponsePromise; } export interface RequestProviderTest extends Test { (url: string, options?: RequestOptions): boolean; } export interface Response { data: T; nativeResponse?: any; requestOptions: RequestOptions; statusCode: number; statusText?: string; url: string; getHeader(name: string): string; } /** * The task returned by a request, which will resolve to a Response */ export interface ResponsePromise extends Task> {} /** * Make a request, returning a Promise that will resolve or reject when the request completes. */ const request: { (url: string, options?: RequestOptions): ResponsePromise; delete(url: string, options?: RequestOptions): ResponsePromise; get(url: string, options?: RequestOptions): ResponsePromise; post(url: string, options?: RequestOptions): ResponsePromise; put(url: string, options?: RequestOptions): ResponsePromise; } = function request(url: string, options: RequestOptions = {}): ResponsePromise { const promise = providerRegistry.match(url, options)(url, options) .then(function (response: Response) { return Task.resolve(filterRegistry.match(response, url, options)(response, url, options)) .then(function (filterResponse: any) { response.data = filterResponse.data; return response; }); }); return promise; }; [ 'DELETE', 'GET', 'POST', 'PUT' ].forEach(function (method) { ( request)[method.toLowerCase()] = function (url: string, options: RequestOptions = {}): ResponsePromise { options = Object.create(options); options.method = method; return request(url, options); }; }); export default request; /** * Add a filter that automatically parses incoming JSON responses. */ filterRegistry.register( function (response: Response, url: string, options: RequestOptions) { return typeof response.data === 'string' && options.responseType === 'json'; }, function (response: Response, url: string, options: RequestOptions): Object { return { data: JSON.parse(response.data) }; } );