ts-sdk

client.ts 11KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432
  1. import { Packet } from './packet';
  2. import { Utils } from './utils';
  3. import { ReadyStateCallback } from './types/callback';
  4. import { WebsocketError } from './error';
  5. const clientError = 400;
  6. export interface WebSocketResp {
  7. value: object | string;
  8. }
  9. /**
  10. * 初始化链接以及收发数据
  11. */
  12. class Client {
  13. private _maxPayload: number;
  14. private _enableLogger: boolean;
  15. private listeners: Map<number, (data: WebSocketResp) => void>;
  16. private requestHeader: string;
  17. private responseHeader: string;
  18. private url: string;
  19. private reconnectTimes: number;
  20. private reconnectLock: boolean;
  21. private socket: WebSocket;
  22. private readyStateCallback: ReadyStateCallback;
  23. /**
  24. * 构造函数,初始化客户端链接
  25. * @param url websocket链接地址
  26. * @param readyStateCallback 链接状态回调,可以处理onOpen、onClose、onError
  27. */
  28. public constructor(url: string, readyStateCallback: ReadyStateCallback) {
  29. this.listeners = new Map<number, (data: WebSocketResp) => void>();
  30. this.requestHeader = '';
  31. this.requestHeader = '';
  32. this._maxPayload = 1024 * 1024;
  33. this.url = url;
  34. this.reconnectTimes = 0;
  35. this.readyStateCallback = readyStateCallback;
  36. this._enableLogger = false;
  37. this.socket = this.connect();
  38. }
  39. /**
  40. * 设置可以处理的数据包上限
  41. * @param maxPayload 最多可以处理的数据包大小
  42. */
  43. public set maxPayload(maxPayload: number) {
  44. this._maxPayload = maxPayload;
  45. }
  46. /**
  47. * 获取可以处理的数据包大小
  48. */
  49. public get maxPayload(): number {
  50. return this._maxPayload;
  51. }
  52. /**
  53. * 设置是否允许显示运行日志
  54. */
  55. public set enableLogger(enableLogger: boolean) {
  56. this._enableLogger = enableLogger;
  57. }
  58. /**
  59. * 获取是否显示日志的配置信息
  60. */
  61. public get enableLogger(): boolean {
  62. return this._enableLogger;
  63. }
  64. /**
  65. * 发送ping请求,来保持长连接
  66. * @param param 请求参数,比如{"hello":"world"}
  67. */
  68. public async ping(param: object): Promise<WebSocketResp> {
  69. return new Promise(
  70. (
  71. resolve: (data: WebSocketResp) => void,
  72. reject: (err: WebsocketError) => void,
  73. ): void => {
  74. if (this.socket.readyState !== this.socket.OPEN) {
  75. if (this._enableLogger) {
  76. console.log('[ping]: connection refuse');
  77. }
  78. reject(new WebsocketError(clientError, 'connection refuse'));
  79. }
  80. const heartbeatOperator = 0;
  81. this.listeners.set(
  82. heartbeatOperator,
  83. (data: WebSocketResp): void => {
  84. const code = this.getResponseProperty('code');
  85. if (code !== '') {
  86. const message = this.getResponseProperty('message');
  87. reject(new WebsocketError(Number(code), message));
  88. } else {
  89. resolve(data);
  90. }
  91. },
  92. );
  93. const p = new Packet();
  94. this.send(
  95. p.pack(
  96. heartbeatOperator,
  97. 0,
  98. this.requestHeader,
  99. JSON.stringify(param),
  100. ),
  101. );
  102. if (this._enableLogger) {
  103. console.info(
  104. '[send data packet]',
  105. heartbeatOperator,
  106. 0,
  107. this.requestHeader,
  108. param,
  109. );
  110. }
  111. },
  112. );
  113. }
  114. /**
  115. * 同步方式向服务端发送请求
  116. * @param operator 路由地址
  117. * @param param 请求参数,比如{"hello":"world"}
  118. * @param callback 请求状态回调处理
  119. */
  120. public async request(
  121. operator: string,
  122. param: object,
  123. ): Promise<WebSocketResp> {
  124. return await this.asyncSend(operator, param);
  125. }
  126. /**
  127. * 添加消息监听
  128. * @description 添加消息监听器,比如operator是/v1/message/listener,那么从服务端推送到/v1/message/listener的消息会进入到定义的listener里面进行处理
  129. * @param operator 消息监听地址
  130. * @param listener 定义如何处理从服务端返回的消息
  131. */
  132. public addMessageListener(
  133. operator: string,
  134. listener: (data: WebSocketResp) => void,
  135. ): void {
  136. this.listeners.set(Utils.crc32(operator), listener);
  137. }
  138. /**
  139. * 移除消息监听
  140. * @param operator 消息监听地址
  141. */
  142. public removeMessageListener(operator: string): void {
  143. delete this.listeners[Utils.crc32(operator)];
  144. }
  145. /**
  146. * 返回Websocket链接状态
  147. * @returns Websocket的链接状态
  148. */
  149. public get readyState(): number {
  150. return this.socket.readyState;
  151. }
  152. /**
  153. * 添加请求属性,会携带在数据帧里面发送到服务端
  154. * @param key 属性名
  155. * @param value 属性值
  156. */
  157. public setRequestProperty(key: string, value: string): void {
  158. let v = this.getRequestProperty(key);
  159. this.requestHeader = this.requestHeader.replace(key + '=' + v + ';', '');
  160. this.requestHeader = this.requestHeader + key + '=' + value + ';';
  161. }
  162. /**
  163. * 获取请求属性
  164. * @param key 属性名
  165. */
  166. public getRequestProperty(key: string): string {
  167. if (this.requestHeader !== undefined) {
  168. let values = this.requestHeader.split(';');
  169. for (let index in values) {
  170. let kv = values[index].split('=');
  171. if (kv[0] === key) {
  172. return kv[1];
  173. }
  174. }
  175. }
  176. return '';
  177. }
  178. /**
  179. * 设置响应属性,客户端基本用不到,都是服务端来进行设置
  180. * @param key 属性名
  181. * @param value 属性值
  182. */
  183. public setResponseProperty(key: string, value: string): void {
  184. let v = this.getResponseProperty(key);
  185. this.responseHeader = this.responseHeader.replace(key + '=' + v + ';', '');
  186. this.responseHeader = this.responseHeader + key + '=' + value + ';';
  187. }
  188. /**
  189. * 获取从服务端返回的属性
  190. * @param key 获取响应属性
  191. */
  192. public getResponseProperty(key: string): string {
  193. if (this.responseHeader !== undefined) {
  194. let values = this.responseHeader.split(';');
  195. for (let index in values) {
  196. let kv = values[index].split('=');
  197. if (kv[0] === key) {
  198. return kv[1];
  199. }
  200. }
  201. }
  202. return '';
  203. }
  204. /**
  205. * 关闭客户端链接
  206. */
  207. public close(code?: number, reason?: string): void {
  208. this.socket.close(code, reason);
  209. }
  210. /**
  211. * 创建websocket链接
  212. */
  213. private connect(): WebSocket {
  214. const readyStateCallback = this.readyStateCallback;
  215. let ws = new WebSocket(this.url);
  216. ws.binaryType = 'blob';
  217. ws.onopen = (ev): void => {
  218. if (this._enableLogger) {
  219. console.info('[websocket] open connection');
  220. }
  221. this.reconnectTimes = 0;
  222. readyStateCallback.onOpen(ev);
  223. };
  224. ws.onclose = (ev): void => {
  225. if (this._enableLogger) {
  226. console.info('[websocket] close connection');
  227. }
  228. this.reconnect();
  229. readyStateCallback.onClose(ev);
  230. };
  231. ws.onerror = (ev): void => {
  232. if (this._enableLogger) {
  233. console.info('[websocket] error');
  234. }
  235. this.reconnect();
  236. readyStateCallback.onError(ev);
  237. };
  238. ws.onmessage = (ev): void => {
  239. if (ev.data instanceof Blob) {
  240. let reader = new FileReader();
  241. reader.readAsArrayBuffer(ev.data);
  242. reader.onload = (): void => {
  243. try {
  244. let packet = new Packet().unPack(reader.result as ArrayBuffer);
  245. let packetLength = packet.headerLength + packet.bodyLength + 20;
  246. if (packetLength > this._maxPayload) {
  247. throw new Error('the packet is big than ' + this._maxPayload);
  248. }
  249. let operator = Number(packet.operator) + Number(packet.sequence);
  250. if (this.listeners.has(operator)) {
  251. if (packet.body === '') {
  252. packet.body = '{}';
  253. }
  254. this.responseHeader = packet.header;
  255. (this.listeners.get(operator) as (data: WebSocketResp) => void)(
  256. JSON.parse(packet.body),
  257. );
  258. }
  259. if (this._enableLogger) {
  260. if (operator !== 0 && packet.body !== 'null') {
  261. console.info('receive data packet', packet.body);
  262. }
  263. }
  264. } catch (e) {
  265. throw new Error(e);
  266. }
  267. };
  268. } else {
  269. throw new Error('unsupported data format');
  270. }
  271. };
  272. return ws;
  273. }
  274. /**
  275. * 断线重连
  276. */
  277. private reconnect(): void {
  278. if (!this.reconnectLock) {
  279. this.reconnectLock = true;
  280. if (this._enableLogger) {
  281. console.info('websocket reconnect in ' + this.reconnectTimes + 's');
  282. }
  283. // 尝试重连
  284. setTimeout((): void => {
  285. this.reconnectTimes++;
  286. this.socket = this.connect();
  287. this.reconnectLock = false;
  288. }, this.reconnectTimes * 1000);
  289. }
  290. }
  291. /**
  292. * 向服务端发送数据请求
  293. * @param data 向服务端传送的数据
  294. */
  295. private send(data: ArrayBuffer): void {
  296. if (this.socket.readyState !== this.socket.OPEN) {
  297. if (this._enableLogger) {
  298. console.error(
  299. '[send] WebSocket is already in CLOSING or CLOSED state.',
  300. );
  301. }
  302. return;
  303. }
  304. try {
  305. this.socket.send(data);
  306. } catch (e) {
  307. throw new Error('send data error' + e);
  308. }
  309. }
  310. /**
  311. * 异步向服务端发送请求
  312. * @param operator 路由地址
  313. * @param param 请求参数,比如{"hello":"world"}
  314. * @param callback 请求状态回调处理
  315. */
  316. private asyncSend(operator: string, param: object): Promise<WebSocketResp> {
  317. return new Promise(
  318. (
  319. resolve: (data: WebSocketResp) => void,
  320. reject: (err: WebsocketError) => void,
  321. ): void => {
  322. if (this.socket.readyState !== this.socket.OPEN) {
  323. if (this._enableLogger) {
  324. console.log('[ping]: connection refuse');
  325. }
  326. reject(
  327. new WebsocketError(clientError, 'asyncSend: connection refuse'),
  328. );
  329. }
  330. const sequence = new Date().getTime();
  331. const listener = Utils.crc32(operator) + sequence;
  332. this.listeners.set(
  333. listener,
  334. (data: WebSocketResp): void => {
  335. const code = this.getResponseProperty('code');
  336. if (code !== '') {
  337. const message = this.getResponseProperty('message');
  338. reject(new WebsocketError(Number(code), message));
  339. } else {
  340. resolve(data);
  341. }
  342. delete this.listeners[listener];
  343. },
  344. );
  345. const p = new Packet();
  346. this.send(
  347. p.pack(
  348. Utils.crc32(operator),
  349. sequence,
  350. this.requestHeader,
  351. JSON.stringify(param),
  352. ),
  353. );
  354. if (this._enableLogger) {
  355. console.info(
  356. '[send data packet]',
  357. operator,
  358. sequence,
  359. this.requestHeader,
  360. param,
  361. );
  362. }
  363. },
  364. );
  365. }
  366. }
  367. let client: Client;
  368. function getClient(url: string, callback: ReadyStateCallback): Client {
  369. if (!client) {
  370. client = new Client(url, callback);
  371. }
  372. return client;
  373. }
  374. export { Client, getClient };