Clients.js 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173
  1. import path from 'path';
  2. import { fileURLToPath } from 'url';
  3. import Cache from '../libs/cache.js';
  4. const __filename = fileURLToPath(import.meta.url);
  5. const __dirname = path.dirname(__filename);
  6. const ClientsCacheFile = path.join(__dirname, '../data/clients.cache');
  7. const CLIENT_HEADER_FIELDS = [
  8. 'X-Device',
  9. 'X-Version',
  10. 'X-Data-Type',
  11. 'X-Market-Type',
  12. 'X-Group-Sequence',
  13. 'X-Platform',
  14. ];
  15. const CLIENT_KEY_FIELDS = [
  16. 'X-Data-Type',
  17. 'X-Market-Type',
  18. 'X-Group-Sequence',
  19. 'X-Platform',
  20. ];
  21. const CLIENT_FIELD_NAMES = {
  22. 'X-Data-Type': 'dataType',
  23. 'X-Device': 'device',
  24. 'X-Group-Sequence': 'groupSequence',
  25. 'X-Market-Type': 'marketType',
  26. 'X-Platform': 'platform',
  27. 'X-Version': 'version',
  28. };
  29. const CLIENTS = {
  30. Items: {},
  31. };
  32. let saveTimer = null;
  33. const normalizeHeaderValue = (value) => {
  34. if (Array.isArray(value)) {
  35. return value.join(',');
  36. }
  37. return value == null ? '' : String(value).trim();
  38. }
  39. const getRequestHeader = (req, field) => {
  40. return normalizeHeaderValue(req.get?.(field));
  41. }
  42. const getClientHeaders = (req) => {
  43. return CLIENT_HEADER_FIELDS.reduce((headers, field) => {
  44. headers[field] = getRequestHeader(req, field);
  45. return headers;
  46. }, {});
  47. }
  48. const formatClientFields = (headers) => {
  49. return CLIENT_HEADER_FIELDS.reduce((clientFields, field) => {
  50. clientFields[CLIENT_FIELD_NAMES[field]] = headers[field];
  51. return clientFields;
  52. }, {});
  53. }
  54. const getClientKey = (headers) => {
  55. return CLIENT_KEY_FIELDS
  56. .map(field => `${field}:${headers[field] ?? ''}`)
  57. .join('|');
  58. }
  59. const scheduleSaveClientsToCache = () => {
  60. if (saveTimer) {
  61. clearTimeout(saveTimer);
  62. }
  63. saveTimer = setTimeout(saveClientsToCache, 1000);
  64. }
  65. const recordRequest = (req) => {
  66. const route = req.path;
  67. const headers = getClientHeaders(req);
  68. const clientFields = formatClientFields(headers);
  69. const key = getClientKey(headers);
  70. const now = Date.now();
  71. const current = CLIENTS.Items[key] ?? {};
  72. CLIENTS.Items[key] = {
  73. key,
  74. ...clientFields,
  75. deviceId: current.deviceId,
  76. route,
  77. firstRequestTime: current.firstRequestTime ?? now,
  78. lastRequestTime: now,
  79. requestCount: (current.requestCount ?? 0) + 1,
  80. };
  81. scheduleSaveClientsToCache();
  82. return CLIENTS.Items[key];
  83. }
  84. const getClients = () => {
  85. return Object.values(CLIENTS.Items)
  86. .sort((a, b) => (b.lastRequestTime ?? 0) - (a.lastRequestTime ?? 0));
  87. }
  88. const updateClient = ({ key, deviceId } = {}) => {
  89. if (!key || !CLIENTS.Items[key]) {
  90. return Promise.reject(new Error('CLIENT_NOT_FOUND'));
  91. }
  92. if (deviceId === undefined || deviceId === null || deviceId === '') {
  93. delete CLIENTS.Items[key].deviceId;
  94. saveClientsToCache();
  95. return Promise.resolve(CLIENTS.Items[key]);
  96. }
  97. const parsedDeviceId = Number(deviceId);
  98. if (!Number.isInteger(parsedDeviceId)) {
  99. return Promise.reject(new Error('DEVICE_ID_INVALID'));
  100. }
  101. CLIENTS.Items[key] = {
  102. ...CLIENTS.Items[key],
  103. deviceId: parsedDeviceId,
  104. };
  105. saveClientsToCache();
  106. return Promise.resolve(CLIENTS.Items[key]);
  107. }
  108. const deleteClient = (key) => {
  109. if (!key || !CLIENTS.Items[key]) {
  110. return Promise.reject(new Error('CLIENT_NOT_FOUND'));
  111. }
  112. delete CLIENTS.Items[key];
  113. saveClientsToCache();
  114. return Promise.resolve();
  115. }
  116. function saveClientsToCache() {
  117. if (saveTimer) {
  118. clearTimeout(saveTimer);
  119. saveTimer = null;
  120. }
  121. Cache.setData(ClientsCacheFile, CLIENTS);
  122. }
  123. function loadClientsFromCache() {
  124. const cachedClients = Cache.getData(ClientsCacheFile, true);
  125. if (!cachedClients?.Items) {
  126. return;
  127. }
  128. CLIENTS.Items = cachedClients.Items;
  129. }
  130. loadClientsFromCache();
  131. process.on('exit', saveClientsToCache);
  132. process.on('SIGINT', () => {
  133. process.exit(0);
  134. });
  135. process.on('SIGTERM', () => {
  136. process.exit(0);
  137. });
  138. process.on('SIGUSR2', () => {
  139. process.exit(0);
  140. });
  141. const Clients = {
  142. recordRequest,
  143. getClients,
  144. updateClient,
  145. deleteClient,
  146. };
  147. export { recordRequest, getClients, updateClient, deleteClient };
  148. export default Clients;