UNPKG

homebridge-tsvesync

Version:

Homebridge plugin for VeSync devices including Levoit air purifiers, humidifiers, and Etekcity smart outlets

119 lines 5.33 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.decodeJwtTimestampsLocal = exports.FileSessionStore = void 0; const fs_1 = __importDefault(require("fs")); const path_1 = __importDefault(require("path")); class FileSessionStore { constructor(basePath, logger) { this.logger = logger; this.saveInProgress = null; this.dir = path_1.default.join(basePath, 'tsvesync'); this.file = path_1.default.join(this.dir, 'session.json'); } async load() { try { await fs_1.default.promises.mkdir(this.dir, { recursive: true }); const data = await fs_1.default.promises.readFile(this.file, 'utf8'); const session = JSON.parse(data); if (!session || !session.token || !session.accountId) return null; try { const expSec = session.expiresAt && session.expiresAt > 1e11 ? Math.floor(session.expiresAt / 1000) : session.expiresAt; const exp = expSec ? new Date(expSec * 1000).toISOString() : 'unknown'; this.logger.debug(`Loaded persisted session from ${this.file} (exp: ${exp})`); } catch (_a) { } return session; } catch (e) { if ((e === null || e === void 0 ? void 0 : e.code) === 'ENOENT') { this.logger.debug(`No persisted session found at ${this.file}`); } else { this.logger.debug(`Session load error: ${(e === null || e === void 0 ? void 0 : e.message) || e}`); } return null; } } async save(session) { // Use a mutex to prevent concurrent writes (race condition fix) if (this.saveInProgress) { await this.saveInProgress; } this.saveInProgress = (async () => { try { await fs_1.default.promises.mkdir(this.dir, { recursive: true }); // Load existing session to preserve fields like 'username' that may not be in the new session let existingSession = null; try { const data = await fs_1.default.promises.readFile(this.file, 'utf8'); existingSession = JSON.parse(data); } catch (_a) { // File doesn't exist or is corrupted, that's okay } // Merge: new session takes precedence, but preserve username if present const mergedSession = { ...existingSession, ...session, // Preserve username from existing session if new session doesn't have it username: session.username || (existingSession === null || existingSession === void 0 ? void 0 : existingSession.username), }; const tmp = this.file + '.tmp'; await fs_1.default.promises.writeFile(tmp, JSON.stringify(mergedSession), { encoding: 'utf8', mode: 0o600 }); await fs_1.default.promises.rename(tmp, this.file); try { await fs_1.default.promises.chmod(this.file, 0o600); } catch ( /* best effort */_b) { /* best effort */ } try { const expSec = session.expiresAt && session.expiresAt > 1e11 ? Math.floor(session.expiresAt / 1000) : session.expiresAt; const exp = expSec ? new Date(expSec * 1000).toISOString() : 'unknown'; this.logger.debug(`Persisted session to ${this.file} (exp: ${exp})`); } catch (_c) { } } catch (e) { this.logger.error(`Session save error: ${(e === null || e === void 0 ? void 0 : e.message) || e}`); // Changed to ERROR for visibility } finally { this.saveInProgress = null; } })(); await this.saveInProgress; } async clear() { try { await fs_1.default.promises.unlink(this.file); } catch (e) { if ((e === null || e === void 0 ? void 0 : e.code) !== 'ENOENT') { this.logger.debug(`Session clear error: ${(e === null || e === void 0 ? void 0 : e.message) || e}`); } } } } exports.FileSessionStore = FileSessionStore; function decodeJwtTimestampsLocal(token) { try { const parts = token.split('.'); if (parts.length !== 3) return null; const payload = JSON.parse(Buffer.from(parts[1].replace(/-/g, '+').replace(/_/g, '/'), 'base64').toString('utf8')); let iat = typeof payload.iat === 'number' ? payload.iat : undefined; let exp = typeof payload.exp === 'number' ? payload.exp : undefined; if (iat && iat > 1e11) iat = Math.floor(iat / 1000); if (exp && exp > 1e11) exp = Math.floor(exp / 1000); return { iat, exp }; } catch (_a) { return null; } } exports.decodeJwtTimestampsLocal = decodeJwtTimestampsLocal; //# sourceMappingURL=session-store.js.map