cynx-lib 1.3.84 → 1.3.86

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/gen.js +77 -77
  2. package/package.json +5 -1
  3. package/ui/components/Drawer.jsx +208 -208
  4. package/ui/components/Pagination.jsx +145 -145
  5. package/ui/components/PrivacyBlur.jsx +603 -603
  6. package/utils/index.cjs +5 -0
  7. package/utils/index.js +22 -20
  8. package/utils/utils/duration.js +139 -139
  9. package/utils/utils/oidc.js +214 -214
  10. package/utils/utils/realtime.js +1634 -1634
  11. package/utils/utils/session.cjs +87 -9
  12. package/utils/utils/session.js +81 -8
  13. package/utils/utils/session_manager.cjs +490 -0
  14. package/utils/utils/session_manager.js +549 -0
  15. package/utils/utils/storage.js +393 -393
  16. package/utils/utils/ui.js +111 -111
  17. package/vue/components/Badge.vue +79 -79
  18. package/vue/components/Button.vue +209 -209
  19. package/vue/components/Checkbox.vue +103 -103
  20. package/vue/components/CircularProgress.vue +383 -383
  21. package/vue/components/Divider.vue +94 -94
  22. package/vue/components/Drawer.vue +243 -243
  23. package/vue/components/InfoBadge.vue +223 -223
  24. package/vue/components/Input.vue +341 -341
  25. package/vue/components/LinearProgress.vue +268 -268
  26. package/vue/components/Modal.vue +226 -226
  27. package/vue/components/Pagination.vue +199 -199
  28. package/vue/components/Select.vue +713 -713
  29. package/vue/components/Sidebar.vue +955 -955
  30. package/vue/components/Skeleton.vue +91 -91
  31. package/vue/components/Spinner.vue +263 -263
  32. package/vue/components/StatusTag.vue +89 -89
  33. package/vue/components/TabBar.vue +176 -176
  34. package/vue/components/Table.vue +388 -388
  35. package/vue/components/TimePicker.vue +399 -399
  36. package/vue/components/Toggle.vue +115 -115
  37. package/vue/components/XTable.vue +119 -119
  38. package/vue/composables/useAutoSkeletonRows.ts +71 -71
  39. package/vue/index.d.ts +67 -67
  40. package/vue/index.js +86 -86
