Stateful implementation of the user session.
npm install @thermopylae/lib.user-session> Stateful implementation of the user session.
``sh`
npm install @thermopylae/lib.user-session
First, you need to implement storage for user sessions. In this example we will build
an in-memory storage, although it's recommended to implement a persistent storage (e.g. by using Redis).
`typescript
// storage.ts
import type { Seconds } from '@thermopylae/core.declarations';
import {
PolicyBasedCache,
AbsoluteExpirationPolicyArgumentsBundle,
BucketGarbageCollector,
EsMapCacheBackend,
ProactiveExpirationPolicy,
CacheEvent
} from '@thermopylae/lib.cache';
import type { SessionId, DeviceBase } from '@thermopylae/lib.user-session.commons';
import type { UserSessionMetaData, UserSessionsStorage } from '@thermopylae/lib.user-session';
class InMemoryUserSessionStorage implements UserSessionsStorage
private readonly cache: PolicyBasedCache
private readonly userSessions: Map
public constructor() {
const backend = new EsMapCacheBackend
const policies = [new ProactiveExpirationPolicy
this.cache = new PolicyBasedCache(backend, policies);
this.userSessions = new Map
this.cache.on(CacheEvent.DELETE, (sessionIdKey) => {
const [subject, sessionId] = InMemoryUserSessionStorage.decodeSessionIdKey(sessionIdKey);
const sessions = this.userSessions.get(subject)!;
sessions.delete(sessionId);
if (sessions.size === 0) {
this.userSessions.delete(subject);
}
});
}
public async insert(subject: string, sessionId: SessionId, metaData: UserSessionMetaData
let sessions = this.userSessions.get(subject);
if (sessions == null) {
sessions = new Set
this.userSessions.set(subject, sessions);
}
sessions.add(sessionId);
this.cache.set(InMemoryUserSessionStorage.sessionIdKey(subject, sessionId), metaData, { expiresAfter: ttl });
}
public async read(subject: string, sessionId: SessionId): Promise
return this.cache.get(InMemoryUserSessionStorage.sessionIdKey(subject, sessionId));
}
public async readAll(subject: string): Promise
const sessions = this.userSessions.get(subject);
if (sessions == null) {
return new Map();
}
const sessionsMetaData = new Map
for (const sessionId of sessions) {
sessionsMetaData.set(sessionId, this.cache.get(InMemoryUserSessionStorage.sessionIdKey(subject, sessionId))!);
}
return sessionsMetaData;
}
public async updateAccessedAt(subject: string, sessionId: SessionId, metaData: UserSessionMetaData
this.cache.set(InMemoryUserSessionStorage.sessionIdKey(subject, sessionId), metaData);
}
public async delete(subject: string, sessionId: SessionId): Promise
this.cache.del(InMemoryUserSessionStorage.sessionIdKey(subject, sessionId));
}
public async deleteAll(subject: string): Promise
const sessions = Array.from(this.userSessions.get(subject) || new Set
for (const sessionId of sessions) {
this.cache.del(InMemoryUserSessionStorage.sessionIdKey(subject, sessionId));
}
return sessions.length;
}
private static sessionIdKey(subject: string, sessionId: SessionId): string {
return ${subject}:${sessionId};
}
private static decodeSessionIdKey(sessionIdKey: string): [string, SessionId] {
return sessionIdKey.split(':') as [string, SessionId];
}
}
export { InMemoryUserSessionStorage };
`
After that, we can create our UserSessionManager instance and manage user sessions.
`typescript
// session.ts
import { UserSessionManager } from '@thermopylae/lib.user-session';
import { InMemoryUserSessionStorage } from './storage';
const manager = new UserSessionManager({
idLength: 24,
sessionTtl: 86_400, // 24h
timeouts: {
idle: 1_800, // 30 min
renewal: 43_200, // 12h
oldSessionAvailabilityAfterRenewal: 5 // 5 seconds
},
storage: new InMemoryUserSessionStorage(),
renewSessionHooks: {
onRenewMadeAlreadyFromCurrentProcess(sessionId) {
console.warn(
Can't renew session '${UserSessionManager.hash(sessionId)}', because it was renewed already. Renew has been made from this NodeJS process.Can't renew session '${UserSessionManager.hash(sessionId)}', because it was renewed already. Renew has been made from another NodeJS process.
);
},
onRenewMadeAlreadyFromAnotherProcess(sessionId) {
console.warn(
Failed to delete renewed session '${UserSessionManager.hash(sessionId)}'.
);
},
onOldSessionDeleteFailure(sessionId, error) {
console.error(, error);
}
}
});
(async function main() {
/ Create session /
let sessionId = await manager.create('uid1', { ip: '127.0.0.1' });
/ Read it /
const [sessionMetaData, renewedSessionId] = await manager.read('uid1', sessionId, { ip: '127.0.0.1' });
console.log(Session meta data associated with session id '${sessionId}': ${JSON.stringify(sessionMetaData)});User session was renewed and the new session id '${renewedSessionId}' needs to be sent to client.
if (renewedSessionId != null) {
console.warn();
sessionId = renewedSessionId; // the old one is no longer valid
}
/ Read all active sessions /
const activeSessions = await manager.readAll('uid1');
console.log(User with id 'uid1' has ${activeSessions.size} active sessions.);
/ Delete session /
await manager.delete('uid1', sessionId);
/ Delete all sessions /
const deletedSessionsNo = await manager.deleteAll('uid1');
console.info(Deleted ${deletedSessionsNo} active sessions of user with id 'uid1'.);`
})();
It can also be generated by issuing the following commands:
`shell``
git clone git@github.com:marinrusu1997/thermopylae.git
cd thermopylae
yarn install
yarn workspace @thermopylae/lib.user-session run doc
* GitHub: @marinrusu1997
* Email: dimarusu2000@gmail.com
* LinkedIn: @marinrusu1997
[api-doc-link]: https://marinrusu1997.github.io/thermopylae/lib.user-session/index.html