ts-sdk

index.ts 11KB

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