WebSocketController.js 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236
  1. const _ = require('lodash');
  2. const WebSocket = require ('ws');
  3. const WorkerState = require('../core/WorkerState');//singleton
  4. const WebWorker = require('../core/WebWorker');//singleton
  5. const log = new (require('../core/AppLogger'))().log;//singleton
  6. const utils = require('../core/utils');
  7. const cleanPeriod = 1*60*1000;//1 минута
  8. const closeSocketOnIdle = 5*60*1000;//5 минут
  9. class WebSocketController {
  10. constructor(wss, webAccess, config) {
  11. this.config = config;
  12. this.isDevelopment = (config.branch == 'development');
  13. this.webAccess = webAccess;
  14. this.workerState = new WorkerState();
  15. this.webWorker = new WebWorker(config);
  16. this.wss = wss;
  17. wss.on('connection', (ws) => {
  18. ws.on('message', (message) => {
  19. this.onMessage(ws, message.toString());
  20. });
  21. ws.on('error', (err) => {
  22. log(LM_ERR, err);
  23. });
  24. });
  25. this.periodicClean();//no await
  26. }
  27. async periodicClean() {
  28. while (1) {//eslint-disable-line no-constant-condition
  29. try {
  30. const now = Date.now();
  31. //почистим ws-клиентов
  32. this.wss.clients.forEach((ws) => {
  33. if (!ws.lastActivity || now - ws.lastActivity > closeSocketOnIdle - 50) {
  34. ws.terminate();
  35. }
  36. });
  37. } catch(e) {
  38. log(LM_ERR, `WebSocketController.periodicClean error: ${e.message}`);
  39. }
  40. await utils.sleep(cleanPeriod);
  41. }
  42. }
  43. async onMessage(ws, message) {
  44. let req = {};
  45. try {
  46. if (this.isDevelopment || this.config.logQueries) {
  47. log(`WebSocket-IN: ${utils.cutString(message)}`);
  48. }
  49. req = JSON.parse(message);
  50. req.__startTime = Date.now();
  51. ws.lastActivity = Date.now();
  52. //pong for WebSocketConnection
  53. this.send({_rok: 1}, req, ws);
  54. //access
  55. if (!await this.webAccess.hasAccess(req.accessToken)) {
  56. await utils.sleep(500);
  57. const salt = this.webAccess.newToken();
  58. this.send({error: 'need_access_token', salt}, req, ws);
  59. return;
  60. }
  61. //api
  62. switch (req.action) {
  63. case 'test':
  64. await this.test(req, ws); break;
  65. case 'logout':
  66. await this.logout(req, ws); break;
  67. case 'get-config':
  68. await this.getConfig(req, ws); break;
  69. case 'get-worker-state':
  70. await this.getWorkerState(req, ws); break;
  71. case 'search':
  72. await this.search(req, ws); break;
  73. case 'bookSearch':
  74. await this.bookSearch(req, ws); break;
  75. case 'get-author-book-list':
  76. await this.getAuthorBookList(req, ws); break;
  77. case 'get-author-series-list':
  78. await this.getAuthorSeriesList(req, ws); break;
  79. case 'get-series-book-list':
  80. await this.getSeriesBookList(req, ws); break;
  81. case 'get-genre-tree':
  82. await this.getGenreTree(req, ws); break;
  83. case 'get-book-link':
  84. await this.getBookLink(req, ws); break;
  85. case 'get-book-info':
  86. await this.getBookInfo(req, ws); break;
  87. case 'get-inpx-file':
  88. await this.getInpxFile(req, ws); break;
  89. default:
  90. throw new Error(`Action not found: ${req.action}`);
  91. }
  92. } catch (e) {
  93. this.send({error: e.message}, req, ws);
  94. }
  95. }
  96. send(res, req, ws) {
  97. if (ws.readyState == WebSocket.OPEN) {
  98. ws.lastActivity = Date.now();
  99. let r = res;
  100. if (req.requestId)
  101. r = Object.assign({requestId: req.requestId}, r);
  102. const message = JSON.stringify(r);
  103. ws.send(message);
  104. if (this.isDevelopment || this.config.logQueries) {
  105. log(`WebSocket-OUT: ${utils.cutString(message)}`);
  106. log(`${Date.now() - req.__startTime}ms`);
  107. }
  108. }
  109. }
  110. //Actions ------------------------------------------------------------------
  111. async test(req, ws) {
  112. this.send({message: `${this.config.name} project is awesome`}, req, ws);
  113. }
  114. async logout(req, ws) {
  115. await this.webAccess.deleteAccess(req.accessToken);
  116. this.send({success: true}, req, ws);
  117. }
  118. async getConfig(req, ws) {
  119. const config = _.pick(this.config, this.config.webConfigParams);
  120. config.dbConfig = await this.webWorker.dbConfig();
  121. config.freeAccess = this.webAccess.freeAccess;
  122. this.send(config, req, ws);
  123. }
  124. async getWorkerState(req, ws) {
  125. if (!req.workerId)
  126. throw new Error(`key 'workerId' is empty`);
  127. const state = this.workerState.getState(req.workerId);
  128. this.send((state ? state : {}), req, ws);
  129. }
  130. async search(req, ws) {
  131. if (!req.query)
  132. throw new Error(`query is empty`);
  133. if (!req.from)
  134. throw new Error(`from is empty`);
  135. const result = await this.webWorker.search(req.from, req.query);
  136. this.send(result, req, ws);
  137. }
  138. async bookSearch(req, ws) {
  139. if (!this.config.extendedSearch)
  140. throw new Error('config.extendedSearch disabled');
  141. if (!req.query)
  142. throw new Error(`query is empty`);
  143. const result = await this.webWorker.bookSearch(req.query);
  144. this.send(result, req, ws);
  145. }
  146. async getAuthorBookList(req, ws) {
  147. const result = await this.webWorker.getAuthorBookList(req.authorId);
  148. this.send(result, req, ws);
  149. }
  150. async getAuthorSeriesList(req, ws) {
  151. const result = await this.webWorker.getAuthorSeriesList(req.authorId);
  152. this.send(result, req, ws);
  153. }
  154. async getSeriesBookList(req, ws) {
  155. const result = await this.webWorker.getSeriesBookList(req.series);
  156. this.send(result, req, ws);
  157. }
  158. async getGenreTree(req, ws) {
  159. const result = await this.webWorker.getGenreTree();
  160. this.send(result, req, ws);
  161. }
  162. async getBookLink(req, ws) {
  163. if (!utils.hasProp(req, 'bookUid'))
  164. throw new Error(`bookUid is empty`);
  165. const result = await this.webWorker.getBookLink(req.bookUid);
  166. this.send(result, req, ws);
  167. }
  168. async getBookInfo(req, ws) {
  169. if (!utils.hasProp(req, 'bookUid'))
  170. throw new Error(`bookUid is empty`);
  171. const result = await this.webWorker.getBookInfo(req.bookUid);
  172. this.send(result, req, ws);
  173. }
  174. async getInpxFile(req, ws) {
  175. if (!this.config.allowRemoteLib)
  176. throw new Error('Remote lib access disabled');
  177. const result = await this.webWorker.getInpxFile(req);
  178. this.send(result, req, ws);
  179. }
  180. }
  181. module.exports = WebSocketController;