|
|
@@ -1,12 +1,28 @@
|
|
|
+import mongoose from 'mongoose';
|
|
|
import path from 'path';
|
|
|
import { fileURLToPath } from 'url';
|
|
|
|
|
|
import Cache from '../libs/cache.js';
|
|
|
|
|
|
+const { Schema } = mongoose;
|
|
|
+
|
|
|
const __filename = fileURLToPath(import.meta.url);
|
|
|
const __dirname = path.dirname(__filename);
|
|
|
const ClientsCacheFile = path.join(__dirname, '../data/clients.cache');
|
|
|
|
|
|
+const clientSettingSchema = new Schema({
|
|
|
+ _id: {
|
|
|
+ required: true,
|
|
|
+ type: String,
|
|
|
+ },
|
|
|
+ deviceId: {
|
|
|
+ required: true,
|
|
|
+ type: Number,
|
|
|
+ },
|
|
|
+}, { timestamps: true });
|
|
|
+
|
|
|
+const ClientSetting = mongoose.model('ClientSetting', clientSettingSchema);
|
|
|
+
|
|
|
const CLIENT_HEADER_FIELDS = [
|
|
|
'X-Device',
|
|
|
'X-Version',
|
|
|
@@ -34,6 +50,8 @@ const CLIENT_FIELD_NAMES = {
|
|
|
|
|
|
const CLIENTS = {
|
|
|
Items: {},
|
|
|
+ LegacySettings: {},
|
|
|
+ Settings: {},
|
|
|
};
|
|
|
|
|
|
let saveTimer = null;
|
|
|
@@ -91,11 +109,12 @@ const recordRequest = (req) => {
|
|
|
const key = getClientKey(headers);
|
|
|
const now = Date.now();
|
|
|
const current = CLIENTS.Items[key] ?? {};
|
|
|
+ const setting = CLIENTS.Settings[key] ?? {};
|
|
|
|
|
|
CLIENTS.Items[key] = {
|
|
|
key,
|
|
|
...clientFields,
|
|
|
- deviceId: current.deviceId,
|
|
|
+ deviceId: setting.deviceId ?? current.deviceId,
|
|
|
ip: getClientIp(req),
|
|
|
route,
|
|
|
firstRequestTime: current.firstRequestTime ?? now,
|
|
|
@@ -109,37 +128,74 @@ const recordRequest = (req) => {
|
|
|
|
|
|
const getClients = () => {
|
|
|
return Object.values(CLIENTS.Items)
|
|
|
+ .map(client => ({
|
|
|
+ ...client,
|
|
|
+ deviceId: CLIENTS.Settings[client.key]?.deviceId ?? client.deviceId,
|
|
|
+ }))
|
|
|
.sort((a, b) => (b.lastRequestTime ?? 0) - (a.lastRequestTime ?? 0));
|
|
|
}
|
|
|
|
|
|
-const updateClient = ({ key, deviceId } = {}) => {
|
|
|
+const updateClient = async ({ key, deviceId } = {}) => {
|
|
|
if (!key || !CLIENTS.Items[key]) {
|
|
|
return Promise.reject(new Error('CLIENT_NOT_FOUND'));
|
|
|
}
|
|
|
if (deviceId === undefined || deviceId === null || deviceId === '') {
|
|
|
+ delete CLIENTS.Settings[key];
|
|
|
delete CLIENTS.Items[key].deviceId;
|
|
|
+ await ClientSetting.deleteOne({ _id: key });
|
|
|
saveClientsToCache();
|
|
|
- return Promise.resolve(CLIENTS.Items[key]);
|
|
|
+ return CLIENTS.Items[key];
|
|
|
}
|
|
|
const parsedDeviceId = Number(deviceId);
|
|
|
if (!Number.isInteger(parsedDeviceId)) {
|
|
|
return Promise.reject(new Error('DEVICE_ID_INVALID'));
|
|
|
}
|
|
|
+ await ClientSetting.findByIdAndUpdate(
|
|
|
+ key,
|
|
|
+ { $set: { deviceId: parsedDeviceId } },
|
|
|
+ { new: true, upsert: true },
|
|
|
+ );
|
|
|
+ CLIENTS.Settings[key] = { deviceId: parsedDeviceId };
|
|
|
CLIENTS.Items[key] = {
|
|
|
...CLIENTS.Items[key],
|
|
|
deviceId: parsedDeviceId,
|
|
|
};
|
|
|
saveClientsToCache();
|
|
|
- return Promise.resolve(CLIENTS.Items[key]);
|
|
|
+ return CLIENTS.Items[key];
|
|
|
}
|
|
|
|
|
|
-const deleteClient = (key) => {
|
|
|
+const deleteClient = async (key) => {
|
|
|
if (!key || !CLIENTS.Items[key]) {
|
|
|
return Promise.reject(new Error('CLIENT_NOT_FOUND'));
|
|
|
}
|
|
|
delete CLIENTS.Items[key];
|
|
|
+ delete CLIENTS.Settings[key];
|
|
|
+ await ClientSetting.deleteOne({ _id: key });
|
|
|
saveClientsToCache();
|
|
|
- return Promise.resolve();
|
|
|
+}
|
|
|
+
|
|
|
+const loadClientSettings = async () => {
|
|
|
+ const legacyEntries = Object.entries(CLIENTS.LegacySettings);
|
|
|
+ if (legacyEntries.length) {
|
|
|
+ await Promise.all(legacyEntries.map(([key, setting]) => {
|
|
|
+ return ClientSetting.updateOne(
|
|
|
+ { _id: key },
|
|
|
+ { $setOnInsert: { deviceId: setting.deviceId } },
|
|
|
+ { upsert: true },
|
|
|
+ );
|
|
|
+ }));
|
|
|
+ CLIENTS.LegacySettings = {};
|
|
|
+ }
|
|
|
+
|
|
|
+ const settings = await ClientSetting.find().lean();
|
|
|
+ CLIENTS.Settings = settings.reduce((map, setting) => {
|
|
|
+ map[setting._id] = { deviceId: setting.deviceId };
|
|
|
+ if (CLIENTS.Items[setting._id]) {
|
|
|
+ CLIENTS.Items[setting._id].deviceId = setting.deviceId;
|
|
|
+ }
|
|
|
+ return map;
|
|
|
+ }, {});
|
|
|
+ return CLIENTS.Settings;
|
|
|
}
|
|
|
|
|
|
function saveClientsToCache() {
|
|
|
@@ -147,7 +203,7 @@ function saveClientsToCache() {
|
|
|
clearTimeout(saveTimer);
|
|
|
saveTimer = null;
|
|
|
}
|
|
|
- Cache.setData(ClientsCacheFile, CLIENTS);
|
|
|
+ Cache.setData(ClientsCacheFile, { Items: CLIENTS.Items });
|
|
|
}
|
|
|
|
|
|
function loadClientsFromCache() {
|
|
|
@@ -156,9 +212,16 @@ function loadClientsFromCache() {
|
|
|
return;
|
|
|
}
|
|
|
CLIENTS.Items = cachedClients.Items;
|
|
|
+ Object.values(CLIENTS.Items).forEach(client => {
|
|
|
+ if (Number.isInteger(client.deviceId)) {
|
|
|
+ CLIENTS.LegacySettings[client.key] = { deviceId: client.deviceId };
|
|
|
+ }
|
|
|
+ delete client.deviceId;
|
|
|
+ });
|
|
|
}
|
|
|
|
|
|
loadClientsFromCache();
|
|
|
+loadClientSettings().catch(() => {});
|
|
|
|
|
|
process.on('exit', saveClientsToCache);
|
|
|
process.on('SIGINT', () => {
|
|
|
@@ -176,7 +239,8 @@ const Clients = {
|
|
|
getClients,
|
|
|
updateClient,
|
|
|
deleteClient,
|
|
|
+ loadClientSettings,
|
|
|
};
|
|
|
|
|
|
-export { recordRequest, getClients, updateClient, deleteClient };
|
|
|
+export { recordRequest, getClients, updateClient, deleteClient, loadClientSettings };
|
|
|
export default Clients;
|