livedesk 0.1.488 → 0.1.490
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/client/bin/livedesk-client.js +119 -44
- package/client/package.json +1 -1
- package/client/src/runtime/fast-runtime-repair.js +300 -0
- package/hub/package.json +32 -32
- package/hub/src/remote-hub.js +728 -722
- package/hub/src/settings/settings-schema.js +251 -239
- package/hub/src/settings/settings-store.js +82 -77
- package/hub/src/transport/secure-direct-acceptor.js +1 -1
- package/package.json +2 -2
- package/web/dist/assets/{index-KNTHLeo-.js → index-BRPz6Wux.js} +1 -1
- package/web/dist/index.html +1 -1
- package/web/dist/livedesk-build-evidence.json +7 -7
|
@@ -1,77 +1,82 @@
|
|
|
1
|
-
import { mkdir, readFile, rename, writeFile } from 'node:fs/promises';
|
|
2
|
-
import path from 'node:path';
|
|
3
|
-
import { DEFAULT_LIVEDESK_SETTINGS, defaultSettingsPath, normalizeLiveDeskSettings, publicSettings } from './settings-schema.js';
|
|
4
|
-
|
|
5
|
-
export class SettingsConflictError extends Error {
|
|
6
|
-
constructor(settings) {
|
|
7
|
-
super('Settings changed in another session. Reload before saving again.');
|
|
8
|
-
this.name = 'SettingsConflictError';
|
|
9
|
-
this.code = 'settings-revision-conflict';
|
|
10
|
-
this.status = 409;
|
|
11
|
-
this.settings = settings;
|
|
12
|
-
}
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
async function writeJsonAtomic(filePath, value) {
|
|
16
|
-
await mkdir(path.dirname(filePath), { recursive: true });
|
|
17
|
-
const temporaryPath = `${filePath}.${process.pid}.${Date.now()}.tmp`;
|
|
18
|
-
await writeFile(temporaryPath, `${JSON.stringify(value, null, 2)}\n`, { encoding: 'utf8', mode: 0o600 });
|
|
19
|
-
await rename(temporaryPath, filePath);
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export class LiveDeskSettingsStore {
|
|
23
|
-
constructor({ dataDir } = {}) {
|
|
24
|
-
this.filePath = defaultSettingsPath(dataDir);
|
|
25
|
-
this.record = null;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
async getRecord() {
|
|
29
|
-
if (this.record) return structuredClone(this.record);
|
|
30
|
-
try {
|
|
31
|
-
const raw = JSON.parse(await readFile(this.filePath, 'utf8'));
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
return
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
const
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
}
|
|
63
|
-
const
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
settings:
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
return
|
|
76
|
-
}
|
|
77
|
-
|
|
1
|
+
import { mkdir, readFile, rename, writeFile } from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { DEFAULT_LIVEDESK_SETTINGS, SETTINGS_SCHEMA_VERSION, defaultSettingsPath, migrateLiveDeskSettings, normalizeLiveDeskSettings, publicSettings } from './settings-schema.js';
|
|
4
|
+
|
|
5
|
+
export class SettingsConflictError extends Error {
|
|
6
|
+
constructor(settings) {
|
|
7
|
+
super('Settings changed in another session. Reload before saving again.');
|
|
8
|
+
this.name = 'SettingsConflictError';
|
|
9
|
+
this.code = 'settings-revision-conflict';
|
|
10
|
+
this.status = 409;
|
|
11
|
+
this.settings = settings;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
async function writeJsonAtomic(filePath, value) {
|
|
16
|
+
await mkdir(path.dirname(filePath), { recursive: true });
|
|
17
|
+
const temporaryPath = `${filePath}.${process.pid}.${Date.now()}.tmp`;
|
|
18
|
+
await writeFile(temporaryPath, `${JSON.stringify(value, null, 2)}\n`, { encoding: 'utf8', mode: 0o600 });
|
|
19
|
+
await rename(temporaryPath, filePath);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export class LiveDeskSettingsStore {
|
|
23
|
+
constructor({ dataDir } = {}) {
|
|
24
|
+
this.filePath = defaultSettingsPath(dataDir);
|
|
25
|
+
this.record = null;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async getRecord() {
|
|
29
|
+
if (this.record) return structuredClone(this.record);
|
|
30
|
+
try {
|
|
31
|
+
const raw = JSON.parse(await readFile(this.filePath, 'utf8'));
|
|
32
|
+
const sourceSettings = raw?.settings || raw;
|
|
33
|
+
const sourceSchemaVersion = Math.max(0, Number(sourceSettings?.settingsSchemaVersion) || 0);
|
|
34
|
+
this.record = {
|
|
35
|
+
revision: Math.max(0, Number(raw?.revision) || 0),
|
|
36
|
+
updatedAt: String(raw?.updatedAt || ''),
|
|
37
|
+
settings: migrateLiveDeskSettings(sourceSettings)
|
|
38
|
+
};
|
|
39
|
+
if (sourceSchemaVersion < SETTINGS_SCHEMA_VERSION) {
|
|
40
|
+
await writeJsonAtomic(this.filePath, this.record);
|
|
41
|
+
}
|
|
42
|
+
} catch {
|
|
43
|
+
this.record = { revision: 0, updatedAt: '', settings: normalizeLiveDeskSettings(DEFAULT_LIVEDESK_SETTINGS) };
|
|
44
|
+
}
|
|
45
|
+
return structuredClone(this.record);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
getCached() {
|
|
49
|
+
if (!this.record) return DEFAULT_LIVEDESK_SETTINGS;
|
|
50
|
+
return { ...this.record.settings, revision: this.record.revision };
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
async get() {
|
|
54
|
+
return publicSettings((await this.getRecord()).settings);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async update(patch = {}, expectedRevision = undefined) {
|
|
58
|
+
const current = await this.getRecord();
|
|
59
|
+
if (expectedRevision !== undefined && Number(expectedRevision) !== current.revision) {
|
|
60
|
+
throw new SettingsConflictError({ revision: current.revision, updatedAt: current.updatedAt, settings: publicSettings(current.settings) });
|
|
61
|
+
}
|
|
62
|
+
const source = patch && typeof patch === 'object' && !Array.isArray(patch) ? patch : {};
|
|
63
|
+
const merged = {};
|
|
64
|
+
for (const section of Object.keys(current.settings)) {
|
|
65
|
+
if (section === 'settingsSchemaVersion') continue;
|
|
66
|
+
merged[section] = { ...current.settings[section], ...(source[section] && typeof source[section] === 'object' ? source[section] : {}) };
|
|
67
|
+
}
|
|
68
|
+
const next = {
|
|
69
|
+
revision: current.revision + 1,
|
|
70
|
+
updatedAt: new Date().toISOString(),
|
|
71
|
+
settings: normalizeLiveDeskSettings(merged)
|
|
72
|
+
};
|
|
73
|
+
await writeJsonAtomic(this.filePath, next);
|
|
74
|
+
this.record = next;
|
|
75
|
+
return structuredClone(next);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
async reset() {
|
|
79
|
+
const current = await this.getRecord();
|
|
80
|
+
return this.update(normalizeLiveDeskSettings(DEFAULT_LIVEDESK_SETTINGS), current.revision);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
@@ -180,7 +180,7 @@ export function createSecureDirectAcceptor({
|
|
|
180
180
|
remoteAddress: ip
|
|
181
181
|
});
|
|
182
182
|
try { rawSocket.write(`${JSON.stringify({ type: 'secure.error', protocol: SECURE_SESSION_PROTOCOL, error: code })}\n`); } catch {}
|
|
183
|
-
logWarn('security', `Secure Direct handshake rejected reason=${code}`);
|
|
183
|
+
logWarn('security', `Secure Direct handshake rejected reason=${code} remote=${ip || 'unknown'}`);
|
|
184
184
|
rawSocket.destroy();
|
|
185
185
|
}
|
|
186
186
|
|
package/package.json
CHANGED