lightman-agent 1.0.18 → 1.0.21

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.
Files changed (52) hide show
  1. package/agent.config.json +22 -23
  2. package/agent.config.template.json +30 -31
  3. package/bin/cms-agent.js +269 -248
  4. package/package.json +1 -1
  5. package/public/assets/index-CcBNCz6h.css +1 -1
  6. package/public/assets/index-D9QHMG8k.js +1 -1
  7. package/public/assets/index-H-8HDl46.js +1 -1
  8. package/public/assets/index-YodeiCia.css +1 -1
  9. package/public/assets/index-legacy-DWtNM8y7.js +41 -41
  10. package/public/assets/polyfills-legacy-DyVYWHbW.js +4 -4
  11. package/scripts/guardian.ps1 +50 -124
  12. package/scripts/install-windows.ps1 +60 -116
  13. package/scripts/lightman-agent.logrotate +12 -12
  14. package/scripts/lightman-agent.service +38 -38
  15. package/scripts/reinstall-windows.ps1 +26 -26
  16. package/scripts/restore-desktop.ps1 +32 -32
  17. package/scripts/setup.ps1 +17 -22
  18. package/scripts/sync-display.mjs +20 -20
  19. package/scripts/uninstall-windows.ps1 +54 -54
  20. package/src/commands/display.ts +177 -177
  21. package/src/commands/kiosk.ts +113 -113
  22. package/src/commands/maintenance.ts +106 -106
  23. package/src/commands/network.ts +129 -129
  24. package/src/commands/power.ts +163 -163
  25. package/src/commands/rpi.ts +45 -45
  26. package/src/commands/screenshot.ts +166 -166
  27. package/src/commands/serial.ts +17 -17
  28. package/src/commands/update.ts +124 -124
  29. package/src/index.ts +173 -90
  30. package/src/lib/config.ts +2 -3
  31. package/src/lib/identity.ts +40 -40
  32. package/src/lib/logger.ts +137 -137
  33. package/src/lib/platform.ts +10 -10
  34. package/src/lib/rpi.ts +180 -180
  35. package/src/lib/screenMap.ts +135 -0
  36. package/src/lib/screens.ts +128 -128
  37. package/src/lib/types.ts +176 -177
  38. package/src/services/commands.ts +107 -107
  39. package/src/services/health.ts +161 -161
  40. package/src/services/localEvents.ts +60 -60
  41. package/src/services/logForwarder.ts +72 -72
  42. package/src/services/multiScreenKiosk.ts +116 -83
  43. package/src/services/oscBridge.ts +186 -186
  44. package/src/services/powerScheduler.ts +260 -260
  45. package/src/services/provisioning.ts +120 -122
  46. package/src/services/serialBridge.ts +230 -230
  47. package/src/services/serviceLauncher.ts +183 -183
  48. package/src/services/staticServer.ts +226 -226
  49. package/src/services/updater.ts +249 -249
  50. package/src/services/watchdog.ts +310 -310
  51. package/src/services/websocket.ts +152 -152
  52. package/tsconfig.json +28 -28
