@trillboards/edge-core 0.2.2 → 0.2.3
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/dist/MemoryAttenuationManager.d.ts +164 -0
- package/dist/MemoryAttenuationManager.d.ts.map +1 -0
- package/dist/MemoryAttenuationManager.js +400 -0
- package/dist/MemoryAttenuationManager.js.map +1 -0
- package/dist/ModelManager.d.ts +166 -0
- package/dist/ModelManager.d.ts.map +1 -0
- package/dist/ModelManager.js +580 -0
- package/dist/ModelManager.js.map +1 -0
- package/dist/StatusServer.d.ts +99 -0
- package/dist/StatusServer.d.ts.map +1 -0
- package/dist/StatusServer.js +262 -0
- package/dist/StatusServer.js.map +1 -0
- package/dist/ZKAudienceProver.d.ts +137 -0
- package/dist/ZKAudienceProver.d.ts.map +1 -0
- package/dist/ZKAudienceProver.js +321 -0
- package/dist/ZKAudienceProver.js.map +1 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +11 -1
- package/dist/index.js.map +1 -1
- package/dist/types.d.ts +40 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,580 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* ModelManager — Model lifecycle management with OTA (over-the-air) updates.
|
|
4
|
+
* Port of CTV agent's ModelManager.kt (387 lines), adapted for Node.js/ONNX.
|
|
5
|
+
*
|
|
6
|
+
* Manages registration, version-checking, and atomic download of ML model files.
|
|
7
|
+
* Downloads use a .tmp + rename pattern to prevent corrupted model loads.
|
|
8
|
+
* The version check timer is unref()'d so it does not keep the process alive.
|
|
9
|
+
*
|
|
10
|
+
* Events emitted:
|
|
11
|
+
* 'modelUpdated' — { name, oldVersion, newVersion, path }
|
|
12
|
+
* 'updateAvailable' — { name, currentVersion, availableVersion }
|
|
13
|
+
* 'downloadProgress' — { name, bytesDownloaded, totalBytes, percent }
|
|
14
|
+
* 'downloadError' — { name, error }
|
|
15
|
+
* 'checkComplete' — { checkedCount, updatesAvailable }
|
|
16
|
+
*/
|
|
17
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
18
|
+
if (k2 === undefined) k2 = k;
|
|
19
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
20
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
21
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
22
|
+
}
|
|
23
|
+
Object.defineProperty(o, k2, desc);
|
|
24
|
+
}) : (function(o, m, k, k2) {
|
|
25
|
+
if (k2 === undefined) k2 = k;
|
|
26
|
+
o[k2] = m[k];
|
|
27
|
+
}));
|
|
28
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
29
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
30
|
+
}) : function(o, v) {
|
|
31
|
+
o["default"] = v;
|
|
32
|
+
});
|
|
33
|
+
var __importStar = (this && this.__importStar) || function (mod) {
|
|
34
|
+
if (mod && mod.__esModule) return mod;
|
|
35
|
+
var result = {};
|
|
36
|
+
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
37
|
+
__setModuleDefault(result, mod);
|
|
38
|
+
return result;
|
|
39
|
+
};
|
|
40
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
41
|
+
exports.ModelManager = void 0;
|
|
42
|
+
const events_1 = require("events");
|
|
43
|
+
const fs = __importStar(require("fs"));
|
|
44
|
+
const path = __importStar(require("path"));
|
|
45
|
+
const https = __importStar(require("https"));
|
|
46
|
+
const http = __importStar(require("http"));
|
|
47
|
+
const crypto = __importStar(require("crypto"));
|
|
48
|
+
// ─── Implementation ─────────────────────────────────────────────────────────
|
|
49
|
+
class ModelManager extends events_1.EventEmitter {
|
|
50
|
+
constructor(options) {
|
|
51
|
+
super();
|
|
52
|
+
this.models = new Map();
|
|
53
|
+
this.checkTimer = null;
|
|
54
|
+
this.downloading = new Set();
|
|
55
|
+
this.options = {
|
|
56
|
+
modelsDir: options.modelsDir,
|
|
57
|
+
apiBaseUrl: options.apiBaseUrl.replace(/\/+$/, ''),
|
|
58
|
+
deviceFingerprint: options.deviceFingerprint,
|
|
59
|
+
checkIntervalMs: options.checkIntervalMs ?? 3600000,
|
|
60
|
+
maxConcurrentDownloads: options.maxConcurrentDownloads ?? 1,
|
|
61
|
+
autoUpdate: options.autoUpdate ?? true,
|
|
62
|
+
requestTimeoutMs: options.requestTimeoutMs ?? 30000,
|
|
63
|
+
downloadTimeoutMs: options.downloadTimeoutMs ?? 300000,
|
|
64
|
+
maxRedirects: options.maxRedirects ?? 5,
|
|
65
|
+
};
|
|
66
|
+
this.manifestPath = path.join(this.options.modelsDir, 'models_manifest.json');
|
|
67
|
+
}
|
|
68
|
+
// ─── Lifecycle ──────────────────────────────────────────────────────────────
|
|
69
|
+
/**
|
|
70
|
+
* Initialize — ensure models directory exists, load manifest, compute
|
|
71
|
+
* hashes for any registered model files already on disk.
|
|
72
|
+
*/
|
|
73
|
+
async initialize() {
|
|
74
|
+
fs.mkdirSync(this.options.modelsDir, { recursive: true });
|
|
75
|
+
this.loadManifest();
|
|
76
|
+
// For every model that was loaded from the manifest, verify the file
|
|
77
|
+
// is still on disk and recompute its hash if needed.
|
|
78
|
+
const verifyPromises = [];
|
|
79
|
+
for (const [name, info] of this.models) {
|
|
80
|
+
verifyPromises.push(this.verifyModelOnDisk(name, info));
|
|
81
|
+
}
|
|
82
|
+
await Promise.all(verifyPromises);
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Start periodic version checking. The timer is unref()'d so it does not
|
|
86
|
+
* prevent the Node.js process from exiting naturally.
|
|
87
|
+
*/
|
|
88
|
+
start() {
|
|
89
|
+
if (this.checkTimer)
|
|
90
|
+
return;
|
|
91
|
+
this.checkTimer = setInterval(() => {
|
|
92
|
+
this.checkForUpdates().catch((err) => {
|
|
93
|
+
this.emit('downloadError', {
|
|
94
|
+
name: '*',
|
|
95
|
+
error: `Periodic check failed: ${String(err)}`,
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
}, this.options.checkIntervalMs);
|
|
99
|
+
// unref() so this timer alone does not keep the process alive
|
|
100
|
+
this.checkTimer.unref();
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Stop periodic checking and cancel any in-flight state tracking.
|
|
104
|
+
*/
|
|
105
|
+
stop() {
|
|
106
|
+
if (this.checkTimer) {
|
|
107
|
+
clearInterval(this.checkTimer);
|
|
108
|
+
this.checkTimer = null;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
// ─── Registration ───────────────────────────────────────────────────────────
|
|
112
|
+
/**
|
|
113
|
+
* Register a model to track. Called during EdgeAgent init for each model type.
|
|
114
|
+
* If the file exists on disk, its SHA-256 is computed immediately.
|
|
115
|
+
* If the manifest already has version info for this name, it is merged.
|
|
116
|
+
*/
|
|
117
|
+
registerModel(name, filePath) {
|
|
118
|
+
const absPath = path.resolve(filePath);
|
|
119
|
+
const existing = this.models.get(name);
|
|
120
|
+
const info = {
|
|
121
|
+
name,
|
|
122
|
+
version: existing?.version ?? '0.0.0',
|
|
123
|
+
filePath: absPath,
|
|
124
|
+
sha256: existing?.sha256 ?? '',
|
|
125
|
+
sizeBytes: existing?.sizeBytes ?? 0,
|
|
126
|
+
lastChecked: existing?.lastChecked ?? 0,
|
|
127
|
+
lastUpdated: existing?.lastUpdated ?? 0,
|
|
128
|
+
};
|
|
129
|
+
// If the file exists, populate size synchronously (hash is async, done in initialize)
|
|
130
|
+
if (fs.existsSync(absPath)) {
|
|
131
|
+
const stat = fs.statSync(absPath);
|
|
132
|
+
info.sizeBytes = stat.size;
|
|
133
|
+
}
|
|
134
|
+
this.models.set(name, info);
|
|
135
|
+
}
|
|
136
|
+
// ─── Update Checking ───────────────────────────────────────────────────────
|
|
137
|
+
/**
|
|
138
|
+
* Check for updates for all registered models by calling the backend API.
|
|
139
|
+
* Endpoint: GET /v1/federated/models?deviceFingerprint=X&modelType=Y
|
|
140
|
+
*
|
|
141
|
+
* Returns the list of ModelVersionResponse objects for which a newer
|
|
142
|
+
* version is available. If autoUpdate is enabled, downloads are
|
|
143
|
+
* initiated automatically (respecting maxConcurrentDownloads).
|
|
144
|
+
*/
|
|
145
|
+
async checkForUpdates() {
|
|
146
|
+
const updates = [];
|
|
147
|
+
for (const [name, info] of this.models) {
|
|
148
|
+
try {
|
|
149
|
+
const encoded = encodeURIComponent(this.options.deviceFingerprint);
|
|
150
|
+
const apiPath = `/v1/federated/models?deviceFingerprint=${encoded}&modelType=${encodeURIComponent(name)}`;
|
|
151
|
+
const raw = await this.apiGet(apiPath);
|
|
152
|
+
if (!raw || typeof raw !== 'object')
|
|
153
|
+
continue;
|
|
154
|
+
// The API may return a single object or an array; normalize to single.
|
|
155
|
+
const response = Array.isArray(raw) ? raw[0] : raw;
|
|
156
|
+
if (!response || !response.version)
|
|
157
|
+
continue;
|
|
158
|
+
info.lastChecked = Date.now();
|
|
159
|
+
if (this.isNewerVersion(response.version, info.version)) {
|
|
160
|
+
updates.push(response);
|
|
161
|
+
this.emit('updateAvailable', {
|
|
162
|
+
name,
|
|
163
|
+
currentVersion: info.version,
|
|
164
|
+
availableVersion: response.version,
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
catch (err) {
|
|
169
|
+
// A failed check for one model must not abort the others
|
|
170
|
+
this.emit('downloadError', {
|
|
171
|
+
name,
|
|
172
|
+
error: `Version check failed: ${String(err)}`,
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
this.emit('checkComplete', {
|
|
177
|
+
checkedCount: this.models.size,
|
|
178
|
+
updatesAvailable: updates.length,
|
|
179
|
+
});
|
|
180
|
+
// Persist lastChecked timestamps
|
|
181
|
+
this.saveManifest();
|
|
182
|
+
// Auto-download if enabled
|
|
183
|
+
if (this.options.autoUpdate) {
|
|
184
|
+
for (const update of updates) {
|
|
185
|
+
if (this.downloading.size >= this.options.maxConcurrentDownloads)
|
|
186
|
+
break;
|
|
187
|
+
// Fire-and-forget — errors are emitted as events
|
|
188
|
+
this.downloadUpdate(update).catch(() => {
|
|
189
|
+
// Error already emitted inside downloadUpdate
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
return updates;
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* Download a specific model update.
|
|
197
|
+
*
|
|
198
|
+
* 1. Download to a .tmp file alongside the target path
|
|
199
|
+
* 2. Verify SHA-256 checksum matches the expected value
|
|
200
|
+
* 3. Atomic swap: rename .tmp -> target .onnx path
|
|
201
|
+
* 4. Update manifest with new version info
|
|
202
|
+
* 5. Emit 'modelUpdated' event
|
|
203
|
+
*
|
|
204
|
+
* Returns true if the model was successfully downloaded and verified.
|
|
205
|
+
*/
|
|
206
|
+
async downloadUpdate(update) {
|
|
207
|
+
const modelName = update.name;
|
|
208
|
+
const info = this.models.get(modelName);
|
|
209
|
+
if (!info) {
|
|
210
|
+
this.emit('downloadError', {
|
|
211
|
+
name: modelName,
|
|
212
|
+
error: `Model '${modelName}' is not registered`,
|
|
213
|
+
});
|
|
214
|
+
return false;
|
|
215
|
+
}
|
|
216
|
+
if (this.downloading.has(modelName)) {
|
|
217
|
+
this.emit('downloadError', {
|
|
218
|
+
name: modelName,
|
|
219
|
+
error: `Download already in progress for '${modelName}'`,
|
|
220
|
+
});
|
|
221
|
+
return false;
|
|
222
|
+
}
|
|
223
|
+
this.downloading.add(modelName);
|
|
224
|
+
const tmpPath = info.filePath + '.tmp';
|
|
225
|
+
const oldVersion = info.version;
|
|
226
|
+
try {
|
|
227
|
+
// Ensure parent directory exists
|
|
228
|
+
const dir = path.dirname(info.filePath);
|
|
229
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
230
|
+
// Step 1: Download to .tmp file
|
|
231
|
+
await this.downloadFile(update.downloadUrl, tmpPath, update.sizeBytes, modelName);
|
|
232
|
+
// Step 2: Verify SHA-256
|
|
233
|
+
const actualHash = await this.computeFileHash(tmpPath);
|
|
234
|
+
if (actualHash !== update.sha256) {
|
|
235
|
+
// Hash mismatch — clean up and report
|
|
236
|
+
this.cleanupTmpFile(tmpPath);
|
|
237
|
+
const msg = `SHA-256 mismatch for '${modelName}': expected ${update.sha256}, got ${actualHash}`;
|
|
238
|
+
this.emit('downloadError', { name: modelName, error: msg });
|
|
239
|
+
return false;
|
|
240
|
+
}
|
|
241
|
+
// Step 3: Atomic swap — rename .tmp to the target path
|
|
242
|
+
fs.renameSync(tmpPath, info.filePath);
|
|
243
|
+
// Step 4: Update model info and manifest
|
|
244
|
+
const stat = fs.statSync(info.filePath);
|
|
245
|
+
info.version = update.version;
|
|
246
|
+
info.sha256 = update.sha256;
|
|
247
|
+
info.sizeBytes = stat.size;
|
|
248
|
+
info.lastUpdated = Date.now();
|
|
249
|
+
info.lastChecked = Date.now();
|
|
250
|
+
this.saveManifest();
|
|
251
|
+
// Step 5: Emit success event
|
|
252
|
+
this.emit('modelUpdated', {
|
|
253
|
+
name: modelName,
|
|
254
|
+
oldVersion,
|
|
255
|
+
newVersion: update.version,
|
|
256
|
+
path: info.filePath,
|
|
257
|
+
});
|
|
258
|
+
return true;
|
|
259
|
+
}
|
|
260
|
+
catch (err) {
|
|
261
|
+
this.cleanupTmpFile(tmpPath);
|
|
262
|
+
this.emit('downloadError', {
|
|
263
|
+
name: modelName,
|
|
264
|
+
error: `Download failed for '${modelName}': ${String(err)}`,
|
|
265
|
+
});
|
|
266
|
+
return false;
|
|
267
|
+
}
|
|
268
|
+
finally {
|
|
269
|
+
this.downloading.delete(modelName);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
// ─── Query Methods ──────────────────────────────────────────────────────────
|
|
273
|
+
/**
|
|
274
|
+
* Get info about a registered model. Returns null if not registered.
|
|
275
|
+
*/
|
|
276
|
+
getModel(name) {
|
|
277
|
+
return this.models.get(name) ?? null;
|
|
278
|
+
}
|
|
279
|
+
/**
|
|
280
|
+
* Get all registered models.
|
|
281
|
+
*/
|
|
282
|
+
getModels() {
|
|
283
|
+
return Array.from(this.models.values());
|
|
284
|
+
}
|
|
285
|
+
/**
|
|
286
|
+
* Check if a model's file exists on disk.
|
|
287
|
+
*/
|
|
288
|
+
hasModel(name) {
|
|
289
|
+
const info = this.models.get(name);
|
|
290
|
+
if (!info)
|
|
291
|
+
return false;
|
|
292
|
+
return fs.existsSync(info.filePath);
|
|
293
|
+
}
|
|
294
|
+
/**
|
|
295
|
+
* Check if a download is currently in progress for a model.
|
|
296
|
+
*/
|
|
297
|
+
isDownloading(name) {
|
|
298
|
+
return this.downloading.has(name);
|
|
299
|
+
}
|
|
300
|
+
// ─── Private: File Hashing ──────────────────────────────────────────────────
|
|
301
|
+
/**
|
|
302
|
+
* Compute the SHA-256 hex digest of a file using a streaming reader.
|
|
303
|
+
*/
|
|
304
|
+
computeFileHash(filePath) {
|
|
305
|
+
return new Promise((resolve, reject) => {
|
|
306
|
+
const hash = crypto.createHash('sha256');
|
|
307
|
+
const stream = fs.createReadStream(filePath);
|
|
308
|
+
stream.on('data', (chunk) => hash.update(chunk));
|
|
309
|
+
stream.on('end', () => resolve(hash.digest('hex')));
|
|
310
|
+
stream.on('error', reject);
|
|
311
|
+
});
|
|
312
|
+
}
|
|
313
|
+
// ─── Private: File Download ─────────────────────────────────────────────────
|
|
314
|
+
/**
|
|
315
|
+
* Download a file from a URL to a local destination path.
|
|
316
|
+
* Follows HTTP redirects (up to maxRedirects), emits progress events,
|
|
317
|
+
* and enforces a download timeout.
|
|
318
|
+
*/
|
|
319
|
+
downloadFile(url, dest, expectedSize, modelName) {
|
|
320
|
+
return this.downloadFileWithRedirects(url, dest, expectedSize, modelName, 0);
|
|
321
|
+
}
|
|
322
|
+
downloadFileWithRedirects(url, dest, expectedSize, modelName, redirectCount) {
|
|
323
|
+
return new Promise((resolve, reject) => {
|
|
324
|
+
if (redirectCount > this.options.maxRedirects) {
|
|
325
|
+
reject(new Error(`Too many redirects (>${this.options.maxRedirects}) downloading ${modelName}`));
|
|
326
|
+
return;
|
|
327
|
+
}
|
|
328
|
+
const isHttps = url.startsWith('https://');
|
|
329
|
+
const transport = isHttps ? https : http;
|
|
330
|
+
const parsedUrl = new URL(url);
|
|
331
|
+
const requestOptions = {
|
|
332
|
+
hostname: parsedUrl.hostname,
|
|
333
|
+
port: parsedUrl.port || (isHttps ? 443 : 80),
|
|
334
|
+
path: parsedUrl.pathname + parsedUrl.search,
|
|
335
|
+
method: 'GET',
|
|
336
|
+
timeout: this.options.downloadTimeoutMs,
|
|
337
|
+
headers: {
|
|
338
|
+
'User-Agent': `TrillboardsEdgeSDK/1.0 (${this.options.deviceFingerprint})`,
|
|
339
|
+
},
|
|
340
|
+
};
|
|
341
|
+
const req = transport.request(requestOptions, (res) => {
|
|
342
|
+
// Handle redirects
|
|
343
|
+
if (res.statusCode && res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
|
|
344
|
+
// Drain the response to free the socket
|
|
345
|
+
res.resume();
|
|
346
|
+
const redirectUrl = res.headers.location.startsWith('http')
|
|
347
|
+
? res.headers.location
|
|
348
|
+
: new URL(res.headers.location, url).toString();
|
|
349
|
+
this.downloadFileWithRedirects(redirectUrl, dest, expectedSize, modelName, redirectCount + 1)
|
|
350
|
+
.then(resolve)
|
|
351
|
+
.catch(reject);
|
|
352
|
+
return;
|
|
353
|
+
}
|
|
354
|
+
if (!res.statusCode || res.statusCode < 200 || res.statusCode >= 300) {
|
|
355
|
+
// Drain and reject
|
|
356
|
+
res.resume();
|
|
357
|
+
reject(new Error(`HTTP ${res.statusCode} downloading '${modelName}' from ${url}`));
|
|
358
|
+
return;
|
|
359
|
+
}
|
|
360
|
+
const totalBytes = expectedSize
|
|
361
|
+
?? (res.headers['content-length'] ? parseInt(res.headers['content-length'], 10) : 0);
|
|
362
|
+
let bytesDownloaded = 0;
|
|
363
|
+
const fileStream = fs.createWriteStream(dest);
|
|
364
|
+
res.on('data', (chunk) => {
|
|
365
|
+
bytesDownloaded += chunk.length;
|
|
366
|
+
const percent = totalBytes > 0
|
|
367
|
+
? Math.min(100, Math.round((bytesDownloaded / totalBytes) * 100))
|
|
368
|
+
: 0;
|
|
369
|
+
this.emit('downloadProgress', {
|
|
370
|
+
name: modelName,
|
|
371
|
+
bytesDownloaded,
|
|
372
|
+
totalBytes,
|
|
373
|
+
percent,
|
|
374
|
+
});
|
|
375
|
+
});
|
|
376
|
+
res.pipe(fileStream);
|
|
377
|
+
fileStream.on('finish', () => {
|
|
378
|
+
fileStream.close(() => resolve());
|
|
379
|
+
});
|
|
380
|
+
fileStream.on('error', (err) => {
|
|
381
|
+
fileStream.close(() => {
|
|
382
|
+
this.cleanupTmpFile(dest);
|
|
383
|
+
reject(err);
|
|
384
|
+
});
|
|
385
|
+
});
|
|
386
|
+
res.on('error', (err) => {
|
|
387
|
+
fileStream.close(() => {
|
|
388
|
+
this.cleanupTmpFile(dest);
|
|
389
|
+
reject(err);
|
|
390
|
+
});
|
|
391
|
+
});
|
|
392
|
+
});
|
|
393
|
+
req.on('timeout', () => {
|
|
394
|
+
req.destroy();
|
|
395
|
+
this.cleanupTmpFile(dest);
|
|
396
|
+
reject(new Error(`Download timeout (${this.options.downloadTimeoutMs}ms) for '${modelName}'`));
|
|
397
|
+
});
|
|
398
|
+
req.on('error', (err) => {
|
|
399
|
+
this.cleanupTmpFile(dest);
|
|
400
|
+
reject(err);
|
|
401
|
+
});
|
|
402
|
+
req.end();
|
|
403
|
+
});
|
|
404
|
+
}
|
|
405
|
+
// ─── Private: Manifest Persistence ──────────────────────────────────────────
|
|
406
|
+
/**
|
|
407
|
+
* Load model manifest from disk. If the file does not exist or is corrupt,
|
|
408
|
+
* start with an empty manifest — never crash.
|
|
409
|
+
*/
|
|
410
|
+
loadManifest() {
|
|
411
|
+
try {
|
|
412
|
+
if (!fs.existsSync(this.manifestPath))
|
|
413
|
+
return;
|
|
414
|
+
const raw = fs.readFileSync(this.manifestPath, 'utf-8');
|
|
415
|
+
const data = JSON.parse(raw);
|
|
416
|
+
if (!data || typeof data !== 'object' || !data.models)
|
|
417
|
+
return;
|
|
418
|
+
for (const [name, entry] of Object.entries(data.models)) {
|
|
419
|
+
if (!entry || typeof entry !== 'object')
|
|
420
|
+
continue;
|
|
421
|
+
// If the model is already registered (via registerModel before initialize),
|
|
422
|
+
// merge manifest info into it. Otherwise create a placeholder entry.
|
|
423
|
+
const existing = this.models.get(name);
|
|
424
|
+
if (existing) {
|
|
425
|
+
existing.version = entry.version || existing.version;
|
|
426
|
+
existing.sha256 = entry.sha256 || existing.sha256;
|
|
427
|
+
existing.lastUpdated = entry.lastUpdated || existing.lastUpdated;
|
|
428
|
+
}
|
|
429
|
+
else {
|
|
430
|
+
// Model not yet registered — store manifest data; filePath will be
|
|
431
|
+
// set when registerModel is called later.
|
|
432
|
+
this.models.set(name, {
|
|
433
|
+
name,
|
|
434
|
+
version: entry.version || '0.0.0',
|
|
435
|
+
filePath: '',
|
|
436
|
+
sha256: entry.sha256 || '',
|
|
437
|
+
sizeBytes: 0,
|
|
438
|
+
lastChecked: data.lastChecked || 0,
|
|
439
|
+
lastUpdated: entry.lastUpdated || 0,
|
|
440
|
+
});
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
catch {
|
|
445
|
+
// Corrupt manifest — start fresh, never crash
|
|
446
|
+
console.warn(`[ModelManager] Corrupt manifest at ${this.manifestPath}, starting fresh`);
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
/**
|
|
450
|
+
* Save the current model state to the manifest file on disk.
|
|
451
|
+
* Uses atomic write: write to .tmp, then rename.
|
|
452
|
+
*/
|
|
453
|
+
saveManifest() {
|
|
454
|
+
const models = {};
|
|
455
|
+
let lastChecked = 0;
|
|
456
|
+
for (const [name, info] of this.models) {
|
|
457
|
+
models[name] = {
|
|
458
|
+
version: info.version,
|
|
459
|
+
sha256: info.sha256,
|
|
460
|
+
lastUpdated: info.lastUpdated,
|
|
461
|
+
};
|
|
462
|
+
if (info.lastChecked > lastChecked) {
|
|
463
|
+
lastChecked = info.lastChecked;
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
const data = { models, lastChecked };
|
|
467
|
+
const tmpPath = this.manifestPath + '.tmp';
|
|
468
|
+
try {
|
|
469
|
+
fs.writeFileSync(tmpPath, JSON.stringify(data, null, 2), 'utf-8');
|
|
470
|
+
fs.renameSync(tmpPath, this.manifestPath);
|
|
471
|
+
}
|
|
472
|
+
catch (err) {
|
|
473
|
+
console.warn(`[ModelManager] Failed to save manifest: ${String(err)}`);
|
|
474
|
+
this.cleanupTmpFile(tmpPath);
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
// ─── Private: API Requests ──────────────────────────────────────────────────
|
|
478
|
+
/**
|
|
479
|
+
* Make an HTTPS GET request to the backend API and return parsed JSON.
|
|
480
|
+
*/
|
|
481
|
+
apiGet(apiPath) {
|
|
482
|
+
return new Promise((resolve, reject) => {
|
|
483
|
+
const fullUrl = this.options.apiBaseUrl + apiPath;
|
|
484
|
+
const isHttps = fullUrl.startsWith('https://');
|
|
485
|
+
const transport = isHttps ? https : http;
|
|
486
|
+
const parsedUrl = new URL(fullUrl);
|
|
487
|
+
const requestOptions = {
|
|
488
|
+
hostname: parsedUrl.hostname,
|
|
489
|
+
port: parsedUrl.port || (isHttps ? 443 : 80),
|
|
490
|
+
path: parsedUrl.pathname + parsedUrl.search,
|
|
491
|
+
method: 'GET',
|
|
492
|
+
timeout: this.options.requestTimeoutMs,
|
|
493
|
+
headers: {
|
|
494
|
+
'Accept': 'application/json',
|
|
495
|
+
'User-Agent': `TrillboardsEdgeSDK/1.0 (${this.options.deviceFingerprint})`,
|
|
496
|
+
},
|
|
497
|
+
};
|
|
498
|
+
const req = transport.request(requestOptions, (res) => {
|
|
499
|
+
if (!res.statusCode || res.statusCode < 200 || res.statusCode >= 300) {
|
|
500
|
+
res.resume();
|
|
501
|
+
reject(new Error(`API request failed: HTTP ${res.statusCode} for ${apiPath}`));
|
|
502
|
+
return;
|
|
503
|
+
}
|
|
504
|
+
const chunks = [];
|
|
505
|
+
res.on('data', (chunk) => chunks.push(chunk));
|
|
506
|
+
res.on('end', () => {
|
|
507
|
+
try {
|
|
508
|
+
const body = Buffer.concat(chunks).toString('utf-8');
|
|
509
|
+
resolve(JSON.parse(body));
|
|
510
|
+
}
|
|
511
|
+
catch (err) {
|
|
512
|
+
reject(new Error(`Failed to parse API response for ${apiPath}: ${String(err)}`));
|
|
513
|
+
}
|
|
514
|
+
});
|
|
515
|
+
res.on('error', reject);
|
|
516
|
+
});
|
|
517
|
+
req.on('timeout', () => {
|
|
518
|
+
req.destroy();
|
|
519
|
+
reject(new Error(`API request timeout (${this.options.requestTimeoutMs}ms) for ${apiPath}`));
|
|
520
|
+
});
|
|
521
|
+
req.on('error', reject);
|
|
522
|
+
req.end();
|
|
523
|
+
});
|
|
524
|
+
}
|
|
525
|
+
// ─── Private: Helpers ───────────────────────────────────────────────────────
|
|
526
|
+
/**
|
|
527
|
+
* Verify that a model file exists on disk, recompute its hash if present,
|
|
528
|
+
* or clear stale info if the file has been deleted.
|
|
529
|
+
*/
|
|
530
|
+
async verifyModelOnDisk(name, info) {
|
|
531
|
+
if (!info.filePath || !fs.existsSync(info.filePath)) {
|
|
532
|
+
// File missing — clear hash/size but keep version metadata
|
|
533
|
+
info.sha256 = '';
|
|
534
|
+
info.sizeBytes = 0;
|
|
535
|
+
return;
|
|
536
|
+
}
|
|
537
|
+
try {
|
|
538
|
+
const stat = fs.statSync(info.filePath);
|
|
539
|
+
info.sizeBytes = stat.size;
|
|
540
|
+
info.sha256 = await this.computeFileHash(info.filePath);
|
|
541
|
+
}
|
|
542
|
+
catch {
|
|
543
|
+
// If we cannot read the file, clear cached info
|
|
544
|
+
info.sha256 = '';
|
|
545
|
+
info.sizeBytes = 0;
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
/**
|
|
549
|
+
* Semver comparison: returns true if `available` is strictly newer than `current`.
|
|
550
|
+
* Handles non-semver gracefully by falling back to string comparison.
|
|
551
|
+
*/
|
|
552
|
+
isNewerVersion(available, current) {
|
|
553
|
+
const parse = (v) => {
|
|
554
|
+
const parts = v.replace(/^v/, '').split('.').map(Number);
|
|
555
|
+
return [parts[0] || 0, parts[1] || 0, parts[2] || 0];
|
|
556
|
+
};
|
|
557
|
+
const [aMajor, aMinor, aPatch] = parse(available);
|
|
558
|
+
const [cMajor, cMinor, cPatch] = parse(current);
|
|
559
|
+
if (aMajor !== cMajor)
|
|
560
|
+
return aMajor > cMajor;
|
|
561
|
+
if (aMinor !== cMinor)
|
|
562
|
+
return aMinor > cMinor;
|
|
563
|
+
return aPatch > cPatch;
|
|
564
|
+
}
|
|
565
|
+
/**
|
|
566
|
+
* Safely remove a .tmp file if it exists. Never throws.
|
|
567
|
+
*/
|
|
568
|
+
cleanupTmpFile(tmpPath) {
|
|
569
|
+
try {
|
|
570
|
+
if (fs.existsSync(tmpPath)) {
|
|
571
|
+
fs.unlinkSync(tmpPath);
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
catch {
|
|
575
|
+
// Best-effort cleanup; do not propagate
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
exports.ModelManager = ModelManager;
|
|
580
|
+
//# sourceMappingURL=ModelManager.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ModelManager.js","sourceRoot":"","sources":["../src/ModelManager.ts"],"names":[],"mappings":"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"}
|