No Description

client.ts 10KB

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