homebridge-tsvesync
Version:
Homebridge plugin for VeSync devices including Levoit air purifiers, humidifiers, and Etekcity smart outlets
119 lines • 5.33 kB
JavaScript
;
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