/* * Copyright (c) 2022. * Author Peter Placzek (tada5hi) * For the full copyright and license information, * view the LICENSE file that was distributed with this source code. */ import {Redis} from "ioredis"; export interface StatusOptions { recordType?: string, redisPrefix: string, redisSuffix?: string, redisGetKey?: (key: T) => string } export interface OnlineIdsResponse { data: Record[], meta: { limit?: number, offset?: number total: number } } export class RecordStatus | undefined | number | string> { constructor(public readonly redisDatabase : Redis, public readonly options: StatusOptions) { } public async setOffline(id: string | number, key?: T) : Promise { await this.redisDatabase.zrem(this.getRedisKey(key), id); await this.dropMeta(id, key); } public async setOnline(id: string | number, key?: T, meta?: Record) : Promise { const seconds = (Date.now() / 1000).toFixed(); await this.redisDatabase.zadd(this.getRedisKey(key), seconds, id); if(typeof meta !== 'undefined') { await this.setMeta(id, meta, key); } } async getOnlineCount(key?: T) { return await this.redisDatabase.zcard(this.getRedisKey(key)); } public async getOnline(key?: T, offset?: number, limit?: number) : Promise { try { let args : (string | number)[] = [ this.getRedisKey(key), '+inf', '-inf', 'WITHSCORES' ]; if(typeof offset !== 'undefined' || typeof limit !== 'undefined') { offset = offset ?? 0; limit = limit ?? 50; args.push(offset); args.push(limit); } // @ts-ignore const set = await this.redisDatabase.zrevrangebyscore(...args); let clients : Record[] = []; for(let i=0; i = { id: id, meta: { firstSeen: parseInt(set[i+1]) * 1000 } }; if(typeof this.options.recordType !== 'undefined') { client.type = this.options.recordType; } clients.push(client); } return { data: clients, meta: { limit, offset, total: await this.getOnlineCount(key) }}; } catch (e) { console.log(e); return { data: [], meta: { limit, offset, total: 0 } }; } } //-------------------------------------------------------------------- public async setMeta(id: string | number, meta: Record, key?: T) { const redisPath : string = this.getRedisMetaKey(id, key); for(let index in meta) { if(!meta.hasOwnProperty(index)) continue; await this.redisDatabase.hset(redisPath, index, meta[index]); } } public async setMetaProperty(id: string | number, metaKey: string, metaValue: any, key?: T) { const redisPath : string = this.getRedisMetaKey(id, key); await this.redisDatabase.hset(redisPath, metaKey, metaValue); } public async dropMeta(id: string | number, key?: T) { const redisPath : string = this.getRedisMetaKey(id, key); await this.redisDatabase.del(redisPath); } //-------------------------------------------------------------------- public getRedisKey(key?: T) : string { let prefix : string = this.options.redisPrefix + '.status'; if(typeof this.options.redisSuffix !== 'undefined') { prefix += '.' + this.options.redisSuffix; } if(typeof key === 'undefined') { return prefix; } if(typeof this.options.redisGetKey !== 'undefined') { return prefix + '.' + this.options.redisGetKey(key); } if(typeof key === 'string' || typeof key === 'number') { return prefix + ':' + key; } return prefix; } public getRedisMetaKey(id: string | number, key?: T) { const redisKey = this.getRedisKey(key); return redisKey + 'meta:' + id; } }