@@ -1,152 +1,152 @@
1
- import WebSocket from 'ws';
2
- import type { WsMessage, Identity } from '../lib/types.js';
3
- import type { Logger } from '../lib/logger.js';
4
-
5
- interface WsClientOptions {
6
- serverUrl: string;
7
- identity: Identity;
8
- logger: Logger;
9
- onMessage: (msg: WsMessage) => void;
10
- }
11
-
12
- export class WsClient {
13
- private ws: WebSocket | null = null;
14
- private serverUrl: string;
15
- private identity: Identity;
16
- private logger: Logger;
17
- private onMessage: (msg: WsMessage) => void;
18
-
19
- private reconnectAttempts = 0;
20
- private maxReconnectDelay = 60_000;
21
- private baseDelay = 1_000;
22
- private reconnectTimer: NodeJS.Timeout | null = null;
23
- private closed = false;
24
- private messageQueue: WsMessage[] = [];
25
-
26
- constructor(options: WsClientOptions) {
27
- this.serverUrl = options.serverUrl;
28
- this.identity = options.identity;
29
- this.logger = options.logger;
30
- this.onMessage = options.onMessage;
31
- }
32
-
33
- connect(): void {
34
- if (this.closed) return;
35
-
36
- const wsUrl = this.serverUrl.replace(/^http/, 'ws') +
37
- '/ws/agent?apiKey=' + encodeURIComponent(this.identity.apiKey);
38
-
39
- this.logger.debug('Connecting to', wsUrl.replace(/apiKey=.*/, 'apiKey=***'));
40
-
41
- try {
42
- this.ws = new WebSocket(wsUrl);
43
- } catch (err) {
44
- this.logger.error('Failed to create WebSocket:', err);
45
- this.scheduleReconnect();
46
- return;
47
- }
48
-
49
- this.ws.on('open', () => {
50
- this.logger.info('WebSocket connected');
51
- this.reconnectAttempts = 0;
52
- this.flushQueue();
53
- });
54
-
55
- this.ws.on('message', (data) => {
56
- try {
57
- const msg: WsMessage = JSON.parse(data.toString());
58
- this.onMessage(msg);
59
- } catch (err) {
60
- this.logger.error('Failed to parse WS message:', err);
61
- }
62
- });
63
-
64
- this.ws.on('close', (code, reason) => {
65
- this.logger.warn(`WebSocket closed: ${code} ${reason.toString()}`);
66
- this.ws = null;
67
- if (!this.closed) {
68
- this.scheduleReconnect();
69
- }
70
- });
71
-
72
- this.ws.on('error', (err) => {
73
- this.logger.error('WebSocket error:', err);
74
- // 'close' event will fire after this, triggering reconnect
75
- });
76
-
77
- this.ws.on('ping', () => {
78
- // ws library auto-responds with pong
79
- });
80
- }
81
-
82
- send(msg: WsMessage): void {
83
- if (this.ws && this.ws.readyState === WebSocket.OPEN) {
84
- this.ws.send(JSON.stringify(msg));
85
- } else {
86
- // Queue message for when connection is restored
87
- this.messageQueue.push(msg);
88
- // Keep queue bounded
89
- if (this.messageQueue.length > 100) {
90
- this.messageQueue.shift();
91
- }
92
- }
93
- }
94
-
95
- close(): void {
96
- this.closed = true;
97
- if (this.reconnectTimer) {
98
- clearTimeout(this.reconnectTimer);
99
- this.reconnectTimer = null;
100
- }
101
- if (this.ws) {
102
- this.ws.close(1000, 'Agent shutting down');
103
- this.ws = null;
104
- }
105
- this.messageQueue = [];
106
- }
107
-
108
- isConnected(): boolean {
109
- return this.ws !== null && this.ws.readyState === WebSocket.OPEN;
110
- }
111
-
112
- /** Calculate delay with exponential backoff + jitter */
113
- getReconnectDelay(): number {
114
- const exponential = this.baseDelay * Math.pow(2, this.reconnectAttempts);
115
- const capped = Math.min(exponential, this.maxReconnectDelay);
116
- // Add jitter: 0.5x to 1.5x
117
- const jitter = capped * (0.5 + Math.random());
118
- return Math.round(jitter);
119
- }
120
-
121
- private scheduleReconnect(): void {
122
- if (this.closed) return;
123
-
124
- const delay = this.getReconnectDelay();
125
- this.reconnectAttempts++;
126
-
127
- this.logger.info(
128
- `Reconnecting in ${delay}ms (attempt ${this.reconnectAttempts})`
129
- );
130
-
131
- this.reconnectTimer = setTimeout(() => {
132
- this.reconnectTimer = null;
133
- this.connect();
134
- }, delay);
135
- }
136
-
137
- private flushQueue(): void {
138
- if (!this.ws || this.ws.readyState !== WebSocket.OPEN) return;
139
-
140
- const now = Date.now();
141
- const MAX_AGE = 300_000; // 5 minutes
142
-
143
- while (this.messageQueue.length > 0) {
144
- const msg = this.messageQueue.shift()!;
145
- if (now - msg.timestamp > MAX_AGE) {
146
- this.logger.warn(`Discarding stale queued message: ${msg.type}`);
147
- continue;
148
- }
149
- this.ws.send(JSON.stringify(msg));
150
- }
151
- }
152
- }
1
+ import WebSocket from 'ws';
2
+ import type { WsMessage, Identity } from '../lib/types.js';
3
+ import type { Logger } from '../lib/logger.js';
4
+
5
+ interface WsClientOptions {
6
+ serverUrl: string;
7
+ identity: Identity;
8
+ logger: Logger;
9
+ onMessage: (msg: WsMessage) => void;
10
+ }
11
+
12
+ export class WsClient {
13
+ private ws: WebSocket | null = null;
14
+ private serverUrl: string;
15
+ private identity: Identity;
16
+ private logger: Logger;
17
+ private onMessage: (msg: WsMessage) => void;
18
+
19
+ private reconnectAttempts = 0;
20
+ private maxReconnectDelay = 60_000;
21
+ private baseDelay = 1_000;
22
+ private reconnectTimer: NodeJS.Timeout | null = null;
23
+ private closed = false;
24
+ private messageQueue: WsMessage[] = [];
25
+
26
+ constructor(options: WsClientOptions) {
27
+ this.serverUrl = options.serverUrl;
28
+ this.identity = options.identity;
29
+ this.logger = options.logger;
30
+ this.onMessage = options.onMessage;
31
+ }
32
+
33
+ connect(): void {
34
+ if (this.closed) return;
35
+
36
+ const wsUrl = this.serverUrl.replace(/^http/, 'ws') +
37
+ '/ws/agent?apiKey=' + encodeURIComponent(this.identity.apiKey);
38
+
39
+ this.logger.debug('Connecting to', wsUrl.replace(/apiKey=.*/, 'apiKey=***'));
40
+
41
+ try {
42
+ this.ws = new WebSocket(wsUrl);
43
+ } catch (err) {
44
+ this.logger.error('Failed to create WebSocket:', err);
45
+ this.scheduleReconnect();
46
+ return;
47
+ }
48
+
49
+ this.ws.on('open', () => {
50
+ this.logger.info('WebSocket connected');
51
+ this.reconnectAttempts = 0;
52
+ this.flushQueue();
53
+ });
54
+
55
+ this.ws.on('message', (data) => {
56
+ try {
57
+ const msg: WsMessage = JSON.parse(data.toString());
58
+ this.onMessage(msg);
59
+ } catch (err) {
60
+ this.logger.error('Failed to parse WS message:', err);
61
+ }
62
+ });
63
+
64
+ this.ws.on('close', (code, reason) => {
65
+ this.logger.warn(`WebSocket closed: ${code} ${reason.toString()}`);
66
+ this.ws = null;
67
+ if (!this.closed) {
68
+ this.scheduleReconnect();
69
+ }
70
+ });
71
+
72
+ this.ws.on('error', (err) => {
73
+ this.logger.error('WebSocket error:', err);
74
+ // 'close' event will fire after this, triggering reconnect
75
+ });
76
+
77
+ this.ws.on('ping', () => {
78
+ // ws library auto-responds with pong
79
+ });
80
+ }
81
+
82
+ send(msg: WsMessage): void {
83
+ if (this.ws && this.ws.readyState === WebSocket.OPEN) {
84
+ this.ws.send(JSON.stringify(msg));
85
+ } else {
86
+ // Queue message for when connection is restored
87
+ this.messageQueue.push(msg);
88
+ // Keep queue bounded
89
+ if (this.messageQueue.length > 100) {
90
+ this.messageQueue.shift();
91
+ }
92
+ }
93
+ }
94
+
95
+ close(): void {
96
+ this.closed = true;
97
+ if (this.reconnectTimer) {
98
+ clearTimeout(this.reconnectTimer);
99
+ this.reconnectTimer = null;
100
+ }
101
+ if (this.ws) {
102
+ this.ws.close(1000, 'Agent shutting down');
103
+ this.ws = null;
104
+ }
105
+ this.messageQueue = [];
106
+ }
107
+
108
+ isConnected(): boolean {
109
+ return this.ws !== null && this.ws.readyState === WebSocket.OPEN;
110
+ }
111
+
112
+ /** Calculate delay with exponential backoff + jitter */
113
+ getReconnectDelay(): number {
114
+ const exponential = this.baseDelay * Math.pow(2, this.reconnectAttempts);
115
+ const capped = Math.min(exponential, this.maxReconnectDelay);
116
+ // Add jitter: 0.5x to 1.5x
117
+ const jitter = capped * (0.5 + Math.random());
118
+ return Math.round(jitter);
119
+ }
120
+
121
+ private scheduleReconnect(): void {
122
+ if (this.closed) return;
123
+
124
+ const delay = this.getReconnectDelay();
125
+ this.reconnectAttempts++;
126
+
127
+ this.logger.info(
128
+ `Reconnecting in ${delay}ms (attempt ${this.reconnectAttempts})`
129
+ );
130
+
131
+ this.reconnectTimer = setTimeout(() => {
132
+ this.reconnectTimer = null;
133
+ this.connect();
134
+ }, delay);
135
+ }
136
+
137
+ private flushQueue(): void {
138
+ if (!this.ws || this.ws.readyState !== WebSocket.OPEN) return;
139
+
140
+ const now = Date.now();
141
+ const MAX_AGE = 300_000; // 5 minutes
142
+
143
+ while (this.messageQueue.length > 0) {
144
+ const msg = this.messageQueue.shift()!;
145
+ if (now - msg.timestamp > MAX_AGE) {
146
+ this.logger.warn(`Discarding stale queued message: ${msg.type}`);
147
+ continue;
148
+ }
149
+ this.ws.send(JSON.stringify(msg));
150
+ }
151
+ }
152
+ }
package/tsconfig.json CHANGED
@@ -1,28 +1,28 @@
1
- {
2
- "compilerOptions": {
3
- "target": "ES2022",
4
- "module": "ESNext",
5
- "moduleResolution": "bundler",
6
- "lib": ["ES2022"],
7
- "outDir": "dist",
8
- "rootDir": "src",
9
- "strict": true,
10
- "esModuleInterop": true,
11
- "skipLibCheck": true,
12
- "forceConsistentCasingInFileNames": true,
13
- "resolveJsonModule": true,
14
- "declaration": true,
15
- "sourceMap": true,
16
- "noUnusedLocals": false,
17
- "noUnusedParameters": false,
18
- "noFallthroughCasesInSwitch": true,
19
- "isolatedModules": true,
20
- "paths": {
21
- "@/*": ["./src/*"]
22
- },
23
- "baseUrl": ".",
24
- "types": ["node"]
25
- },
26
- "include": ["src/**/*"],
27
- "exclude": ["node_modules", "dist", "src/__tests__"]
28
- }
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "ESNext",
5
+ "moduleResolution": "bundler",
6
+ "lib": ["ES2022"],
7
+ "outDir": "dist",
8
+ "rootDir": "src",
9
+ "strict": true,
10
+ "esModuleInterop": true,
11
+ "skipLibCheck": true,
12
+ "forceConsistentCasingInFileNames": true,
13
+ "resolveJsonModule": true,
14
+ "declaration": true,
15
+ "sourceMap": true,
16
+ "noUnusedLocals": false,
17
+ "noUnusedParameters": false,
18
+ "noFallthroughCasesInSwitch": true,
19
+ "isolatedModules": true,
20
+ "paths": {
21
+ "@/*": ["./src/*"]
22
+ },
23
+ "baseUrl": ".",
24
+ "types": ["node"]
25
+ },
26
+ "include": ["src/**/*"],
27
+ "exclude": ["node_modules", "dist", "src/__tests__"]
28
+ }