@fitzdoud/valun 0.0.0-stage → 0.3.1
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/LICENSE +21 -0
- package/README.md +259 -2
- package/package.json +53 -4
- package/src/agent.js +590 -0
- package/src/basis.js +243 -0
- package/src/blake2b.js +137 -0
- package/src/bytes.js +60 -0
- package/src/client.js +565 -0
- package/src/dns.d.ts +4 -0
- package/src/dns.js +12 -0
- package/src/errors.js +18 -0
- package/src/file-store.d.ts +16 -0
- package/src/file-store.js +78 -0
- package/src/index.d.ts +650 -0
- package/src/index.js +24 -0
- package/src/measure.js +214 -0
- package/src/protocol.js +167 -0
- package/src/py.js +318 -0
- package/src/random.js +24 -0
- package/src/sandbox.js +131 -0
- package/src/schemas.js +157 -0
- package/src/sha256.js +52 -0
- package/src/tools.js +112 -0
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
// Node-only store: one private JSON file holding settings, the agent's credentials and its own record of credit notes
|
|
2
|
+
// issued. Same file layout as the Python client's FileStore (state keys plus "secret" and "agent_token" at the top
|
|
3
|
+
// level, 1-space indent), so either client can read the other's file. Imported separately ("@fitzdoud/valun/file-store") so
|
|
4
|
+
// that browser bundles of the main entry never pull in node:fs.
|
|
5
|
+
|
|
6
|
+
import fs from 'node:fs';
|
|
7
|
+
import path from 'node:path';
|
|
8
|
+
import {ValunError} from './errors.js';
|
|
9
|
+
|
|
10
|
+
const POSIX = process.platform !== 'win32';
|
|
11
|
+
|
|
12
|
+
export class FileStore {
|
|
13
|
+
#secret = null;
|
|
14
|
+
#token = null;
|
|
15
|
+
|
|
16
|
+
/** Loads `filePath` if it exists. Nothing is created until save(). */
|
|
17
|
+
constructor(filePath) {
|
|
18
|
+
if (typeof filePath !== 'string' || !filePath) throw new TypeError('FileStore needs a file path');
|
|
19
|
+
this.path = filePath;
|
|
20
|
+
this.state = {};
|
|
21
|
+
if (!fs.existsSync(filePath)) return;
|
|
22
|
+
let parsed;
|
|
23
|
+
try {
|
|
24
|
+
parsed = JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
|
25
|
+
} catch {
|
|
26
|
+
// never echo the parser's message: it quotes the file, which holds the key
|
|
27
|
+
throw new ValunError('bad_store', `${filePath} is not a valid Valun state file (not JSON)`);
|
|
28
|
+
}
|
|
29
|
+
if (parsed === null || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
|
30
|
+
throw new ValunError('bad_store', `${filePath} is not a valid Valun state file (not a JSON object)`);
|
|
31
|
+
}
|
|
32
|
+
const {secret = null, agent_token = null, ...state} = parsed;
|
|
33
|
+
this.#secret = secret;
|
|
34
|
+
this.#token = agent_token;
|
|
35
|
+
this.state = state;
|
|
36
|
+
if (POSIX) {
|
|
37
|
+
try {
|
|
38
|
+
if (fs.statSync(filePath).mode & 0o077) fs.chmodSync(filePath, 0o600); // tighten a file others can read
|
|
39
|
+
} catch { /* best effort */ }
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
secret() { return this.#secret; }
|
|
44
|
+
token() { return this.#token; }
|
|
45
|
+
|
|
46
|
+
saveCredentials(secretHex, token) {
|
|
47
|
+
this.#secret = secretHex;
|
|
48
|
+
this.#token = token;
|
|
49
|
+
this.save();
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** Atomic write: a fresh temporary file created with mode 0600 (O_EXCL), then renamed over the state file. */
|
|
53
|
+
save() {
|
|
54
|
+
const dir = path.dirname(path.resolve(this.path));
|
|
55
|
+
fs.mkdirSync(dir, {recursive: true, mode: 0o700});
|
|
56
|
+
const data = {...this.state};
|
|
57
|
+
delete data.secret;
|
|
58
|
+
delete data.agent_token;
|
|
59
|
+
if (this.#secret !== null) data.secret = this.#secret;
|
|
60
|
+
if (this.#token !== null) data.agent_token = this.#token;
|
|
61
|
+
const tmp = `${this.path}.${process.pid}.${Date.now().toString(36)}${Math.random().toString(36).slice(2, 8)}.tmp`;
|
|
62
|
+
const fd = fs.openSync(tmp, 'wx', 0o600);
|
|
63
|
+
try {
|
|
64
|
+
if (POSIX) fs.fchmodSync(fd, 0o600);
|
|
65
|
+
fs.writeSync(fd, JSON.stringify(data, null, 1));
|
|
66
|
+
fs.fsyncSync(fd);
|
|
67
|
+
} catch (e) {
|
|
68
|
+
fs.closeSync(fd);
|
|
69
|
+
try { fs.unlinkSync(tmp); } catch { /* already gone */ }
|
|
70
|
+
throw e;
|
|
71
|
+
}
|
|
72
|
+
fs.closeSync(fd);
|
|
73
|
+
fs.renameSync(tmp, this.path);
|
|
74
|
+
if (POSIX) {
|
|
75
|
+
try { fs.chmodSync(this.path, 0o600); } catch { /* best effort, as the Python client */ }
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|