@trillboards/edge-sdk 0.2.4 → 0.2.6
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/README.md +4 -3
- package/deploy/docker/Dockerfile.cloud-sense +117 -0
- package/deploy/docker/Dockerfile.cpu +30 -21
- package/deploy/docker/Dockerfile.cuda +30 -21
- package/deploy/docker/Dockerfile.openvino +30 -20
- package/deploy/docker/README.md +9 -4
- package/deploy/docker/connector-entrypoint.sh +122 -0
- package/deploy/helm/README.md +1 -1
- package/deploy/k8s/daemonset.yaml +1 -1
- package/deploy/macos/README.md +161 -0
- package/deploy/macos/TrillboardsSetupApp.m +603 -0
- package/deploy/macos/build-macos-pkg.sh +588 -0
- package/deploy/macos/com.trillboards.edge.plist +62 -0
- package/deploy/macos/com.trillboards.litertlm.plist +45 -0
- package/deploy/macos/pair.command +519 -0
- package/deploy/macos/scripts/ensure-litertlm.sh +62 -0
- package/deploy/macos/scripts/postinstall +153 -0
- package/deploy/macos/scripts/setup-litertlm.sh +272 -0
- package/deploy/macos/test-install-pair.sh +383 -0
- package/deploy/macos/test-setup-litertlm-reinstall.sh +109 -0
- package/deploy/systemd/install.sh +25 -15
- package/deploy/systemd/trillboards-edge.service +4 -1
- package/deploy/windows/README.md +70 -0
- package/deploy/windows/build-windows.ps1 +225 -0
- package/deploy/windows/bundle-windows-installer.ps1 +153 -0
- package/deploy/windows/check-feeds.js +26 -0
- package/deploy/windows/e2e/fixtures/person.jpg +0 -0
- package/deploy/windows/e2e/test-local-sense.ps1 +185 -0
- package/deploy/windows/install-update.ps1 +101 -0
- package/deploy/windows/installer.nsi +138 -0
- package/deploy/windows/register-litertlm-task.ps1 +42 -0
- package/deploy/windows/register-task.ps1 +44 -0
- package/deploy/windows/run-connector.ps1 +69 -0
- package/deploy/windows/setup-litertlm.ps1 +361 -0
- package/deploy/windows/stop-existing.ps1 +72 -0
- package/deploy/windows/trillboards-edge-task.vbs +25 -0
- package/deploy/windows/trillboards-edge.cmd +33 -0
- package/deploy/windows/trillboards-pair.cmd +276 -0
- package/deploy/windows/trillboards-setup.ps1 +456 -0
- package/deploy/windows/trillboards-setup.vbs +13 -0
- package/dist/AudienceAnalyzeClient.d.ts +136 -0
- package/dist/AudienceAnalyzeClient.d.ts.map +1 -0
- package/dist/AudienceAnalyzeClient.js +246 -0
- package/dist/AudienceAnalyzeClient.js.map +1 -0
- package/dist/ChannelProber.d.ts +150 -0
- package/dist/ChannelProber.d.ts.map +1 -0
- package/dist/ChannelProber.js +887 -0
- package/dist/ChannelProber.js.map +1 -0
- package/dist/CloudEscalationPolicy.d.ts +143 -0
- package/dist/CloudEscalationPolicy.d.ts.map +1 -0
- package/dist/CloudEscalationPolicy.js +137 -0
- package/dist/CloudEscalationPolicy.js.map +1 -0
- package/dist/CloudSensingManager.d.ts +533 -0
- package/dist/CloudSensingManager.d.ts.map +1 -0
- package/dist/CloudSensingManager.js +1254 -0
- package/dist/CloudSensingManager.js.map +1 -0
- package/dist/DiscoveryReportClient.d.ts +66 -0
- package/dist/DiscoveryReportClient.d.ts.map +1 -0
- package/dist/DiscoveryReportClient.js +85 -0
- package/dist/DiscoveryReportClient.js.map +1 -0
- package/dist/EdgeAgent.d.ts +12 -2
- package/dist/EdgeAgent.d.ts.map +1 -1
- package/dist/EdgeAgent.js +55 -13
- package/dist/EdgeAgent.js.map +1 -1
- package/dist/LiteRtVlmProvider.d.ts +193 -0
- package/dist/LiteRtVlmProvider.d.ts.map +1 -0
- package/dist/LiteRtVlmProvider.js +436 -0
- package/dist/LiteRtVlmProvider.js.map +1 -0
- package/dist/MosaicSource.d.ts +108 -0
- package/dist/MosaicSource.d.ts.map +1 -0
- package/dist/MosaicSource.js +274 -0
- package/dist/MosaicSource.js.map +1 -0
- package/dist/OnvifClient.d.ts +83 -0
- package/dist/OnvifClient.d.ts.map +1 -0
- package/dist/OnvifClient.js +209 -0
- package/dist/OnvifClient.js.map +1 -0
- package/dist/PairClient.d.ts +106 -0
- package/dist/PairClient.d.ts.map +1 -0
- package/dist/PairClient.js +186 -0
- package/dist/PairClient.js.map +1 -0
- package/dist/RecorderAddressRecovery.d.ts +53 -0
- package/dist/RecorderAddressRecovery.d.ts.map +1 -0
- package/dist/RecorderAddressRecovery.js +359 -0
- package/dist/RecorderAddressRecovery.js.map +1 -0
- package/dist/SceneChangeDetector.d.ts +291 -0
- package/dist/SceneChangeDetector.d.ts.map +1 -0
- package/dist/SceneChangeDetector.js +682 -0
- package/dist/SceneChangeDetector.js.map +1 -0
- package/dist/SensingProfileClient.d.ts +59 -0
- package/dist/SensingProfileClient.d.ts.map +1 -0
- package/dist/SensingProfileClient.js +78 -0
- package/dist/SensingProfileClient.js.map +1 -0
- package/dist/SetupSessionClient.d.ts +114 -0
- package/dist/SetupSessionClient.d.ts.map +1 -0
- package/dist/SetupSessionClient.js +95 -0
- package/dist/SetupSessionClient.js.map +1 -0
- package/dist/VlmAdmissionGate.d.ts +126 -0
- package/dist/VlmAdmissionGate.d.ts.map +1 -0
- package/dist/VlmAdmissionGate.js +166 -0
- package/dist/VlmAdmissionGate.js.map +1 -0
- package/dist/autoSense.d.ts +216 -0
- package/dist/autoSense.d.ts.map +1 -0
- package/dist/autoSense.js +521 -0
- package/dist/autoSense.js.map +1 -0
- package/dist/cli.d.ts +27 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +511 -21
- package/dist/cli.js.map +1 -1
- package/dist/combinedSense.d.ts +128 -0
- package/dist/combinedSense.d.ts.map +1 -0
- package/dist/combinedSense.js +261 -0
- package/dist/combinedSense.js.map +1 -0
- package/dist/config.d.ts +351 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +99 -2
- package/dist/config.js.map +1 -1
- package/dist/connectorCadence.d.ts +135 -0
- package/dist/connectorCadence.d.ts.map +1 -0
- package/dist/connectorCadence.js +151 -0
- package/dist/connectorCadence.js.map +1 -0
- package/dist/dataDir.d.ts +33 -0
- package/dist/dataDir.d.ts.map +1 -0
- package/dist/dataDir.js +76 -0
- package/dist/dataDir.js.map +1 -0
- package/dist/deployedCadence.d.ts +167 -0
- package/dist/deployedCadence.d.ts.map +1 -0
- package/dist/deployedCadence.js +209 -0
- package/dist/deployedCadence.js.map +1 -0
- package/dist/deployedCadenceSource.d.ts +48 -0
- package/dist/deployedCadenceSource.d.ts.map +1 -0
- package/dist/deployedCadenceSource.js +74 -0
- package/dist/deployedCadenceSource.js.map +1 -0
- package/dist/diagnose.d.ts +1 -1
- package/dist/diagnose.d.ts.map +1 -1
- package/dist/diagnose.js +13 -12
- package/dist/diagnose.js.map +1 -1
- package/dist/discovery.d.ts +131 -0
- package/dist/discovery.d.ts.map +1 -0
- package/dist/discovery.js +499 -0
- package/dist/discovery.js.map +1 -0
- package/dist/gemmaModelOta.d.ts +118 -0
- package/dist/gemmaModelOta.d.ts.map +1 -0
- package/dist/gemmaModelOta.js +291 -0
- package/dist/gemmaModelOta.js.map +1 -0
- package/dist/index.d.ts +16 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +40 -1
- package/dist/index.js.map +1 -1
- package/dist/init.d.ts.map +1 -1
- package/dist/init.js +41 -18
- package/dist/init.js.map +1 -1
- package/dist/keepAwake.d.ts +33 -0
- package/dist/keepAwake.d.ts.map +1 -0
- package/dist/keepAwake.js +54 -0
- package/dist/keepAwake.js.map +1 -0
- package/dist/legacyRecorderProfile.d.ts +20 -0
- package/dist/legacyRecorderProfile.d.ts.map +1 -0
- package/dist/legacyRecorderProfile.js +27 -0
- package/dist/legacyRecorderProfile.js.map +1 -0
- package/dist/localInferenceSchema.d.ts +132 -0
- package/dist/localInferenceSchema.d.ts.map +1 -0
- package/dist/localInferenceSchema.js +274 -0
- package/dist/localInferenceSchema.js.map +1 -0
- package/dist/localSenseE2eHarness.d.ts +101 -0
- package/dist/localSenseE2eHarness.d.ts.map +1 -0
- package/dist/localSenseE2eHarness.js +279 -0
- package/dist/localSenseE2eHarness.js.map +1 -0
- package/dist/localSensing.d.ts +268 -0
- package/dist/localSensing.d.ts.map +1 -0
- package/dist/localSensing.js +492 -0
- package/dist/localSensing.js.map +1 -0
- package/dist/modelIntegrity.d.ts +59 -0
- package/dist/modelIntegrity.d.ts.map +1 -0
- package/dist/modelIntegrity.js +165 -0
- package/dist/modelIntegrity.js.map +1 -0
- package/dist/pairVerify.d.ts +100 -0
- package/dist/pairVerify.d.ts.map +1 -0
- package/dist/pairVerify.js +296 -0
- package/dist/pairVerify.js.map +1 -0
- package/dist/qrScanner.d.ts +33 -0
- package/dist/qrScanner.d.ts.map +1 -0
- package/dist/qrScanner.js +103 -0
- package/dist/qrScanner.js.map +1 -0
- package/dist/runtimePaths.d.ts +10 -0
- package/dist/runtimePaths.d.ts.map +1 -0
- package/dist/runtimePaths.js +79 -0
- package/dist/runtimePaths.js.map +1 -0
- package/dist/sensingRunMode.d.ts +68 -0
- package/dist/sensingRunMode.d.ts.map +1 -0
- package/dist/sensingRunMode.js +346 -0
- package/dist/sensingRunMode.js.map +1 -0
- package/dist/setupMode.d.ts +98 -0
- package/dist/setupMode.d.ts.map +1 -0
- package/dist/setupMode.js +828 -0
- package/dist/setupMode.js.map +1 -0
- package/dist/setupProgress.d.ts +31 -0
- package/dist/setupProgress.d.ts.map +1 -0
- package/dist/setupProgress.js +173 -0
- package/dist/setupProgress.js.map +1 -0
- package/package.json +13 -4
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Is this file actually an ONNX model?
|
|
4
|
+
*
|
|
5
|
+
* THE FAILURE THIS PREVENTS
|
|
6
|
+
* -------------------------------------------------------------------------
|
|
7
|
+
* `models.trillboards.com` was never provisioned. The `trillboards.com` zone
|
|
8
|
+
* has no `models` record, so the name is caught by the `*.trillboards.com`
|
|
9
|
+
* wildcard A-alias and answered by the application ALB, which returns the
|
|
10
|
+
* marketing SPA's `index.html` — HTTP **200**, `content-type: text/html`,
|
|
11
|
+
* 2490 bytes — for every path under it.
|
|
12
|
+
*
|
|
13
|
+
* Every consumer of that URL treated 200 as success and, at most, checked a
|
|
14
|
+
* SIZE FLOOR:
|
|
15
|
+
*
|
|
16
|
+
* deploy/docker/Dockerfile.{cpu,cuda,openvino} `if (size < 1000) reject`
|
|
17
|
+
* scripts/download-models.sh `curl -f` (does not fail on 200)
|
|
18
|
+
* src/cli.ts / src/init.ts statusCode !== 200 only
|
|
19
|
+
* src/diagnose.ts `size === 0` only
|
|
20
|
+
*
|
|
21
|
+
* 2490 > 1000, so every one of them accepted a web page as a model, wrote it
|
|
22
|
+
* to `blazeface.onnx`, and reported success — `download-models` printed
|
|
23
|
+
* `[OK] blazeface.onnx (0.0 MB)` and `diagnose` reported `ONNX Models: pass`.
|
|
24
|
+
* At runtime onnxruntime then throws `Protobuf parsing failed`, the camera
|
|
25
|
+
* subsystem reports ERROR, and the operator has been told everything is fine.
|
|
26
|
+
*
|
|
27
|
+
* A size floor cannot distinguish a model from a web page and never will. The
|
|
28
|
+
* check has to be on the CONTENT.
|
|
29
|
+
*
|
|
30
|
+
* WHAT IS CHECKED
|
|
31
|
+
* -------------------------------------------------------------------------
|
|
32
|
+
* An ONNX file is a serialized `ModelProto`. Protobuf encodes field 1
|
|
33
|
+
* (`ir_version`, a varint) as tag byte `0x08`, so every real ONNX model begins
|
|
34
|
+
* with `0x08`. HTML begins with `<` (0x3C). The two are decisive and cheap.
|
|
35
|
+
*
|
|
36
|
+
* Deliberately reports WHAT it found rather than a bare "invalid": an operator
|
|
37
|
+
* who is told "this is an HTML page, the model host is serving a web page" can
|
|
38
|
+
* act, where "invalid model" sends them looking at their disk.
|
|
39
|
+
*/
|
|
40
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
41
|
+
if (k2 === undefined) k2 = k;
|
|
42
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
43
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
44
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
45
|
+
}
|
|
46
|
+
Object.defineProperty(o, k2, desc);
|
|
47
|
+
}) : (function(o, m, k, k2) {
|
|
48
|
+
if (k2 === undefined) k2 = k;
|
|
49
|
+
o[k2] = m[k];
|
|
50
|
+
}));
|
|
51
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
52
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
53
|
+
}) : function(o, v) {
|
|
54
|
+
o["default"] = v;
|
|
55
|
+
});
|
|
56
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
57
|
+
var ownKeys = function(o) {
|
|
58
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
59
|
+
var ar = [];
|
|
60
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
61
|
+
return ar;
|
|
62
|
+
};
|
|
63
|
+
return ownKeys(o);
|
|
64
|
+
};
|
|
65
|
+
return function (mod) {
|
|
66
|
+
if (mod && mod.__esModule) return mod;
|
|
67
|
+
var result = {};
|
|
68
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
69
|
+
__setModuleDefault(result, mod);
|
|
70
|
+
return result;
|
|
71
|
+
};
|
|
72
|
+
})();
|
|
73
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
74
|
+
exports.classifyModelBytes = classifyModelBytes;
|
|
75
|
+
exports.inspectModelFile = inspectModelFile;
|
|
76
|
+
exports.assertOnnxModel = assertOnnxModel;
|
|
77
|
+
const fs = __importStar(require("fs"));
|
|
78
|
+
/** Protobuf tag for `ModelProto.ir_version` (field 1, varint) — every ONNX file starts here. */
|
|
79
|
+
const ONNX_FIRST_BYTE = 0x08;
|
|
80
|
+
/**
|
|
81
|
+
* Smallest plausible ONNX model. Not a substitute for the magic-byte check —
|
|
82
|
+
* it only catches a truncated write that happens to start correctly.
|
|
83
|
+
*/
|
|
84
|
+
const MIN_MODEL_BYTES = 1024;
|
|
85
|
+
/** Classify a model file's leading bytes. Pure — takes the buffer, touches no disk. */
|
|
86
|
+
function classifyModelBytes(buf, totalBytes) {
|
|
87
|
+
if (totalBytes === 0) {
|
|
88
|
+
return { valid: false, detected: 'empty', bytes: 0, reason: 'file is empty' };
|
|
89
|
+
}
|
|
90
|
+
// Check HTML BEFORE the size floor: an HTML error page is the failure we are
|
|
91
|
+
// actually here for, and saying "too small" about a web page sends the reader
|
|
92
|
+
// to the wrong place.
|
|
93
|
+
const head = buf.subarray(0, Math.min(buf.length, 512)).toString('utf8').trimStart().toLowerCase();
|
|
94
|
+
if (head.startsWith('<!doctype') || head.startsWith('<html') || head.startsWith('<?xml')) {
|
|
95
|
+
return {
|
|
96
|
+
valid: false,
|
|
97
|
+
detected: 'html',
|
|
98
|
+
bytes: totalBytes,
|
|
99
|
+
reason: `served an HTML page (${totalBytes} bytes), not a model — the model host is ` +
|
|
100
|
+
'answering with a web page. Nothing was downloaded.',
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
if (buf.length > 0 && buf[0] !== ONNX_FIRST_BYTE) {
|
|
104
|
+
return {
|
|
105
|
+
valid: false,
|
|
106
|
+
detected: 'not-onnx',
|
|
107
|
+
bytes: totalBytes,
|
|
108
|
+
reason: `does not begin with the ONNX ModelProto tag 0x${ONNX_FIRST_BYTE.toString(16)} ` +
|
|
109
|
+
`(found 0x${buf[0].toString(16).padStart(2, '0')}) — this is not an ONNX model`,
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
if (totalBytes < MIN_MODEL_BYTES) {
|
|
113
|
+
return {
|
|
114
|
+
valid: false,
|
|
115
|
+
detected: 'truncated',
|
|
116
|
+
bytes: totalBytes,
|
|
117
|
+
reason: `only ${totalBytes} bytes — truncated or incomplete`,
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
return { valid: true, bytes: totalBytes };
|
|
121
|
+
}
|
|
122
|
+
/** Classify a model file on disk. Returns `{valid:false}` rather than throwing for a missing file. */
|
|
123
|
+
function inspectModelFile(filePath) {
|
|
124
|
+
let fd;
|
|
125
|
+
try {
|
|
126
|
+
const size = fs.statSync(filePath).size;
|
|
127
|
+
const buf = Buffer.alloc(Math.min(size, 512));
|
|
128
|
+
if (buf.length > 0) {
|
|
129
|
+
fd = fs.openSync(filePath, 'r');
|
|
130
|
+
fs.readSync(fd, buf, 0, buf.length, 0);
|
|
131
|
+
}
|
|
132
|
+
return classifyModelBytes(buf, size);
|
|
133
|
+
}
|
|
134
|
+
catch (err) {
|
|
135
|
+
return {
|
|
136
|
+
valid: false,
|
|
137
|
+
reason: `unreadable (${err instanceof Error ? err.message : String(err)})`,
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
finally {
|
|
141
|
+
if (fd !== undefined)
|
|
142
|
+
fs.closeSync(fd);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Throw unless `filePath` is a real ONNX model, DELETING it if it is not.
|
|
147
|
+
*
|
|
148
|
+
* Deleting is the point. A rejected download that stays on disk is worse than
|
|
149
|
+
* no download at all: the next `download-models` run sees the file, prints
|
|
150
|
+
* `[SKIP] (already exists)`, and the bad bytes become permanent.
|
|
151
|
+
*/
|
|
152
|
+
function assertOnnxModel(filePath, label = filePath) {
|
|
153
|
+
const result = inspectModelFile(filePath);
|
|
154
|
+
if (result.valid)
|
|
155
|
+
return;
|
|
156
|
+
try {
|
|
157
|
+
if (fs.existsSync(filePath))
|
|
158
|
+
fs.unlinkSync(filePath);
|
|
159
|
+
}
|
|
160
|
+
catch {
|
|
161
|
+
/* best effort — the throw below is what matters */
|
|
162
|
+
}
|
|
163
|
+
throw new Error(`${label}: ${result.reason ?? 'not a valid ONNX model'}`);
|
|
164
|
+
}
|
|
165
|
+
//# sourceMappingURL=modelIntegrity.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"modelIntegrity.js","sourceRoot":"","sources":["../src/modelIntegrity.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { type HttpPoster } from './AudienceAnalyzeClient';
|
|
2
|
+
/**
|
|
3
|
+
* pairVerify — the "honest pairing" helpers behind `trillboards-edge pair` and
|
|
4
|
+
* the new `verify-connection` subcommand.
|
|
5
|
+
*
|
|
6
|
+
* Motivated by two field failures where the pairing surface LIED about success:
|
|
7
|
+
* - Hiten (Windows, 16-ch NVR): pairing "succeeded" while the TrillboardsEdge
|
|
8
|
+
* scheduled task was never registered — 16 screens, zero frames, hours of
|
|
9
|
+
* CloudWatch archaeology.
|
|
10
|
+
* - Rushi (macOS): an EACCES from an admin-owned config dir was misreported
|
|
11
|
+
* as "Double-check the code (codes expire)" (#8200).
|
|
12
|
+
*
|
|
13
|
+
* Three jobs, all pure/injectable so they unit-test without network or shell:
|
|
14
|
+
* 1. classifyPairFailure — map a pair() failure (thrown transport error OR a
|
|
15
|
+
* non-2xx response) to an owner-grade failure class + copy. NEVER tells
|
|
16
|
+
* the operator to re-check a code unless the SERVER rejected the code.
|
|
17
|
+
* 2. verifyApiConnection — one authenticated round-trip after pairing
|
|
18
|
+
* (GET /v2/earner/cameras/sensing-profile with a paired device token,
|
|
19
|
+
* mirroring SensingProfileClient's auth headers) so "connected" is proven,
|
|
20
|
+
* not assumed. Distinguishes network vs auth vs server failures.
|
|
21
|
+
* 3. pairing log — a small rolling transcript under the platform log dir so
|
|
22
|
+
* "send diagnostics" has something to send. Never logs codes or tokens.
|
|
23
|
+
*/
|
|
24
|
+
export type PairFailureKind = 'network' | 'rate_limited' | 'bad_code' | 'server';
|
|
25
|
+
export interface PairFailure {
|
|
26
|
+
kind: PairFailureKind;
|
|
27
|
+
/** Owner-grade explanation + next step. */
|
|
28
|
+
message: string;
|
|
29
|
+
}
|
|
30
|
+
/** True when an error thrown by the HTTP transport is a connectivity failure. */
|
|
31
|
+
export declare function isNetworkError(err: unknown): boolean;
|
|
32
|
+
/**
|
|
33
|
+
* Classify a pairing failure. `thrown` is an error from the transport (the
|
|
34
|
+
* request never completed); `statusCode`/`error` come from a completed non-2xx
|
|
35
|
+
* response. The copy rules:
|
|
36
|
+
* - Only a SERVER code rejection (400/404/410) may mention the code.
|
|
37
|
+
* - Connectivity problems say the code is still valid (it was never spent).
|
|
38
|
+
*/
|
|
39
|
+
export declare function classifyPairFailure(input: {
|
|
40
|
+
thrown?: unknown;
|
|
41
|
+
statusCode?: number;
|
|
42
|
+
error?: string;
|
|
43
|
+
}): PairFailure;
|
|
44
|
+
export type VerifyKind = 'verified' | 'network' | 'auth' | 'server' | 'no_config';
|
|
45
|
+
export interface VerifyResult {
|
|
46
|
+
ok: boolean;
|
|
47
|
+
kind: VerifyKind;
|
|
48
|
+
/** Owner-grade explanation (empty when ok). */
|
|
49
|
+
message: string;
|
|
50
|
+
/** HTTP status of the probe, when a response was received. */
|
|
51
|
+
statusCode?: number;
|
|
52
|
+
}
|
|
53
|
+
/** The minimal slice of trillboards.config.json that verification needs. */
|
|
54
|
+
export interface VerifiableConfig {
|
|
55
|
+
apiBaseUrl: string;
|
|
56
|
+
/** First available per-feed device token (one feed = one token = one screen). */
|
|
57
|
+
deviceToken: string;
|
|
58
|
+
/** Display name of the feed the token belongs to (for the success line). */
|
|
59
|
+
feedName: string;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Extract the API base + one device token from a written config file. Accepts
|
|
63
|
+
* both shapes (multi-feed `cameras: []` and the single-camera back-compat
|
|
64
|
+
* top-level `deviceToken`), mirroring pair.command's count_feeds_missing_rtsp.
|
|
65
|
+
* Returns null when the file is missing/unreadable or holds no token.
|
|
66
|
+
*/
|
|
67
|
+
export declare function readVerifiableConfig(configPath: string): VerifiableConfig | null;
|
|
68
|
+
/**
|
|
69
|
+
* Prove the pairing works end-to-end: an authenticated GET of this feed's
|
|
70
|
+
* sensing profile. Same endpoint + device-auth headers as
|
|
71
|
+
* SensingProfileClient.fetchProfile (deviceRequestAuth validates the token +
|
|
72
|
+
* nonce + timestamp) — but unlike that best-effort fetch, this one DISTINGUISHES
|
|
73
|
+
* failure classes, because the whole point is telling the operator the truth.
|
|
74
|
+
*/
|
|
75
|
+
export declare function verifyApiConnection(config: VerifiableConfig, poster?: HttpPoster): Promise<VerifyResult>;
|
|
76
|
+
/**
|
|
77
|
+
* Platform log dir, matching where the installers already put logs:
|
|
78
|
+
* macOS ~/Library/Logs/Trillboards (postinstall creates it)
|
|
79
|
+
* Windows %LOCALAPPDATA%\Trillboards\logs
|
|
80
|
+
* other ~/.trillboards/logs (Docker/systemd installs)
|
|
81
|
+
* Injectable platform/env for tests.
|
|
82
|
+
*/
|
|
83
|
+
export declare function defaultLogDir(platform?: NodeJS.Platform, env?: NodeJS.ProcessEnv, homedir?: string): string;
|
|
84
|
+
/**
|
|
85
|
+
* Append timestamped lines to the rolling pairing log. Fully best-effort: a
|
|
86
|
+
* logging failure must never fail pairing. Returns the log file path (for the
|
|
87
|
+
* "details saved to …" hint) or null when the write failed.
|
|
88
|
+
* NEVER pass claim codes or device tokens in `lines`.
|
|
89
|
+
*/
|
|
90
|
+
export declare function appendPairLog(logDir: string, lines: string | string[]): string | null;
|
|
91
|
+
/**
|
|
92
|
+
* Exit-code contract (the install wrappers branch on these — keep stable):
|
|
93
|
+
* 0 verified — config present, API reachable, token accepted
|
|
94
|
+
* 2 network — config present but Trillboards is unreachable from here
|
|
95
|
+
* 3 auth — API reachable but the saved pairing was rejected
|
|
96
|
+
* 4 no usable config (missing file / no device token)
|
|
97
|
+
* 5 server — API responded with an unexpected status
|
|
98
|
+
*/
|
|
99
|
+
export declare function runVerifyConnection(args: string[]): Promise<void>;
|
|
100
|
+
//# sourceMappingURL=pairVerify.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pairVerify.d.ts","sourceRoot":"","sources":["../src/pairVerify.ts"],"names":[],"mappings":"AAIA,OAAO,EAAqB,KAAK,UAAU,EAAE,MAAM,yBAAyB,CAAC;AAE7E;;;;;;;;;;;;;;;;;;;;;GAqBG;AAIH,MAAM,MAAM,eAAe,GAAG,SAAS,GAAG,cAAc,GAAG,UAAU,GAAG,QAAQ,CAAC;AAEjF,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,eAAe,CAAC;IACtB,2CAA2C;IAC3C,OAAO,EAAE,MAAM,CAAC;CACjB;AAcD,iFAAiF;AACjF,wBAAgB,cAAc,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,CAKpD;AAED;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE;IACzC,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,GAAG,WAAW,CA+Cd;AAID,MAAM,MAAM,UAAU,GAAG,UAAU,GAAG,SAAS,GAAG,MAAM,GAAG,QAAQ,GAAG,WAAW,CAAC;AAElF,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,OAAO,CAAC;IACZ,IAAI,EAAE,UAAU,CAAC;IACjB,+CAA+C;IAC/C,OAAO,EAAE,MAAM,CAAC;IAChB,8DAA8D;IAC9D,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,4EAA4E;AAC5E,MAAM,WAAW,gBAAgB;IAC/B,UAAU,EAAE,MAAM,CAAC;IACnB,iFAAiF;IACjF,WAAW,EAAE,MAAM,CAAC;IACpB,4EAA4E;IAC5E,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,UAAU,EAAE,MAAM,GAAG,gBAAgB,GAAG,IAAI,CA0BhF;AAED;;;;;;GAMG;AACH,wBAAsB,mBAAmB,CACvC,MAAM,EAAE,gBAAgB,EACxB,MAAM,GAAE,UAA8B,GACrC,OAAO,CAAC,YAAY,CAAC,CAgDvB;AAQD;;;;;;GAMG;AACH,wBAAgB,aAAa,CAC3B,QAAQ,GAAE,MAAM,CAAC,QAA2B,EAC5C,GAAG,GAAE,MAAM,CAAC,UAAwB,EACpC,OAAO,GAAE,MAAqB,GAC7B,MAAM,CAOR;AAED;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,MAAM,GAAG,IAAI,CAmBrF;AAID;;;;;;;GAOG;AACH,wBAAsB,mBAAmB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAgCvE"}
|
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.isNetworkError = isNetworkError;
|
|
37
|
+
exports.classifyPairFailure = classifyPairFailure;
|
|
38
|
+
exports.readVerifiableConfig = readVerifiableConfig;
|
|
39
|
+
exports.verifyApiConnection = verifyApiConnection;
|
|
40
|
+
exports.defaultLogDir = defaultLogDir;
|
|
41
|
+
exports.appendPairLog = appendPairLog;
|
|
42
|
+
exports.runVerifyConnection = runVerifyConnection;
|
|
43
|
+
const fs = __importStar(require("fs"));
|
|
44
|
+
const os = __importStar(require("os"));
|
|
45
|
+
const path = __importStar(require("path"));
|
|
46
|
+
const crypto_1 = require("crypto");
|
|
47
|
+
const AudienceAnalyzeClient_1 = require("./AudienceAnalyzeClient");
|
|
48
|
+
const NETWORK_ERRNO = new Set([
|
|
49
|
+
'ENOTFOUND',
|
|
50
|
+
'EAI_AGAIN',
|
|
51
|
+
'ECONNREFUSED',
|
|
52
|
+
'ECONNRESET',
|
|
53
|
+
'ETIMEDOUT',
|
|
54
|
+
'EHOSTUNREACH',
|
|
55
|
+
'ENETUNREACH',
|
|
56
|
+
'ENETDOWN',
|
|
57
|
+
'EPIPE',
|
|
58
|
+
]);
|
|
59
|
+
/** True when an error thrown by the HTTP transport is a connectivity failure. */
|
|
60
|
+
function isNetworkError(err) {
|
|
61
|
+
const e = err;
|
|
62
|
+
if (!e)
|
|
63
|
+
return false;
|
|
64
|
+
if (e.code && NETWORK_ERRNO.has(e.code))
|
|
65
|
+
return true;
|
|
66
|
+
return /timed out|network|getaddrinfo|socket hang up/i.test(String(e.message ?? ''));
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Classify a pairing failure. `thrown` is an error from the transport (the
|
|
70
|
+
* request never completed); `statusCode`/`error` come from a completed non-2xx
|
|
71
|
+
* response. The copy rules:
|
|
72
|
+
* - Only a SERVER code rejection (400/404/410) may mention the code.
|
|
73
|
+
* - Connectivity problems say the code is still valid (it was never spent).
|
|
74
|
+
*/
|
|
75
|
+
function classifyPairFailure(input) {
|
|
76
|
+
if (input.thrown !== undefined) {
|
|
77
|
+
if (isNetworkError(input.thrown)) {
|
|
78
|
+
const code = input.thrown?.code;
|
|
79
|
+
return {
|
|
80
|
+
kind: 'network',
|
|
81
|
+
message: `This computer can't reach Trillboards right now` +
|
|
82
|
+
(code ? ` (${code})` : '') +
|
|
83
|
+
`.\nCheck this machine's internet connection (Wi-Fi/ethernet, firewall, captive portal),\n` +
|
|
84
|
+
`then run pairing again. Your code was NOT used — it stays valid until it expires.`,
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
const msg = input.thrown instanceof Error ? input.thrown.message : String(input.thrown);
|
|
88
|
+
return {
|
|
89
|
+
kind: 'server',
|
|
90
|
+
message: `Pairing hit an unexpected error: ${msg}\n` +
|
|
91
|
+
`Try again in a minute. Your code was NOT used.`,
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
const status = input.statusCode ?? 0;
|
|
95
|
+
if (status === 429) {
|
|
96
|
+
return {
|
|
97
|
+
kind: 'rate_limited',
|
|
98
|
+
message: `Too many pairing attempts from this network — wait a minute, then try again.\n` +
|
|
99
|
+
`Your code stays valid until it expires.`,
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
if (status === 400 || status === 404 || status === 410) {
|
|
103
|
+
return {
|
|
104
|
+
kind: 'bad_code',
|
|
105
|
+
message: `That code wasn't recognized — it may have expired (codes last about 30 minutes)\n` +
|
|
106
|
+
`or have a typo. Get a fresh code from the portal and try again.` +
|
|
107
|
+
(input.error ? `\n(Server said: ${input.error})` : ''),
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
return {
|
|
111
|
+
kind: 'server',
|
|
112
|
+
message: `Trillboards had a hiccup (HTTP ${status || '?'}). This is not a code problem —\n` +
|
|
113
|
+
`wait a minute and try again. Your code stays valid until it expires.` +
|
|
114
|
+
(input.error ? `\n(Server said: ${input.error})` : ''),
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Extract the API base + one device token from a written config file. Accepts
|
|
119
|
+
* both shapes (multi-feed `cameras: []` and the single-camera back-compat
|
|
120
|
+
* top-level `deviceToken`), mirroring pair.command's count_feeds_missing_rtsp.
|
|
121
|
+
* Returns null when the file is missing/unreadable or holds no token.
|
|
122
|
+
*/
|
|
123
|
+
function readVerifiableConfig(configPath) {
|
|
124
|
+
let raw;
|
|
125
|
+
try {
|
|
126
|
+
raw = fs.readFileSync(configPath, 'utf8');
|
|
127
|
+
}
|
|
128
|
+
catch {
|
|
129
|
+
return null;
|
|
130
|
+
}
|
|
131
|
+
try {
|
|
132
|
+
const cfg = JSON.parse(raw);
|
|
133
|
+
const apiBaseUrl = String(cfg.apiBaseUrl || 'https://api.trillboards.com');
|
|
134
|
+
const cameras = Array.isArray(cfg.cameras) ? cfg.cameras : [];
|
|
135
|
+
const withToken = cameras.find((c) => c && typeof c.deviceToken === 'string' && c.deviceToken);
|
|
136
|
+
if (withToken) {
|
|
137
|
+
return {
|
|
138
|
+
apiBaseUrl,
|
|
139
|
+
deviceToken: String(withToken.deviceToken),
|
|
140
|
+
feedName: String(withToken.name || withToken.screenId || 'camera'),
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
if (typeof cfg.deviceToken === 'string' && cfg.deviceToken) {
|
|
144
|
+
return { apiBaseUrl, deviceToken: cfg.deviceToken, feedName: 'camera' };
|
|
145
|
+
}
|
|
146
|
+
return null;
|
|
147
|
+
}
|
|
148
|
+
catch {
|
|
149
|
+
return null;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Prove the pairing works end-to-end: an authenticated GET of this feed's
|
|
154
|
+
* sensing profile. Same endpoint + device-auth headers as
|
|
155
|
+
* SensingProfileClient.fetchProfile (deviceRequestAuth validates the token +
|
|
156
|
+
* nonce + timestamp) — but unlike that best-effort fetch, this one DISTINGUISHES
|
|
157
|
+
* failure classes, because the whole point is telling the operator the truth.
|
|
158
|
+
*/
|
|
159
|
+
async function verifyApiConnection(config, poster = AudienceAnalyzeClient_1.defaultHttpPoster) {
|
|
160
|
+
const base = config.apiBaseUrl.replace(/\/+$/, '');
|
|
161
|
+
const endpoint = `${base}/v2/earner/cameras/sensing-profile`;
|
|
162
|
+
const headers = {
|
|
163
|
+
Accept: 'application/json',
|
|
164
|
+
'X-Device-Token': config.deviceToken,
|
|
165
|
+
'X-Trillboard-Device-Token': config.deviceToken,
|
|
166
|
+
'X-Device-Timestamp': String(Date.now()),
|
|
167
|
+
'X-Device-Nonce': (0, crypto_1.randomUUID)(),
|
|
168
|
+
};
|
|
169
|
+
let result;
|
|
170
|
+
try {
|
|
171
|
+
result = await poster(endpoint, { method: 'GET', headers, timeoutMs: 15000 }, '');
|
|
172
|
+
}
|
|
173
|
+
catch (err) {
|
|
174
|
+
const code = err?.code;
|
|
175
|
+
return {
|
|
176
|
+
ok: false,
|
|
177
|
+
kind: 'network',
|
|
178
|
+
message: `Paired and saved, but this computer can't reach Trillboards right now` +
|
|
179
|
+
(code ? ` (${code})` : '') +
|
|
180
|
+
`.\nYour pairing is SAVED — fix this machine's internet connection and run the\n` +
|
|
181
|
+
`pairing helper again. You will NOT need a new code.`,
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
if (result.statusCode >= 200 && result.statusCode < 300) {
|
|
185
|
+
return { ok: true, kind: 'verified', message: '', statusCode: result.statusCode };
|
|
186
|
+
}
|
|
187
|
+
if (result.statusCode === 401 || result.statusCode === 403) {
|
|
188
|
+
return {
|
|
189
|
+
ok: false,
|
|
190
|
+
kind: 'auth',
|
|
191
|
+
statusCode: result.statusCode,
|
|
192
|
+
message: `Trillboards rejected this machine's saved pairing (HTTP ${result.statusCode}).\n` +
|
|
193
|
+
`The pairing may have been revoked. Generate a fresh code in the portal and pair again.`,
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
return {
|
|
197
|
+
ok: false,
|
|
198
|
+
kind: 'server',
|
|
199
|
+
statusCode: result.statusCode,
|
|
200
|
+
message: `Paired and saved, but the connection check got HTTP ${result.statusCode} from Trillboards.\n` +
|
|
201
|
+
`Your pairing is SAVED — wait a minute and run the pairing helper again.`,
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
// ─── 3. Rolling pairing log ──────────────────────────────────────────────────
|
|
205
|
+
/** Rotate at 512 KB, keep one previous generation — enough for diagnostics. */
|
|
206
|
+
const LOG_MAX_BYTES = 512 * 1024;
|
|
207
|
+
const LOG_FILE = 'pairing.log';
|
|
208
|
+
/**
|
|
209
|
+
* Platform log dir, matching where the installers already put logs:
|
|
210
|
+
* macOS ~/Library/Logs/Trillboards (postinstall creates it)
|
|
211
|
+
* Windows %LOCALAPPDATA%\Trillboards\logs
|
|
212
|
+
* other ~/.trillboards/logs (Docker/systemd installs)
|
|
213
|
+
* Injectable platform/env for tests.
|
|
214
|
+
*/
|
|
215
|
+
function defaultLogDir(platform = process.platform, env = process.env, homedir = os.homedir()) {
|
|
216
|
+
if (platform === 'darwin')
|
|
217
|
+
return path.join(homedir, 'Library', 'Logs', 'Trillboards');
|
|
218
|
+
if (platform === 'win32') {
|
|
219
|
+
const localAppData = env.LOCALAPPDATA || path.join(homedir, 'AppData', 'Local');
|
|
220
|
+
return path.join(localAppData, 'Trillboards', 'logs');
|
|
221
|
+
}
|
|
222
|
+
return path.join(homedir, '.trillboards', 'logs');
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* Append timestamped lines to the rolling pairing log. Fully best-effort: a
|
|
226
|
+
* logging failure must never fail pairing. Returns the log file path (for the
|
|
227
|
+
* "details saved to …" hint) or null when the write failed.
|
|
228
|
+
* NEVER pass claim codes or device tokens in `lines`.
|
|
229
|
+
*/
|
|
230
|
+
function appendPairLog(logDir, lines) {
|
|
231
|
+
try {
|
|
232
|
+
fs.mkdirSync(logDir, { recursive: true });
|
|
233
|
+
const file = path.join(logDir, LOG_FILE);
|
|
234
|
+
try {
|
|
235
|
+
const st = fs.statSync(file);
|
|
236
|
+
if (st.size > LOG_MAX_BYTES)
|
|
237
|
+
fs.renameSync(file, `${file}.1`);
|
|
238
|
+
}
|
|
239
|
+
catch {
|
|
240
|
+
/* first write */
|
|
241
|
+
}
|
|
242
|
+
const stamp = new Date().toISOString();
|
|
243
|
+
const text = (Array.isArray(lines) ? lines : [lines])
|
|
244
|
+
.map((l) => `[${stamp}] ${l}`)
|
|
245
|
+
.join('\n');
|
|
246
|
+
fs.appendFileSync(file, text + '\n');
|
|
247
|
+
return file;
|
|
248
|
+
}
|
|
249
|
+
catch {
|
|
250
|
+
return null;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
// ─── CLI entry: `trillboards-edge verify-connection` ─────────────────────────
|
|
254
|
+
/**
|
|
255
|
+
* Exit-code contract (the install wrappers branch on these — keep stable):
|
|
256
|
+
* 0 verified — config present, API reachable, token accepted
|
|
257
|
+
* 2 network — config present but Trillboards is unreachable from here
|
|
258
|
+
* 3 auth — API reachable but the saved pairing was rejected
|
|
259
|
+
* 4 no usable config (missing file / no device token)
|
|
260
|
+
* 5 server — API responded with an unexpected status
|
|
261
|
+
*/
|
|
262
|
+
async function runVerifyConnection(args) {
|
|
263
|
+
const cfgIdx = args.indexOf('--config');
|
|
264
|
+
const configPath = cfgIdx >= 0 && args[cfgIdx + 1] ? args[cfgIdx + 1] : 'trillboards.config.json';
|
|
265
|
+
const asJson = args.includes('--json');
|
|
266
|
+
const resolved = path.resolve(configPath);
|
|
267
|
+
const cfg = readVerifiableConfig(resolved);
|
|
268
|
+
if (!cfg) {
|
|
269
|
+
if (asJson)
|
|
270
|
+
console.log(JSON.stringify({ ok: false, kind: 'no_config' }));
|
|
271
|
+
else
|
|
272
|
+
console.error(`No paired config with a device token at: ${resolved}`);
|
|
273
|
+
process.exit(4);
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
const res = await verifyApiConnection(cfg);
|
|
277
|
+
appendPairLog(defaultLogDir(), `verify-connection: ${res.kind}${res.statusCode ? ` (HTTP ${res.statusCode})` : ''}`);
|
|
278
|
+
if (asJson) {
|
|
279
|
+
console.log(JSON.stringify({ ok: res.ok, kind: res.kind, statusCode: res.statusCode ?? null }));
|
|
280
|
+
}
|
|
281
|
+
else if (res.ok) {
|
|
282
|
+
console.log(`Connected to Trillboards — pairing for "${cfg.feedName}" is valid.`);
|
|
283
|
+
}
|
|
284
|
+
else {
|
|
285
|
+
console.error(res.message);
|
|
286
|
+
}
|
|
287
|
+
const exitByKind = {
|
|
288
|
+
verified: 0,
|
|
289
|
+
network: 2,
|
|
290
|
+
auth: 3,
|
|
291
|
+
server: 5,
|
|
292
|
+
no_config: 4,
|
|
293
|
+
};
|
|
294
|
+
process.exit(exitByKind[res.kind]);
|
|
295
|
+
}
|
|
296
|
+
//# sourceMappingURL=pairVerify.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pairVerify.js","sourceRoot":"","sources":["../src/pairVerify.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* qrScanner — decode a Trillboards claim code from a QR image so the connector's
|
|
3
|
+
* `pair` flow can be driven by SCANNING the portal's Install-step QR instead of
|
|
4
|
+
* hand-typing the code. The portal renders that QR via `<QrCodeDisplay
|
|
5
|
+
* value={claimCode} />`, so the QR payload IS the raw claim code — we return it
|
|
6
|
+
* verbatim and let PairClient.normalizeClaimCode canonicalize it.
|
|
7
|
+
*
|
|
8
|
+
* Pure-JS and only reached via cli.ts's lazy `require('./qrScanner')` inside the
|
|
9
|
+
* `pair --qr` branch, so the common `pair --code` entrypoint (the macOS/edge
|
|
10
|
+
* "cloud-sense" installer surface) never pulls in the image-decode deps. Supports
|
|
11
|
+
* PNG (a screenshot / "save image" of the portal QR) and JPEG (a phone photo).
|
|
12
|
+
*/
|
|
13
|
+
export interface DecodedImage {
|
|
14
|
+
/** RGBA pixels, 4 bytes per pixel. */
|
|
15
|
+
data: Uint8ClampedArray;
|
|
16
|
+
width: number;
|
|
17
|
+
height: number;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Decode a PNG or JPEG buffer to RGBA pixels, or null for an unsupported format
|
|
21
|
+
* (sniffed by magic bytes, not file extension, so a mislabeled file still works).
|
|
22
|
+
*/
|
|
23
|
+
export declare function decodeImageToRgba(buf: Buffer): DecodedImage | null;
|
|
24
|
+
/** Decode the QR payload from an image buffer, or null if unsupported / no QR found. */
|
|
25
|
+
export declare function decodeQrFromImageBuffer(buf: Buffer): string | null;
|
|
26
|
+
/**
|
|
27
|
+
* Read an image file (a PNG/JPEG screenshot or photo of the portal's claim-code
|
|
28
|
+
* QR) and return the embedded claim code. Throws a clear, owner-facing error for
|
|
29
|
+
* a missing file, an unsupported format, or an image with no QR — so the CLI can
|
|
30
|
+
* print actionable guidance instead of a stack trace.
|
|
31
|
+
*/
|
|
32
|
+
export declare function decodeClaimCodeFromImageFile(filePath: string): Promise<string>;
|
|
33
|
+
//# sourceMappingURL=qrScanner.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"qrScanner.d.ts","sourceRoot":"","sources":["../src/qrScanner.ts"],"names":[],"mappings":"AAKA;;;;;;;;;;;GAWG;AAEH,MAAM,WAAW,YAAY;IAC3B,sCAAsC;IACtC,IAAI,EAAE,iBAAiB,CAAC;IACxB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CAChB;AAaD;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,GAAG,YAAY,GAAG,IAAI,CAUlE;AAED,wFAAwF;AACxF,wBAAgB,uBAAuB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAKlE;AAED;;;;;GAKG;AACH,wBAAsB,4BAA4B,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAepF"}
|