UNPKG

@zocial-chat/sdk

Version:

Node.js SDK for Zocial.Chat. Application interface for server methods and message streams.

307 lines (271 loc) 9.88 kB
import { logger as Logger } from '../log' import { ILogger, ILoginResultAPI, IAPIRequest, IMessage, ICredentials } from '../../interfaces' import { Message } from '../message' import { EventEmitter } from 'tiny-events' import * as settings from '../settings'; /** Check for existing login */ // export function loggedIn () { // return (currentLogin !== null) // } /** * Prepend protocol (or put back if removed from env settings for driver) * Hard code endpoint prefix, because all syntax depends on this version */ // export const url = `${(host.indexOf('http') === -1) ? host.replace(/^(\/\/)?/, 'http://') : host}/api/v1/` /** Populate auth headers (from response data on login) */ // export function setAuth (authData: {authToken: string, userId: string}) { // client.defaults.headers.common['X-Auth-Token'] = authData.authToken // client.defaults.headers.common['X-User-Id'] = authData.userId // } // /** Clear headers so they can't be used without logging in again */ // export function clearHeaders () { // delete client.defaults.headers.common['X-Auth-Token'] // delete client.defaults.headers.common['X-User-Id'] // } // /** // * Login a user for further API calls // * Result should come back with a token, to authorise following requests. // * Use env default credentials, unless overridden by login arguments. // */ // export async function login (user: ICredentialsAPI = { username, password }) { // this.logger.info(`[API] Logging in ${user.username}`) // if (currentLogin !== null) { // this.logger.debug(`[API] Already logged in`) // if (currentLogin.username === user.username) return currentLogin.result // else await logout() // } // const result = (await this.post('login', user, false) as ILoginResultAPI) // if (result && result.data && result.data.authToken) { // currentLogin = { // result: result, // keep to return if login requested again for same user // username: user.username, // keep to compare with following login attempt // authToken: result.data.authToken, // userId: result.data.userId // } // setAuth(currentLogin) // this.logger.info(`[API] Logged in ID ${currentLogin.userId}`) // return result // } else { // throw new Error(`[API] Login failed for ${user.username}`) // } // } // /** Logout a user at end of API calls */ // export function logout () { // if (currentLogin === null) { // this.logger.debug(`[API] Already logged out`) // return Promise.resolve() // } // this.logger.info(`[API] Logging out ${ currentLogin.username }`) // return this.get('logout', null, true).then(() => { // clearHeaders() // currentLogin = null // }) // } export interface IClient { headers: any get (url: string, data: any, options?: any): Promise<any> post (url: string, data: any, options?: any): Promise<any> put (url: string, data: any, options?: any): Promise<any> delete (url: string, data: any, options?: any): Promise<any> } class Client implements IClient { host: string _headers: any = {} constructor ({ host = 'http://localhost:3000' }: any) { this.host = host } set headers (obj: any) { this._headers = obj } get headers (): any { return { 'Content-Type': 'application/json', ...settings.customHeaders, ...this._headers } } getHeaders (options?: any) { return options && options.customHeaders ? options.customHeaders : this.headers } getBody (data: any) { return data instanceof FormData ? data : JSON.stringify(data) } getSignal (options?: any): AbortSignal { return options && options.signal; } get (url: string, data: any, options?: any): Promise<any> { return fetch(`${this.host}/api/v1/${encodeURI(url)}?${this.getParams(data)}`, { method: 'GET', headers: this.getHeaders(options), signal: this.getSignal(options) }).then(this.handle) } post (url: string, data: any, options?: any): Promise<any> { return fetch(`${this.host}/api/v1/${encodeURI(url)}`, { method: 'POST', body: this.getBody(data), headers: this.getHeaders(options), signal: this.getSignal(options) }).then(this.handle) } put (url: string, data: any, options?: any): Promise<any> { return fetch(`${this.host}/api/v1/${encodeURI(url)}`, { method: 'PUT', body: this.getBody(data), headers: this.getHeaders(options), signal: this.getSignal(options) }).then(this.handle) } delete (url: string, data?: any, options?: any): Promise<any> { return fetch(`${this.host}/api/v1/${encodeURI(url)}`, { method: 'DELETE', body: this.getBody(data), headers: this.getHeaders(options), signal: this.getSignal(options) }).then(this.handle) } private async handle (r: any) { const { status } = r const data = await r.json() return { status, data } } private getParams (data: any) { return Object.keys(data).map(function (k) { return encodeURIComponent(k) + '=' + (typeof data[k] === 'object' ? encodeURIComponent(JSON.stringify(data[k])) : encodeURIComponent(data[k])) }).join('&') } } export const regExpSuccess = /(?!([45][0-9][0-9]))\d{3}/ /** * @module API * Provides a base client for handling requests with generic Zocial.Chat's REST API */ export default class Api extends EventEmitter { userId: string = '' logger: ILogger client: IClient currentLogin: { username: string, userId: string, authToken: string, result: ILoginResultAPI } | null = null controller: AbortController constructor ({ client, host, logger = Logger }: any) { super() this.client = client || new Client({ host } as any) this.logger = Logger this.controller = new AbortController(); } get username () { return this.currentLogin && this.currentLogin.username } loggedIn () { return Object.keys(this.currentLogin || {} as any).every((e: any) => e) } /** * Do a request to an API endpoint. * If it needs a token, login first (with defaults) to set auth headers. * @param method Request method GET | POST | PUT | DEL * @param endpoint The API endpoint (including version) e.g. `chat.update` * @param data Payload for POST request to endpoint * @param auth Require auth headers for endpoint, default true * @param ignore Allows certain matching error messages to not count as errors */ request = async ( method: 'POST' | 'GET' | 'PUT' | 'DELETE', endpoint: string, data: any = {}, auth: boolean = true, ignore?: RegExp, options?: any ) => { this.logger && this.logger.debug(`[API] ${ method } ${ endpoint }: ${ JSON.stringify(data) }`) try { if (auth && !this.loggedIn()) { throw new Error('') } const { signal } = this.controller; options = { ...options, signal }; let result switch (method) { case 'GET': result = await this.client.get(endpoint, data, options); break case 'PUT': result = await this.client.put(endpoint, data, options); break case 'DELETE': result = await this.client.delete(endpoint, data, options); break default: case 'POST': result = await this.client.post(endpoint, data, options); break } if (!result) throw new Error(`API ${ method } ${ endpoint } result undefined`) if (!this.success(result, ignore)) throw result this.logger && this.logger.debug(`[API] ${method} ${endpoint} result ${result.status}`) const hasDataInsideResult = result && !result.data return (method === 'DELETE') && hasDataInsideResult ? result : result.data } catch (err) { this.logger && this.logger.error(`[API] POST error(${ endpoint }): ${ JSON.stringify(err) }`) throw err } } /** Do a POST request to an API endpoint. */ post: IAPIRequest = (endpoint, data, auth, ignore, options = {}) => this.request('POST', endpoint, data, auth, ignore, options) /** Do a GET request to an API endpoint. */ get: IAPIRequest = (endpoint, data, auth, ignore, options = {}) => this.request('GET', endpoint, data, auth, ignore, options) /** Do a PUT request to an API endpoint. */ put: IAPIRequest = (endpoint, data, auth, ignore, options = {}) => this.request('PUT', endpoint, data, auth, ignore, options) /** Do a DELETE request to an API endpoint. */ del: IAPIRequest = (endpoint, data, auth, ignore, options = {}) => this.request('DELETE', endpoint, data, auth, ignore, options) /** Abort all current API requests. */ abort = (): void => this.controller.abort() /** Check result data for success, allowing override to ignore some errors */ success (result: any, ignore?: RegExp) { return ( typeof result.status === 'undefined' || (result.status && regExpSuccess.test(result.status)) || (result.status && ignore && ignore.test(result.status)) ) ? true : false } async login (credentials: ICredentials, args?: any): Promise<any> { const { data } = await this.post('login', { ...credentials, ...args }) this.userId = data.userId this.currentLogin = { username: data.me.username, userId: data.userId, authToken: data.authToken, result: data } this.client.headers = { 'X-Auth-Token': data.authToken, 'X-User-Id': data.userId } return data } async logout () { if (!this.currentLogin) { return null } const result = await this.post('logout', {}, true) this.userId = '' this.currentLogin = null return result } /** * Structure message content, optionally addressing to room ID. * Accepts message text string or a structured message object. */ prepareMessage ( content: string | IMessage, rid?: string, args?: any ): Message { return new Message(content, { rid, roomId: rid, ...args }) } }