@@ -0,0 +1,549 @@
1
+ /**
2
+ * cynx/utils — Universal Enterprise Session Manager (session_manager.js)
3
+ *
4
+ * Universal PostgreSQL and in-memory session management engine with dual-token architecture:
5
+ * - v2: Compound token format (`st_${sessionId}$${secretCode}`) for O(1) indexed lookups
6
+ * and safe non-secret Real-Time / WebSocket event targeting.
7
+ * - v1: Legacy flat token format (`st_${hex}`) for seamless backward compatibility.
8
+ *
9
+ * STRICT NO EMOJIS!
10
+ */
11
+
12
+ import { genUid, getRandomBytes } from './uid.js';
13
+ import { parseDurationMs } from './duration.js';
14
+ import { deriveDeviceName, genSessionToken } from './session.js';
15
+
16
+ /**
17
+ * Format a compound v2 token: prefix + sessionId + '$' + secretCode
18
+ * Example: "st_2SuO33NNKhXd$d9e1807390014a5b6c7d8e9f01234567"
19
+ *
20
+ * @param {string} sessionId Unique session row identifier (ID)
21
+ * @param {string} secret Cryptographically secure random secret
22
+ * @param {string} [prefix='st_'] Token prefix
23
+ * @returns {string}
24
+ */
25
+ export function formatToken(sessionId, secret, prefix = 'st_') {
26
+ return `${prefix}${sessionId}$${secret}`;
27
+ }
28
+
29
+ /**
30
+ * Parse an incoming token string.
31
+ * Automatically recognizes v2 compound tokens and legacy v1 flat tokens.
32
+ *
33
+ * @param {string} token Raw token string or Bearer header
34
+ * @param {string} [prefix='st_'] Expected token prefix
35
+ * @returns {{ version: number, sessionId: string|null, secret: string|null, prefix: string, raw: string, isValid: boolean }}
36
+ */
37
+ export function parseToken(token, prefix = 'st_') {
38
+ if (!token || typeof token !== 'string') {
39
+ return { version: 0, sessionId: null, secret: null, prefix, raw: '', isValid: false };
40
+ }
41
+ const clean = token.replace(/^Bearer\s+/i, '').trim();
42
+ if (!clean) {
43
+ return { version: 0, sessionId: null, secret: null, prefix, raw: '', isValid: false };
44
+ }
45
+
46
+ if (clean.includes('$')) {
47
+ const parts = clean.split('$');
48
+ const head = parts[0];
49
+ const secret = parts.slice(1).join('$');
50
+ const sessionId = head.startsWith(prefix) ? head.slice(prefix.length) : head;
51
+ return {
52
+ version: 2,
53
+ sessionId,
54
+ secret,
55
+ prefix,
56
+ raw: clean,
57
+ isValid: Boolean(sessionId && secret),
58
+ };
59
+ }
60
+
61
+ // Legacy v1 token (flat string without '$')
62
+ return {
63
+ version: 1,
64
+ sessionId: null,
65
+ secret: clean,
66
+ prefix,
67
+ raw: clean,
68
+ isValid: true,
69
+ };
70
+ }
71
+
72
+ /**
73
+ * Extract public sessionId from token if available.
74
+ * Returns null for legacy v1 tokens.
75
+ *
76
+ * @param {string} token
77
+ * @param {string} [prefix='st_']
78
+ * @returns {string|null}
79
+ */
80
+ export function getSessionId(token, prefix = 'st_') {
81
+ const parsed = parseToken(token, prefix);
82
+ return parsed.sessionId;
83
+ }
84
+
85
+ /**
86
+ * Universally extract session token from HTTP Request object (Express, Fastify, Next.js, Node).
87
+ * Inspects Authorization Bearer header, x-auth-token, x-session-token, and query parameter.
88
+ *
89
+ * @param {object} req HTTP request object
90
+ * @returns {string} Clean session token string or empty string
91
+ */
92
+ export function extractToken(req) {
93
+ if (!req) return '';
94
+ const headers = req.headers || {};
95
+ const auth = headers.authorization || headers.Authorization || '';
96
+ if (typeof auth === 'string' && auth.startsWith('Bearer ')) {
97
+ return auth.split(' ')[1].trim();
98
+ }
99
+ const custom = headers['x-auth-token'] || headers['x-session-token'] || headers['x-token'] || '';
100
+ if (typeof custom === 'string' && custom.trim()) {
101
+ return custom.trim();
102
+ }
103
+ if (req.query && req.query.token) {
104
+ return String(req.query.token).trim();
105
+ }
106
+ return '';
107
+ }
108
+
109
+ /**
110
+ * Universal Session Manager Engine.
111
+ */
112
+ export class SessionManager {
113
+ constructor(options = {}) {
114
+ this.options = {
115
+ query: options.query || null,
116
+ sessionsTable: options.sessionsTable || 'user_sessions',
117
+ usersTable: options.usersTable || 'users',
118
+ rolesTable: options.rolesTable || 'roles',
119
+ settingsTable: options.settingsTable || 'system_settings',
120
+ superRole: (options.superRole || 'superadmin').toLowerCase(),
121
+ tokenPrefix: options.tokenPrefix || 'st_',
122
+ tokenVersion: options.tokenVersion || 2,
123
+ secretBytes: options.secretBytes || 32,
124
+ idLength: options.idLength || 12,
125
+ defaultAbsoluteTimeoutMs: options.defaultAbsoluteTimeoutMs || 30 * 24 * 60 * 60 * 1000,
126
+ inactivityCacheTtlMs: options.inactivityCacheTtlMs || 30000,
127
+ jwtSecret: options.jwtSecret || (typeof process !== 'undefined' ? process.env.JWT_SECRET : ''),
128
+ ...options,
129
+ };
130
+
131
+ this._inactTimeoutMs = null;
132
+ this._inactFetchedAt = 0;
133
+ }
134
+
135
+ async _query(sql, params = []) {
136
+ if (typeof this.options.query !== 'function') {
137
+ throw new Error('[cynx/session] Database query function not configured in SessionManager.');
138
+ }
139
+ return this.options.query(sql, params);
140
+ }
141
+
142
+ parseToken(token) {
143
+ return parseToken(token, this.options.tokenPrefix);
144
+ }
145
+
146
+ getSessionId(token) {
147
+ return getSessionId(token, this.options.tokenPrefix);
148
+ }
149
+
150
+ extractToken(req) {
151
+ return extractToken(req);
152
+ }
153
+
154
+ formatToken(sessionId, secret) {
155
+ return formatToken(sessionId, secret, this.options.tokenPrefix);
156
+ }
157
+
158
+ async getInactivityTimeoutMs() {
159
+ const now = Date.now();
160
+ if (this._inactTimeoutMs !== null && now - this._inactFetchedAt < this.options.inactivityCacheTtlMs) {
161
+ return this._inactTimeoutMs;
162
+ }
163
+ try {
164
+ const sql = `SELECT value FROM ${this.options.settingsTable} WHERE key = 'session_inactivity_timeout'`;
165
+ const sRes = await this._query(sql).catch(() => ({ rows: [] }));
166
+ if (sRes.rows && sRes.rows.length > 0) {
167
+ let val = sRes.rows[0].value;
168
+ if (typeof val === 'string') {
169
+ try { val = JSON.parse(val); } catch {}
170
+ }
171
+ this._inactTimeoutMs = parseDurationMs(val, 0);
172
+ } else {
173
+ this._inactTimeoutMs = 0;
174
+ }
175
+ } catch {
176
+ this._inactTimeoutMs = 0;
177
+ }
178
+ this._inactFetchedAt = now;
179
+ return this._inactTimeoutMs;
180
+ }
181
+
182
+ clearSettingsCache() {
183
+ this._inactTimeoutMs = null;
184
+ this._inactFetchedAt = 0;
185
+ }
186
+
187
+ /**
188
+ * Create and persist a new session for a user.
189
+ *
190
+ * @param {string|number} userId
191
+ * @param {object} [req] HTTP request object for device and IP detection
192
+ * @param {object} [options]
193
+ * @returns {Promise<string>} Generated session token
194
+ */
195
+ async createSession(userId, req = null, options = {}) {
196
+ const id = genUid(this.options.idLength);
197
+ const userAgent = (req?.headers ? req.headers['user-agent'] || '' : '').slice(0, 500);
198
+ const ipAddress = (req?.headers ? req.headers['x-forwarded-for'] || req.socket?.remoteAddress || '' : '').slice(0, 45);
199
+ const deviceName = deriveDeviceName(req, userAgent);
200
+
201
+ let token;
202
+ if (this.options.tokenVersion === 2) {
203
+ const bytes = getRandomBytes(this.options.secretBytes);
204
+ let hex = '';
205
+ for (let i = 0; i < bytes.length; i++) {
206
+ hex += bytes[i].toString(16).padStart(2, '0');
207
+ }
208
+ token = formatToken(id, hex, this.options.tokenPrefix);
209
+ } else {
210
+ token = genSessionToken(this.options.secretBytes);
211
+ }
212
+
213
+ let absoluteMs = this.options.defaultAbsoluteTimeoutMs;
214
+ try {
215
+ const sRes = await this._query(`SELECT value FROM ${this.options.settingsTable} WHERE key = 'session_absolute_timeout'`);
216
+ if (sRes.rows && sRes.rows.length > 0) {
217
+ let val = sRes.rows[0].value;
218
+ if (typeof val === 'string') {
219
+ try { val = JSON.parse(val); } catch {}
220
+ }
221
+ const parsed = parseDurationMs(val, absoluteMs);
222
+ if (parsed > 0) absoluteMs = parsed;
223
+ }
224
+ } catch {}
225
+
226
+ const expiresAt = new Date(Date.now() + absoluteMs);
227
+
228
+ const insertSql = `
229
+ INSERT INTO ${this.options.sessionsTable} (id, user_id, token, user_agent, device_name, ip_address, is_active, expires_at)
230
+ VALUES ($1, $2, $3, $4, $5, $6, true, $7)
231
+ `;
232
+ await this._query(insertSql, [id, userId, token, userAgent, deviceName, ipAddress, expiresAt]);
233
+
234
+ return token;
235
+ }
236
+
237
+ /**
238
+ * Validate a session token against PostgreSQL database and users table.
239
+ * Seamlessly resolves v2 compound tokens (O(1) lookup by primary key id) and v1 tokens.
240
+ *
241
+ * @param {string} token
242
+ * @param {object} [options] Options: { updateActivity: boolean }
243
+ * @returns {Promise<object|null>}
244
+ */
245
+ async validateSession(token, options = {}) {
246
+ if (!token || typeof token !== 'string') return null;
247
+ const cleanToken = token.replace(/^Bearer\s+/i, '').trim();
248
+ if (!cleanToken) return null;
249
+
250
+ try {
251
+ const parsed = parseToken(cleanToken, this.options.tokenPrefix);
252
+ let sessionRes;
253
+
254
+ if (parsed.version === 2 && parsed.sessionId) {
255
+ // v2: Ultra-fast O(1) Primary Key lookup by id
256
+ const sql = `
257
+ SELECT s.id as session_id, s.user_id, s.token, s.is_active as session_active,
258
+ s.expires_at, s.last_activity, s.device_name, s.ip_address,
259
+ u.id as user_id, u.username, u.full_name, u.role, u.is_active as user_active,
260
+ COALESCE(u.is_disabled, false) as user_disabled,
261
+ u.employee_id, u.leader_id,
262
+ u.permissions as user_permissions,
263
+ r.permissions as role_permissions
264
+ FROM ${this.options.sessionsTable} s
265
+ JOIN ${this.options.usersTable} u ON u.id = s.user_id
266
+ LEFT JOIN ${this.options.rolesTable} r ON r.id = u.role OR r.name = u.role OR LOWER(r.name) = LOWER(u.role)
267
+ WHERE s.id = $1
268
+ `;
269
+ sessionRes = await this._query(sql, [parsed.sessionId]).catch(() => ({ rows: [] }));
270
+ } else {
271
+ // v1 (legacy): Direct token string matching
272
+ const sql = `
273
+ SELECT s.id as session_id, s.user_id, s.token, s.is_active as session_active,
274
+ s.expires_at, s.last_activity, s.device_name, s.ip_address,
275
+ u.id as user_id, u.username, u.full_name, u.role, u.is_active as user_active,
276
+ COALESCE(u.is_disabled, false) as user_disabled,
277
+ u.employee_id, u.leader_id,
278
+ u.permissions as user_permissions,
279
+ r.permissions as role_permissions
280
+ FROM ${this.options.sessionsTable} s
281
+ JOIN ${this.options.usersTable} u ON u.id = s.user_id
282
+ LEFT JOIN ${this.options.rolesTable} r ON r.id = u.role OR r.name = u.role OR LOWER(r.name) = LOWER(u.role)
283
+ WHERE s.token = $1
284
+ `;
285
+ sessionRes = await this._query(sql, [cleanToken]).catch(() => ({ rows: [] }));
286
+ }
287
+
288
+ if (sessionRes.rows && sessionRes.rows.length > 0) {
289
+ const row = sessionRes.rows[0];
290
+
291
+ // Security check: verify that row.token matches cleanToken
292
+ if (row.token !== cleanToken) {
293
+ return null;
294
+ }
295
+
296
+ const now = new Date();
297
+
298
+ // Check active status & expiration
299
+ if (!row.session_active || !row.user_active || row.user_disabled || new Date(row.expires_at) < now) {
300
+ return null;
301
+ }
302
+
303
+ // Check inactivity timeout
304
+ const inactTimeoutMs = await this.getInactivityTimeoutMs();
305
+ if (inactTimeoutMs > 0 && row.last_activity) {
306
+ const lastAct = new Date(row.last_activity).getTime();
307
+ if (now.getTime() - lastAct > inactTimeoutMs) {
308
+ this._query(`UPDATE ${this.options.sessionsTable} SET is_active = false WHERE id = $1`, [row.session_id]).catch(() => {});
309
+ return null;
310
+ }
311
+ }
312
+
313
+ // Asynchronously update last_activity
314
+ if (options.updateActivity !== false) {
315
+ this._query(`UPDATE ${this.options.sessionsTable} SET last_activity = NOW() WHERE id = $1`, [row.session_id]).catch(() => {});
316
+ }
317
+
318
+ return {
319
+ valid: true,
320
+ userId: row.user_id,
321
+ sessionId: row.session_id,
322
+ token: cleanToken,
323
+ role: row.role,
324
+ employeeId: row.employee_id || null,
325
+ leaderId: row.leader_id || null,
326
+ userPermissions: row.user_permissions || {},
327
+ rolePermissions: row.role_permissions || {},
328
+ user: {
329
+ id: row.user_id,
330
+ username: row.username,
331
+ fullName: row.full_name,
332
+ role: row.role,
333
+ employeeId: row.employee_id,
334
+ leaderId: row.leader_id,
335
+ },
336
+ };
337
+ }
338
+
339
+ // Legacy JWT fallback if enabled
340
+ if (this.options.jwtSecret) {
341
+ try {
342
+ const jwtModule = typeof require === 'function' ? require('jsonwebtoken') : null;
343
+ if (jwtModule) {
344
+ const decoded = jwtModule.verify(cleanToken, this.options.jwtSecret);
345
+ if (decoded && (decoded.userId || decoded.id)) {
346
+ const uId = decoded.userId || decoded.id;
347
+ const uRes = await this._query(
348
+ `SELECT u.id, u.username, u.full_name, u.role, u.is_active, COALESCE(u.is_disabled, false) as is_disabled,
349
+ u.employee_id, u.leader_id, u.permissions as user_permissions, r.permissions as role_permissions
350
+ FROM ${this.options.usersTable} u
351
+ LEFT JOIN ${this.options.rolesTable} r ON r.id = u.role OR r.name = u.role OR LOWER(r.name) = LOWER(u.role)
352
+ WHERE u.id = $1`,
353
+ [uId]
354
+ ).catch(() => ({ rows: [] }));
355
+
356
+ if (uRes.rows && uRes.rows.length > 0 && uRes.rows[0].is_active && !uRes.rows[0].is_disabled) {
357
+ const uRow = uRes.rows[0];
358
+ return {
359
+ valid: true,
360
+ userId: uRow.id,
361
+ sessionId: null,
362
+ token: cleanToken,
363
+ role: uRow.role || decoded.role,
364
+ employeeId: uRow.employee_id || null,
365
+ leaderId: uRow.leader_id || null,
366
+ userPermissions: uRow.user_permissions || {},
367
+ rolePermissions: uRow.role_permissions || {},
368
+ user: {
369
+ id: uRow.id,
370
+ username: uRow.username,
371
+ fullName: uRow.full_name,
372
+ role: uRow.role || decoded.role,
373
+ employeeId: uRow.employee_id,
374
+ leaderId: uRow.leader_id,
375
+ },
376
+ };
377
+ }
378
+ }
379
+ }
380
+ } catch {}
381
+ }
382
+
383
+ return null;
384
+ } catch (err) {
385
+ console.warn('[SessionManager validateSession error]:', err.message);
386
+ return null;
387
+ }
388
+ }
389
+
390
+ /**
391
+ * Lock a session screen.
392
+ *
393
+ * @param {string} sessionIdOrToken Session ID or Token
394
+ * @returns {Promise<{ ok: boolean, sessionId: string, userId: string, token: string } | null>}
395
+ */
396
+ async lockSession(sessionIdOrToken) {
397
+ if (!sessionIdOrToken) return null;
398
+ const parsed = parseToken(sessionIdOrToken, this.options.tokenPrefix);
399
+ const idToUse = parsed.sessionId || sessionIdOrToken;
400
+
401
+ const sql = `
402
+ UPDATE ${this.options.sessionsTable}
403
+ SET is_locked = true, locked_at = NOW()
404
+ WHERE (id = $1 OR token = $1) AND is_active = true
405
+ RETURNING id, user_id, token
406
+ `;
407
+ const r = await this._query(sql, [idToUse]);
408
+ if (r.rows && r.rows.length > 0) {
409
+ const row = r.rows[0];
410
+ return { ok: true, sessionId: row.id, userId: row.user_id, token: row.token };
411
+ }
412
+ return null;
413
+ }
414
+
415
+ /**
416
+ * Unlock a session screen.
417
+ *
418
+ * @param {string} sessionIdOrToken Session ID or Token
419
+ * @returns {Promise<{ ok: boolean, sessionId: string, userId: string, token: string } | null>}
420
+ */
421
+ async unlockSession(sessionIdOrToken) {
422
+ if (!sessionIdOrToken) return null;
423
+ const parsed = parseToken(sessionIdOrToken, this.options.tokenPrefix);
424
+ const idToUse = parsed.sessionId || sessionIdOrToken;
425
+
426
+ const sql = `
427
+ UPDATE ${this.options.sessionsTable}
428
+ SET is_locked = false, locked_at = NULL
429
+ WHERE (id = $1 OR token = $1) AND is_active = true
430
+ RETURNING id, user_id, token
431
+ `;
432
+ const r = await this._query(sql, [idToUse]);
433
+ if (r.rows && r.rows.length > 0) {
434
+ const row = r.rows[0];
435
+ return { ok: true, sessionId: row.id, userId: row.user_id, token: row.token };
436
+ }
437
+ return null;
438
+ }
439
+
440
+ /**
441
+ * Terminate/revoke a single session.
442
+ *
443
+ * @param {string} sessionIdOrToken Session ID or Token
444
+ * @returns {Promise<{ ok: boolean, sessionId: string, userId: string, token: string } | null>}
445
+ */
446
+ async terminateSession(sessionIdOrToken) {
447
+ if (!sessionIdOrToken) return null;
448
+ const parsed = parseToken(sessionIdOrToken, this.options.tokenPrefix);
449
+ const idToUse = parsed.sessionId || sessionIdOrToken;
450
+
451
+ const sql = `
452
+ UPDATE ${this.options.sessionsTable}
453
+ SET is_active = false
454
+ WHERE id = $1 OR token = $1
455
+ RETURNING id, user_id, token
456
+ `;
457
+ const r = await this._query(sql, [idToUse]);
458
+ if (r.rows && r.rows.length > 0) {
459
+ const row = r.rows[0];
460
+ return { ok: true, sessionId: row.id, userId: row.user_id, token: row.token };
461
+ }
462
+ return null;
463
+ }
464
+
465
+ /**
466
+ * Terminate all other sessions except the current one.
467
+ *
468
+ * @param {string} currentSessionIdOrToken Current session ID or token to preserve
469
+ * @param {string|number} [currentUserId]
470
+ * @param {boolean} [protectSuperAdmin=true]
471
+ * @returns {Promise<Array<{ sessionId: string, userId: string, token: string }>>}
472
+ */
473
+ async terminateOtherSessions(currentSessionIdOrToken, currentUserId = null, protectSuperAdmin = true) {
474
+ const parsed = parseToken(currentSessionIdOrToken, this.options.tokenPrefix);
475
+ const preserveId = parsed.sessionId || currentSessionIdOrToken;
476
+
477
+ let whereClause = `id != $1 AND token != $1`;
478
+ let params = [preserveId];
479
+
480
+ if (currentUserId) {
481
+ whereClause += ` AND user_id = $2`;
482
+ params.push(currentUserId);
483
+ } else if (protectSuperAdmin) {
484
+ whereClause += ` AND user_id NOT IN (SELECT id FROM ${this.options.usersTable} WHERE LOWER(COALESCE(role, '')) = '${this.options.superRole}' OR LOWER(username) = '${this.options.superRole}')`;
485
+ }
486
+
487
+ const sql = `
488
+ UPDATE ${this.options.sessionsTable}
489
+ SET is_active = false
490
+ WHERE ${whereClause} AND is_active = true
491
+ RETURNING id, user_id, token
492
+ `;
493
+ const r = await this._query(sql, params);
494
+ return (r.rows || []).map(row => ({
495
+ sessionId: row.id,
496
+ userId: row.user_id,
497
+ token: row.token,
498
+ }));
499
+ }
500
+
501
+ /**
502
+ * Terminate all active sessions for a specific user.
503
+ *
504
+ * @param {string|number} userId
505
+ * @returns {Promise<Array<{ sessionId: string, userId: string, token: string }>>}
506
+ */
507
+ async terminateUserSessions(userId) {
508
+ if (!userId) return [];
509
+ const sql = `
510
+ UPDATE ${this.options.sessionsTable}
511
+ SET is_active = false
512
+ WHERE user_id = $1 AND is_active = true
513
+ RETURNING id, user_id, token
514
+ `;
515
+ const r = await this._query(sql, [userId]);
516
+ return (r.rows || []).map(row => ({
517
+ sessionId: row.id,
518
+ userId: row.user_id,
519
+ token: row.token,
520
+ }));
521
+ }
522
+
523
+ /**
524
+ * Aliases for backward compatibility.
525
+ */
526
+ async revokeSession(tokenOrId) {
527
+ return this.terminateSession(tokenOrId);
528
+ }
529
+
530
+ async revokeAllUserSessions(userId) {
531
+ return this.terminateUserSessions(userId);
532
+ }
533
+ }
534
+
535
+ /**
536
+ * Universal Factory.
537
+ */
538
+ export function createSessionManager(options) {
539
+ return new SessionManager(options);
540
+ }
541
+
542
+ export default {
543
+ SessionManager,
544
+ createSessionManager,
545
+ formatToken,
546
+ parseToken,
547
+ getSessionId,
548
+ extractToken,
549
+ };