UNPKG

@kikitrade/api-gateway-client

Version:

alibaba cloud open api gateway two way communication library

177 lines (147 loc) 5.72 kB
import {parse, UrlWithParsedQuery} from 'url'; import CryptoJS from 'crypto-js'; import {v4} from 'uuid'; import {ParsedUrlQuery} from "querystring"; const Content_Type_Form_Data = 'application/x-www-form-urlencoded; charset=utf-8'; const Content_Type_Json_Data = 'application/octet-stream; charset=utf-8'; const Accept_JSON = 'application/json; charset=utf-8'; let seq = 1; function seqInc() { return seq++; } type Stage = 'TEST' | 'RELEASE'; type WebSocketApiType = 'NOTIFY' | 'REGISTER' | 'UNREGISTER' | 'COMMON'; interface Header { [key: string]: any | any[]; } interface FormData { [key: string]: any; } class Util { appKey: string; appSecret: Buffer; stage: Stage; constructor(key: string, secret: string, stage: Stage) { this.appKey = key; this.appSecret = Buffer.from(secret, 'utf8'); this.stage = stage; } static buildUrl(parsedUrl: UrlWithParsedQuery, data = {}): string { let toStringify = Object.assign(parsedUrl.query, data); let result = parsedUrl.pathname as string; if (Object.keys(toStringify).length) { let list = this.buildParameters(toStringify); result += '?' + list.join('&'); } return result; } static buildParameters(toStringify: ParsedUrlQuery): string[] { let keys = Object.keys(toStringify).sort(); let list = new Array(keys.length); for (let i = 0; i < keys.length; i++) { let key = keys[i]; if (toStringify[key] !== undefined && toStringify[key] !== null && ('' + toStringify[key])) { list[i] = `${key}=${toStringify[key]}`; } else { list[i] = `${key}`; } } return list; } buildStringToSign(method: string, headers: Header, signedHeadersStr: string, url: UrlWithParsedQuery, data: FormData = {}) { // accept, contentMD5, contentType, const lf = '\n'; let list: string[] = [method, lf]; const accept = headers['accept']; if (accept) { list.push(accept as string); } list.push(lf); const contentMD5 = headers['content-md5']; if (contentMD5) { list.push(contentMD5 as string); } list.push(lf); const contentType = (headers['content-type'] || '') as string; if (contentType) { list.push(contentType); } list.push(lf); const date = headers['date']; if (date) { list.push(date as string); } list.push(lf); if (signedHeadersStr) { list.push(signedHeadersStr); list.push(lf); } if (contentType.startsWith(Content_Type_Form_Data)) { list.push(Util.buildUrl(url, data)); } else { list.push(Util.buildUrl(url)); } return list.join(''); } sign(stringToSign: string) { return CryptoJS.HmacSHA256(stringToSign, this.appSecret).toString(CryptoJS.enc.Base64); } static md5(content: string) { return CryptoJS.MD5(content).toString(CryptoJS.enc.Base64); } getSignHeaderKeys(headers: {}) { const keys = Object.keys(headers).sort(); const signKeys = []; for (let i = 0; i < keys.length; i++) { const key = keys[i]; // x-ca- 开头的header或者指定的header if (key.startsWith('x-ca-')) { signKeys.push(key); } } // 按字典序排序 return signKeys.sort(); } buildHeaders(headers = {}, extra = {}): Header { return Object.assign({ 'ca_version': ['1'], 'x-ca-timestamp': [Date.now()], 'x-ca-key': [this.appKey], 'x-ca-nonce': [v4()], 'x-ca-seq': [Number(seqInc()).toString()], 'x-ca-stage': [this.stage], }, headers, extra); } getSignedHeadersString(signHeaders: Header, headers: Header) { let list = []; for (let i = 0; i < signHeaders.length; i++) { let key = signHeaders[i]; list.push(key + ':' + headers[key]); } return list.join('\n'); } // normal post or get to ws signed request createSignedRequestHeaders(method: string, url: string, requestHeaders: Header, contentType: string, acceptType: string, webSocketApiType?: WebSocketApiType, body?: string | FormData, deviceId?: string) { // 小写化,合并之后的headers let headers = this.buildHeaders(requestHeaders, {'content-type': [contentType], 'accept': [acceptType]}); if (webSocketApiType) { headers['x-ca-websocket_api_type'] = [webSocketApiType]; } if (deviceId) { headers['x-ca-deviceid'] = [deviceId]; } let requestContentType = headers['content-type'] || ''; //stream form if (method === 'POST' && !requestContentType.startsWith(Content_Type_Form_Data) && body) { headers['content-md5'] = [Util.md5(body as string)]; } let signHeaderKeys = this.getSignHeaderKeys(headers); headers['x-ca-signature-headers'] = [signHeaderKeys.join(',')]; let signedHeadersStr = this.getSignedHeadersString(signHeaderKeys, headers); let parsedUrl = parse(url, true); let stringToSign = this.buildStringToSign(method, headers, signedHeadersStr, parsedUrl, body as FormData); headers['x-ca-signature'] = [this.sign(stringToSign)]; return headers; } } export {Util, Stage, seqInc, WebSocketApiType, Content_Type_Form_Data, Content_Type_Json_Data, Accept_JSON, FormData}