|
@@ -0,0 +1,135 @@
|
|
|
+const { asyncSafeSetInterval } = require("../util/promise");
|
|
|
+const { MINUTE, SECOND } = require("../util/time");
|
|
|
+const BaseService = require("./BaseService");
|
|
|
+const { DB_WRITE } = require("./database/consts");
|
|
|
+
|
|
|
+/**
|
|
|
+ * This service is responsible for updating session activity
|
|
|
+ * timestamps and maintaining the number of active sessions.
|
|
|
+ */
|
|
|
+class SessionService extends BaseService {
|
|
|
+ static MODULES = {
|
|
|
+ // uuidv5: require('uuid').v5,
|
|
|
+ uuidv4: require('uuid').v4,
|
|
|
+ }
|
|
|
+
|
|
|
+ _construct () {
|
|
|
+ this.sessions = {};
|
|
|
+ }
|
|
|
+
|
|
|
+ async _init () {
|
|
|
+ this.db = await this.services.get('database').get(DB_WRITE, 'session');
|
|
|
+
|
|
|
+ (async () => {
|
|
|
+ // TODO: change to 5 minutes or configured value
|
|
|
+ asyncSafeSetInterval(async () => {
|
|
|
+ await this._update_sessions();
|
|
|
+ }, 1000);
|
|
|
+ // }, 2 * MINUTE);
|
|
|
+ })();
|
|
|
+ }
|
|
|
+
|
|
|
+ async create_session (user, meta) {
|
|
|
+ const unix_ts = Math.floor(Date.now() / 1000);
|
|
|
+
|
|
|
+ meta = {
|
|
|
+ // clone
|
|
|
+ ...(meta || {}),
|
|
|
+ };
|
|
|
+ meta.created = new Date().toISOString();
|
|
|
+ meta.created_unix = unix_ts;
|
|
|
+ const uuid = this.modules.uuidv4();
|
|
|
+ await this.db.write(
|
|
|
+ 'INSERT INTO `sessions` ' +
|
|
|
+ '(`uuid`, `user_id`, `meta`, `last_activity`, `created_at`) ' +
|
|
|
+ 'VALUES (?, ?, ?, ?, ?)',
|
|
|
+ [uuid, user.id, JSON.stringify(meta), unix_ts, unix_ts],
|
|
|
+ );
|
|
|
+ const session = {
|
|
|
+ last_touch: Date.now(),
|
|
|
+ last_store: Date.now(),
|
|
|
+ uuid,
|
|
|
+ user_uid: user.uuid,
|
|
|
+ meta,
|
|
|
+ };
|
|
|
+ this.sessions[uuid] = session;
|
|
|
+
|
|
|
+ return session;
|
|
|
+ }
|
|
|
+
|
|
|
+ async get_session_ (uuid) {
|
|
|
+ let session = this.sessions[uuid];
|
|
|
+ if ( session ) return session;
|
|
|
+ ;[session] = await this.db.read(
|
|
|
+ "SELECT * FROM `sessions` WHERE `uuid` = ? LIMIT 1",
|
|
|
+ [uuid],
|
|
|
+ );
|
|
|
+ if ( ! session ) return;
|
|
|
+ session.last_store = Date.now();
|
|
|
+ session.meta = this.db.case({
|
|
|
+ mysql: () => session.meta,
|
|
|
+ otherwise: () => JSON.parse(session.meta ?? "{}")
|
|
|
+ })();
|
|
|
+ this.sessions[uuid] = session;
|
|
|
+ return session;
|
|
|
+ }
|
|
|
+ async get_session (uuid) {
|
|
|
+ const session = await this.get_session_(uuid);
|
|
|
+ if ( session ) {
|
|
|
+ session.last_touch = Date.now();
|
|
|
+ session.meta.last_activity = (new Date()).toISOString();
|
|
|
+ }
|
|
|
+ return this.remove_internal_values_(session);
|
|
|
+ }
|
|
|
+
|
|
|
+ remove_internal_values_ (session) {
|
|
|
+ const copy = {
|
|
|
+ ...session,
|
|
|
+ };
|
|
|
+ delete copy.last_touch;
|
|
|
+ delete copy.last_store;
|
|
|
+ delete copy.user_id;
|
|
|
+ return copy;
|
|
|
+ }
|
|
|
+
|
|
|
+ get_user_sessions (user) {
|
|
|
+ const sessions = [];
|
|
|
+ for ( const session of Object.values(this.sessions) ) {
|
|
|
+ if ( session.user_id === user.id ) {
|
|
|
+ sessions.push(session);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return sessions.map(this.remove_internal_values_.bind(this));
|
|
|
+ }
|
|
|
+
|
|
|
+ remove_session (uuid) {
|
|
|
+ delete this.sessions[uuid];
|
|
|
+ return this.db.write(
|
|
|
+ 'DELETE FROM `sessions` WHERE `uuid` = ?',
|
|
|
+ [uuid],
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ async _update_sessions () {
|
|
|
+ this.log.tick('UPDATING SESSIONS');
|
|
|
+ const now = Date.now();
|
|
|
+ const keys = Object.keys(this.sessions);
|
|
|
+ for ( const key of keys ) {
|
|
|
+ const session = this.sessions[key];
|
|
|
+ // if ( now - session.last_store > 5 * MINUTE ) {
|
|
|
+ if ( now - session.last_store > 20 * SECOND ) {
|
|
|
+ this.log.debug('storing session meta: ' + session.uuid);
|
|
|
+ const unix_ts = Math.floor(now / 1000);
|
|
|
+ await this.db.write(
|
|
|
+ 'UPDATE `sessions` ' +
|
|
|
+ 'SET `meta` = ?, `last_activity` = ? ' +
|
|
|
+ 'WHERE `uuid` = ?',
|
|
|
+ [JSON.stringify(session.meta), unix_ts, session.uuid],
|
|
|
+ );
|
|
|
+ session.last_store = now;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+module.exports = { SessionService };
|