nakarte

Source code of https://map.sikmir.ru (fork)
git clone git://git.sikmir.ru/nakarte
Log | Files | Refs | LICENSE

index.js (6112B)


      1 import {openDB} from 'idb';
      2 
      3 const EVENT_STORED_SESSIONS_CHANGED = 'storedsessionschanged';
      4 const EVENT_ACTIVE_SESSIONS_CHANGED = 'activesessionschanged';
      5 
      6 class SessionRepository {
      7     static DB_NAME = 'sessions';
      8     static STORE_NAME = 'sessionData';
      9     static MAX_HISTORY_ENTRIES = 100;
     10     static MESSAGE_SESSION_CHANGED = 'sessionchanged';
     11 
     12     constructor() {
     13         this.channel = new BroadcastChannel('sessionrepository');
     14         this.channel.addEventListener('message', (e) => this.onChannelMessage(e));
     15         this.dbPromise = openDB(SessionRepository.DB_NAME, 1, {
     16             upgrade(db) {
     17                 const store = db.createObjectStore(SessionRepository.STORE_NAME, {keyPath: 'sessionId'});
     18                 store.createIndex('mtime', 'mtime', {unique: false});
     19             },
     20         });
     21     }
     22 
     23     onChannelMessage(e) {
     24         if (e.data.message === SessionRepository.MESSAGE_SESSION_CHANGED) {
     25             window.dispatchEvent(new Event(EVENT_STORED_SESSIONS_CHANGED));
     26         }
     27     }
     28 
     29     async listSessionStates() {
     30         const db = await this.dbPromise;
     31         return db.getAll(SessionRepository.STORE_NAME);
     32     }
     33 
     34     async getSessionState(sessionId) {
     35         const db = await this.dbPromise;
     36         const record = await db.get(SessionRepository.STORE_NAME, sessionId);
     37         return record?.data;
     38     }
     39 
     40     async pruneOldSessions() {
     41         const db = await this.dbPromise;
     42         const tx = db.transaction(SessionRepository.STORE_NAME, 'readwrite');
     43         const recordsCount = await tx.store.count();
     44         const recordsCountToDelete = recordsCount - SessionRepository.MAX_HISTORY_ENTRIES;
     45         if (recordsCountToDelete > 0) {
     46             let cursor = await tx.store.index('mtime').openCursor();
     47             for (let i = 0; i < recordsCountToDelete; i++) {
     48                 if (!cursor) {
     49                     break;
     50                 }
     51                 await cursor.delete();
     52                 cursor = await cursor.continue();
     53             }
     54             this.broadcastStorageChanged();
     55         }
     56     }
     57 
     58     async setSessionState(sessionId, data) {
     59         const db = await this.dbPromise;
     60         await db.put(SessionRepository.STORE_NAME, {
     61             sessionId,
     62             mtime: Date.now(),
     63             data,
     64         });
     65         this.broadcastStorageChanged();
     66         this.pruneOldSessions();
     67     }
     68 
     69     async clearSessionState(sessionId) {
     70         await (await this.dbPromise).delete(SessionRepository.STORE_NAME, sessionId);
     71         this.broadcastStorageChanged();
     72     }
     73 
     74     broadcastStorageChanged() {
     75         this.channel.postMessage({message: SessionRepository.MESSAGE_SESSION_CHANGED});
     76     }
     77 }
     78 
     79 const sessionRepository = new SessionRepository();
     80 
     81 class Session {
     82     constructor() {
     83         let sessionId = window.history.state?.sessionId;
     84         if (!sessionId) {
     85             sessionId = this.generateSessionId();
     86             window.history.replaceState({sessionId}, '');
     87         }
     88         this.sessionId = sessionId;
     89         window.addEventListener('popstate', () => window.history.replaceState({sessionId}, ''));
     90     }
     91 
     92     generateSessionId() {
     93         return Date.now().toString(36) + '_' + Math.random().toString(36).slice(2);
     94     }
     95 
     96     async loadState() {
     97         return sessionRepository.getSessionState(this.sessionId);
     98     }
     99 
    100     async saveState(data) {
    101         sessionRepository.setSessionState(this.sessionId, data);
    102     }
    103 
    104     async clearState() {
    105         sessionRepository.clearSessionState(this.sessionId);
    106     }
    107 }
    108 
    109 const session = new Session();
    110 
    111 class ActiveSessionsMonitor {
    112     static MESSAGE_REQUEST_SESSION = 'requestsessions';
    113     static MESSAGE_ACTIVE_SESSION = 'activesession';
    114     static MESSAGE_CLOSE_SESSION = 'closesession';
    115 
    116     constructor() {
    117         this.channel = new BroadcastChannel('sessions');
    118         this.channel.addEventListener('message', (e) => this.onChannelMessage(e));
    119         this._activeSessions = {};
    120         this.broadcastActiveSession();
    121         window.addEventListener('unload', () => this.broadcastSessionEnd());
    122         window.addEventListener('pagehide', () => this.broadcastSessionEnd());
    123         this.monitorRunning = false;
    124     }
    125 
    126     onChannelMessage(e) {
    127         switch (e.data.message) {
    128             case ActiveSessionsMonitor.MESSAGE_REQUEST_SESSION:
    129                 this.broadcastActiveSession();
    130                 break;
    131             case ActiveSessionsMonitor.MESSAGE_ACTIVE_SESSION:
    132                 if (!this.monitorRunning) {
    133                     break;
    134                 }
    135                 this._activeSessions[e.data.sessionId] = true;
    136                 this.notifyActiveSessionsChanged();
    137                 break;
    138             case ActiveSessionsMonitor.MESSAGE_CLOSE_SESSION:
    139                 if (!this.monitorRunning) {
    140                     break;
    141                 }
    142                 delete this._activeSessions[e.data.sessionId];
    143                 this.notifyActiveSessionsChanged();
    144                 break;
    145             default:
    146         }
    147     }
    148 
    149     startMonitor() {
    150         this._activeSessions = {};
    151         this.monitorRunning = true;
    152         this.requestActiveSessions();
    153     }
    154 
    155     stopMonitor() {
    156         this.monitorRunning = false;
    157         this._activeSessions = {};
    158     }
    159 
    160     broadcastActiveSession() {
    161         this.channel.postMessage({
    162             message: ActiveSessionsMonitor.MESSAGE_ACTIVE_SESSION,
    163             sessionId: session.sessionId,
    164         });
    165     }
    166 
    167     broadcastSessionEnd() {
    168         this.channel.postMessage({
    169             message: ActiveSessionsMonitor.MESSAGE_CLOSE_SESSION,
    170             sessionId: session.sessionId,
    171         });
    172     }
    173 
    174     notifyActiveSessionsChanged() {
    175         window.dispatchEvent(new Event(EVENT_ACTIVE_SESSIONS_CHANGED));
    176     }
    177 
    178     requestActiveSessions() {
    179         this.channel.postMessage({message: ActiveSessionsMonitor.MESSAGE_REQUEST_SESSION});
    180     }
    181 
    182     getActiveSessions() {
    183         return Object.keys(this._activeSessions);
    184     }
    185 }
    186 
    187 const activeSessionsMonitor = new ActiveSessionsMonitor();
    188 
    189 export {
    190     session,
    191     sessionRepository,
    192     activeSessionsMonitor,
    193     EVENT_STORED_SESSIONS_CHANGED,
    194     EVENT_ACTIVE_SESSIONS_CHANGED,
    195 };