import session from 'express-session'; import fs from 'fs'; import path from 'path'; interface SessionFile { session: session.SessionData; expires: number; } export class JsonSessionStore extends session.Store { _dir: string; constructor( dir: string ) { super(); this._dir = dir; if ( ! fs.existsSync( dir ) ) fs.mkdirSync( dir, { recursive: true } ); } _filePath( sid: string ): string { const safe = sid.replace( /[^a-zA-Z0-9_-]/g, '_' ); return path.join( this._dir, safe + '.json' ); } get( sid: string, callback: ( err: any, session?: session.SessionData ) => void ): void { try { const fp = this._filePath( sid ); if ( ! fs.existsSync( fp ) ) { callback( null, null ); return; } const data: SessionFile = JSON.parse( fs.readFileSync( fp, 'utf8' ) ); if ( Date.now() > data.expires ) { fs.unlinkSync( fp ); callback( null, null ); return; } callback( null, data.session ); } catch ( e ) { callback( e ); } } set( sid: string, sessionData: session.SessionData, callback?: ( err?: any ) => void ): void { try { const fp = this._filePath( sid ); const expires = sessionData.cookie?.expires ? new Date( sessionData.cookie.expires ).getTime() : Date.now() + 7 * 24 * 60 * 60 * 1000; const file: SessionFile = { session: sessionData, expires }; fs.writeFileSync( fp, JSON.stringify( file ), 'utf8' ); callback?.(); } catch ( e ) { callback?.( e ); } } destroy( sid: string, callback?: ( err?: any ) => void ): void { try { const fp = this._filePath( sid ); if ( fs.existsSync( fp ) ) fs.unlinkSync( fp ); callback?.(); } catch ( e ) { callback?.( e ); } } }