import {APICursorHttpResponse, APICursorHelper} from "../helpers/apiCursor.helper"; import {keys, merge, first, forEach, last, map, reject, split} from "lodash"; import {mc_log, MultichatCoreClientSettings} from '../settings'; export type QueryStringParam = { key: string, value: string }; export abstract class Resource { protected baseResource: string; // The base resource of this instance, e.g. '/chats/' protected _subResources: { [key: string]: any }; // A resource has sub resources, like a chat that has messages 'under' it protected resourceIdentifyingMetadata: { [key: string]: any }; // Metadata identifying an instance (key/value) that can be sent as a body in messages. protected resourceIdentifier: string | number; // The primary identifier used to identify an instance protected getSubresourceValue(obj: { [key: string]: any }, key: string) { return typeof obj[key] === "function" ? obj[key]() : obj[key]; } } class ResponseError extends Error { [key: string]: any; } export type SimpleDetailHttpResponse = { [key: string]: string, detail: string } export type HttpMethods = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE'; export interface HttpConf { method: HttpMethods; url: string; body: { [key: string]: any }; cursorId: string; } export interface MultichatHttpSettings { BASE_URL: string; DEBUG: boolean; } export let MultichatChatClientConf: { confPipes: Array, headers: { [key: string]: any } } = { confPipes: [], headers: {} }; export class BaseHttpResource extends Resource { protected cursors: { [key: string]: APICursorHelper } = {}; protected get confPipes() { return MultichatChatClientConf.confPipes; } protected get baseUrl() { return MultichatCoreClientSettings.BASE_URL } constructor() { super(); } protected registerConfPipe(pipe: Function) { this.confPipes.push(pipe); } /** * Wrap a HTTP call * @param conf * @return {{then: BaseHttpResource.then}} * @private */ protected _wrapHttp(conf: HttpConf | any): Promise { let that = this; if (!('headers' in conf)) conf['headers'] = {'Content-Type': 'application/json'}; this.confPipes.forEach(function (pipe: Function) { pipe(conf); }); merge(conf.headers, MultichatChatClientConf.headers); conf['credentials'] = 'include'; if ('body' in conf) conf['body'] = JSON.stringify(conf['body']); function checkStatus(response: Response) { if (response.ok) { return Promise.resolve(response); } else { let error = new ResponseError(response.statusText); error['response'] = response; return Promise.reject(error); } } return window.fetch(this.baseUrl + conf['url'], conf).then(checkStatus).then((r: Response) => r.json()).then(function (response: APICursorHttpResponse | any) { if (that.isPaginatedResponse(response) && typeof conf.cursorId !== "undefined") { that.cursors[conf.cursorId] = new APICursorHelper(response); } return response; }); }; /** * @param response * @returns {boolean} */ private isPaginatedResponse(response: APICursorHttpResponse): boolean { return response.current !== undefined; } public decodeQuerystring = function (queryString: string) { let qs = reject(split(split(queryString, '?')[1], '&'), ""), ret: { [key: string]: any } = {}; forEach(qs, function (o: string) { let l = split(o, '='); ret[l[0]] = l[1]; }); return ret }; public encodeQuerystring(qs: string | QueryStringParam[] | { [key: string]: any }): string | undefined { let convertKeyValueMapToString = (_qs: QueryStringParam[]): string => { let _qs_base = '?'; return last(map(_qs, function (v: QueryStringParam) { return _qs_base += ((first(_qs) === v ? '' : '&') + (v['key'] + '=' + v['value'])) })); }; qs = >qs; // Appears typescript will in no way accept type casts for length. This is a hack. if (qs.length === map(qs, 'key').length) { return convertKeyValueMapToString(qs); } else if (keys(qs).length > 0) { let _qs = []; forEach(<{ [key: string]: any }>qs, function (v: string, k: string) { _qs.push({key: k, value: v}); }); return convertKeyValueMapToString(_qs); } else if (typeof qs === 'string') { return qs; } else { return undefined; } }; public extractQuerystring(url: string) { return split(url, '?')[1] }; public async list(id?: string, url?: string) { return this._wrapHttp({ method: 'GET', url: url ? url : (this.baseResource + (id ? id : '')) }); }; public post(url?: string, body?: object, queryParam?: string) { return this.request((url ? url : this.baseResource), queryParam, "POST", body); } public request(url?: string, queryParams?: string | QueryStringParam[], method?: HttpMethods, body?: object, cursorId?: string,) { return this._wrapHttp({ method: method || 'GET', url: (url ? url : this.baseResource) + (queryParams ? this.encodeQuerystring(queryParams) : ''), body: body, cursorId: cursorId }); } public getUrl(url: string, cursorId?: string) { return this.request(url, undefined, "GET", {}, cursorId) } /** * Generic function to do a request() call to a subresource of this class. */ public requestSubResource(resource: string, url?: string, queryParams?: any, method?: HttpMethods, body?: object) { let conf = this._subResources[resource]; return this.request(this.getSubresourceValue(conf, 'url') + (url ? url : ''), queryParams, method ? method : "GET", body ? body : conf.body, resource); } }