ts-sdk

client.ts 8.5KB

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