1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
|
import { mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
import { dirname, join } from "node:path";
/**
* Small service-state file kept next to the Matter fabric storage, with one
* entry per commissioned node (keyed by node ID as a decimal string). Written
* atomically (temp file + rename) so a power loss mid-write never corrupts it.
*/
export interface NodeState {
lastSuccessfulConnection: string | null;
lastSuccessfulSync: string | null;
lastAttemptedSync: string | null;
lastError: string | null;
}
export interface ServiceState {
nodes: Record<string, NodeState>;
}
export const EMPTY_NODE_STATE: NodeState = {
lastSuccessfulConnection: null,
lastSuccessfulSync: null,
lastAttemptedSync: null,
lastError: null,
};
export function serviceStatePath(storagePath: string): string {
return join(storagePath, "service-state.json");
}
export function readServiceState(path: string): ServiceState {
let raw: string;
try {
raw = readFileSync(path, "utf8");
} catch {
return { nodes: {} };
}
try {
const parsed = JSON.parse(raw) as Partial<ServiceState>;
const nodes: Record<string, NodeState> = {};
if (typeof parsed.nodes === "object" && parsed.nodes !== null) {
for (const [nodeId, value] of Object.entries(parsed.nodes)) {
if (!/^\d+$/.test(nodeId) || typeof value !== "object" || value === null) continue;
const entry = value as Partial<NodeState>;
nodes[nodeId] = {
lastSuccessfulConnection: stringOrNull(entry.lastSuccessfulConnection),
lastSuccessfulSync: stringOrNull(entry.lastSuccessfulSync),
lastAttemptedSync: stringOrNull(entry.lastAttemptedSync),
lastError: stringOrNull(entry.lastError),
};
}
}
return { nodes };
} catch {
// A corrupt state file is not fatal; it only holds status metadata.
return { nodes: {} };
}
}
export function writeServiceState(path: string, state: ServiceState): void {
mkdirSync(dirname(path), { recursive: true, mode: 0o700 });
const temp = `${path}.tmp`;
writeFileSync(temp, JSON.stringify(state, null, 2) + "\n", { mode: 0o600 });
renameSync(temp, path);
}
export function nodeState(state: ServiceState, nodeId: bigint): NodeState {
return state.nodes[nodeId.toString()] ?? { ...EMPTY_NODE_STATE };
}
/** Merges a patch into one node's entry and persists the result. */
export function updateNodeState(path: string, nodeId: bigint, patch: Partial<NodeState>): ServiceState {
const state = readServiceState(path);
const key = nodeId.toString();
state.nodes[key] = { ...(state.nodes[key] ?? EMPTY_NODE_STATE), ...patch };
writeServiceState(path, state);
return state;
}
/** Drops one node's entry (after decommissioning) and persists the result. */
export function removeNodeState(path: string, nodeId: bigint): ServiceState {
const state = readServiceState(path);
delete state.nodes[nodeId.toString()];
writeServiceState(path, state);
return state;
}
function stringOrNull(value: unknown): string | null {
return typeof value === "string" ? value : null;
}
|