@phystack/screen-phyos 4.3.40-dev
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 +1 -0
- package/bin/index.js +2 -0
- package/dist/browser.js +117 -0
- package/dist/index.js +394 -0
- package/dist/mirror.js +127 -0
- package/dist/proxy-settings.js +12 -0
- package/dist/proxy.js +52 -0
- package/dist/schema.js +2 -0
- package/dist/screenshot.js +202 -0
- package/dist/sound.js +83 -0
- package/dist/sway.js +163 -0
- package/dist/types/twin.types.js +15 -0
- package/dist/udev.js +44 -0
- package/dist/version.js +10 -0
- package/package.json +63 -0
package/README.md
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
Physcreen
|
package/bin/index.js
ADDED
package/dist/browser.js
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.setup = exports.reload = exports.open = exports.restart = void 0;
|
|
7
|
+
const child_process_1 = __importDefault(require("child_process"));
|
|
8
|
+
const ws_1 = __importDefault(require("ws"));
|
|
9
|
+
const node_fetch_1 = __importDefault(require("node-fetch"));
|
|
10
|
+
const phy_logger_1 = require("@phystack/phy-logger");
|
|
11
|
+
const delay = (n) => new Promise((resolve) => setTimeout(resolve, n));
|
|
12
|
+
const CHROME_PORT = 3000;
|
|
13
|
+
const logger = new phy_logger_1.PhyLogger({
|
|
14
|
+
logToFile: false,
|
|
15
|
+
logToConsole: true,
|
|
16
|
+
includeTrace: true,
|
|
17
|
+
namespace: 'screen-phyos-browser',
|
|
18
|
+
});
|
|
19
|
+
const restart = async () => {
|
|
20
|
+
for (let counter = 0; counter < 3; counter++) {
|
|
21
|
+
try {
|
|
22
|
+
child_process_1.default.execSync('killall chromium');
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
catch (e) {
|
|
26
|
+
await delay(10000);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
exports.restart = restart;
|
|
31
|
+
const fetchTargets = async () => {
|
|
32
|
+
const response = await (0, node_fetch_1.default)(`http://localhost:${CHROME_PORT}/json`);
|
|
33
|
+
const targets = await response.json();
|
|
34
|
+
logger.debug('Available targets', { targets });
|
|
35
|
+
return targets;
|
|
36
|
+
};
|
|
37
|
+
const getWebSocketDebuggerUrl = async () => {
|
|
38
|
+
const targets = await fetchTargets();
|
|
39
|
+
// Filter targets to find the page you want
|
|
40
|
+
const pageTarget = targets.find((target) => {
|
|
41
|
+
const url = new URL(target.url);
|
|
42
|
+
return (target.type === 'page' &&
|
|
43
|
+
(url.protocol === 'http:' || url.protocol === 'https:' || url.protocol === 'file:'));
|
|
44
|
+
});
|
|
45
|
+
if (!pageTarget) {
|
|
46
|
+
throw new Error('No suitable page target found');
|
|
47
|
+
}
|
|
48
|
+
return pageTarget.webSocketDebuggerUrl;
|
|
49
|
+
};
|
|
50
|
+
const sendCmd = async (cmd) => {
|
|
51
|
+
const webSocketDebuggerUrl = await getWebSocketDebuggerUrl();
|
|
52
|
+
const ws = new ws_1.default(webSocketDebuggerUrl);
|
|
53
|
+
return new Promise((resolve, reject) => {
|
|
54
|
+
ws.on('open', () => {
|
|
55
|
+
logger.debug('WebSocket connection opened');
|
|
56
|
+
ws.send(JSON.stringify(cmd));
|
|
57
|
+
});
|
|
58
|
+
ws.on('message', (data) => {
|
|
59
|
+
logger.debug('WebSocket message received', { data });
|
|
60
|
+
resolve(data);
|
|
61
|
+
ws.close();
|
|
62
|
+
});
|
|
63
|
+
ws.on('error', (err) => {
|
|
64
|
+
logger.error('WebSocket error', { error: err });
|
|
65
|
+
reject(err);
|
|
66
|
+
});
|
|
67
|
+
ws.on('close', () => {
|
|
68
|
+
logger.debug('WebSocket connection closed');
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
};
|
|
72
|
+
const open = async (url) => {
|
|
73
|
+
logger.info('Opening URL', { url });
|
|
74
|
+
for (let i = 0; i < 5; i++) {
|
|
75
|
+
try {
|
|
76
|
+
const cmd = {
|
|
77
|
+
id: 1,
|
|
78
|
+
method: 'Page.navigate',
|
|
79
|
+
params: { url },
|
|
80
|
+
};
|
|
81
|
+
await sendCmd(cmd);
|
|
82
|
+
return true;
|
|
83
|
+
}
|
|
84
|
+
catch (e) {
|
|
85
|
+
logger.error('Cannot send message, restarting browser', { error: e.toString() });
|
|
86
|
+
await (0, exports.restart)();
|
|
87
|
+
await delay(1000);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
logger.error('Cannot reach the browser, giving up');
|
|
91
|
+
return false;
|
|
92
|
+
};
|
|
93
|
+
exports.open = open;
|
|
94
|
+
const reload = async () => {
|
|
95
|
+
const cmd = {
|
|
96
|
+
id: 1,
|
|
97
|
+
method: 'Page.reload',
|
|
98
|
+
params: {},
|
|
99
|
+
};
|
|
100
|
+
await sendCmd(cmd);
|
|
101
|
+
};
|
|
102
|
+
exports.reload = reload;
|
|
103
|
+
const setup = async () => {
|
|
104
|
+
const cmd = {
|
|
105
|
+
id: 1,
|
|
106
|
+
method: 'Page.setDeviceMetricsOverride',
|
|
107
|
+
params: {
|
|
108
|
+
width: 1280,
|
|
109
|
+
height: 720,
|
|
110
|
+
deviceScaleFactor: 0,
|
|
111
|
+
mobile: false,
|
|
112
|
+
},
|
|
113
|
+
};
|
|
114
|
+
await sendCmd(cmd);
|
|
115
|
+
};
|
|
116
|
+
exports.setup = setup;
|
|
117
|
+
exports.default = { restart: exports.restart, open: exports.open, reload: exports.reload, setup: exports.setup };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,394 @@
|
|
|
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 (mod) {
|
|
19
|
+
if (mod && mod.__esModule) return mod;
|
|
20
|
+
var result = {};
|
|
21
|
+
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
22
|
+
__setModuleDefault(result, mod);
|
|
23
|
+
return result;
|
|
24
|
+
};
|
|
25
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
26
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
27
|
+
};
|
|
28
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
29
|
+
require("./version.js");
|
|
30
|
+
require("./proxy-settings.js");
|
|
31
|
+
// import { connect, Module } from '@ombori/ga-module';
|
|
32
|
+
const hub_client_1 = require("@phystack/hub-client");
|
|
33
|
+
const phy_logger_1 = require("@phystack/phy-logger");
|
|
34
|
+
const child_process_1 = __importDefault(require("child_process"));
|
|
35
|
+
// import net from 'net';
|
|
36
|
+
const fs_1 = __importDefault(require("fs"));
|
|
37
|
+
// import { Settings } from './schema.js';
|
|
38
|
+
const screenshot_js_1 = require("./screenshot.js");
|
|
39
|
+
const sway_js_1 = require("./sway.js");
|
|
40
|
+
const sound = __importStar(require("./sound.js"));
|
|
41
|
+
const udev_js_1 = __importDefault(require("./udev.js"));
|
|
42
|
+
const browser_js_1 = __importDefault(require("./browser.js"));
|
|
43
|
+
const proxy_js_1 = __importDefault(require("./proxy.js"));
|
|
44
|
+
const twin_types_1 = require("./types/twin.types");
|
|
45
|
+
const logger = new phy_logger_1.PhyLogger({
|
|
46
|
+
logToFile: false,
|
|
47
|
+
logToConsole: true,
|
|
48
|
+
includeTrace: true,
|
|
49
|
+
namespace: 'screen-phyos',
|
|
50
|
+
});
|
|
51
|
+
(async () => {
|
|
52
|
+
const { PHYSCREEN_DEBUG = "0" } = process.env;
|
|
53
|
+
if (parseInt(PHYSCREEN_DEBUG, 10) === 1) {
|
|
54
|
+
logger.info('Starting in PHYSCREEN_DEBUG mode');
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
proxy_js_1.default.start({
|
|
58
|
+
port: 1234,
|
|
59
|
+
dport: 3000,
|
|
60
|
+
});
|
|
61
|
+
const NO_SETTINGS_URL = 'file:///var/physcreen/index.html#no-settings';
|
|
62
|
+
const client = await (0, hub_client_1.connectPhyClient)();
|
|
63
|
+
const deviceInstance = await client.getDeviceInstance();
|
|
64
|
+
const screenInstance = await client.getInstance();
|
|
65
|
+
const { properties } = screenInstance;
|
|
66
|
+
logger.info('Device Instance retrieved', {
|
|
67
|
+
spaceId: deviceInstance.twin.properties.desired.spaceId,
|
|
68
|
+
env: deviceInstance.twin.properties.desired.env,
|
|
69
|
+
displayName: deviceInstance.twin.properties.desired.displayName,
|
|
70
|
+
deviceSerial: deviceInstance.twin.properties.desired.deviceSerial,
|
|
71
|
+
accessKey: deviceInstance.twin.properties.desired.accessKey
|
|
72
|
+
});
|
|
73
|
+
logger.info('Screen Instance retrieved', {
|
|
74
|
+
screenId: screenInstance.id,
|
|
75
|
+
desiredProperties: screenInstance.properties.desired
|
|
76
|
+
});
|
|
77
|
+
if (properties.desired && properties.desired.url) {
|
|
78
|
+
const cachedUrl = properties.desired.url || NO_SETTINGS_URL;
|
|
79
|
+
logger.info('Opening cached URL', { url: cachedUrl });
|
|
80
|
+
await browser_js_1.default.open(cachedUrl);
|
|
81
|
+
}
|
|
82
|
+
const HEARTBEAT_PATH = '/tmp/physcreen-heartbeat';
|
|
83
|
+
setInterval(() => {
|
|
84
|
+
fs_1.default.writeFileSync(HEARTBEAT_PATH, new Date().toISOString());
|
|
85
|
+
}, 1000);
|
|
86
|
+
const emitReportedProperties = async (data, status) => {
|
|
87
|
+
const payload = {
|
|
88
|
+
twinId: screenInstance.id,
|
|
89
|
+
data,
|
|
90
|
+
...(status && { status }),
|
|
91
|
+
};
|
|
92
|
+
logger.info('Reporting properties', { payload });
|
|
93
|
+
await client.setScreenInstanceReportedProperties(payload);
|
|
94
|
+
};
|
|
95
|
+
let crashes = 0;
|
|
96
|
+
await emitReportedProperties({ crash: { count: crashes } }, twin_types_1.TwinStatusEnum.Online);
|
|
97
|
+
let lastHeartbeat = 0;
|
|
98
|
+
// module.subscribe('App.Heartbeat', () => {
|
|
99
|
+
// if (!lastHeartbeat) console.log('Heartbeat: an initial heartbeat is received');
|
|
100
|
+
// lastHeartbeat = new Date().getTime();
|
|
101
|
+
// });
|
|
102
|
+
async function onSupervisorUrlUpdated() {
|
|
103
|
+
const url = properties.desired.url;
|
|
104
|
+
if (url === activeUrl)
|
|
105
|
+
return;
|
|
106
|
+
activeUrl = url || NO_SETTINGS_URL;
|
|
107
|
+
logger.info('URL changed', { newUrl: activeUrl });
|
|
108
|
+
await browser_js_1.default.open(activeUrl);
|
|
109
|
+
}
|
|
110
|
+
const delay = (n) => new Promise((resolve) => setTimeout(resolve, n));
|
|
111
|
+
let activeUrl = properties.desired.url || NO_SETTINGS_URL;
|
|
112
|
+
logger.info('Opening initial URL', { url: activeUrl });
|
|
113
|
+
await browser_js_1.default.open(activeUrl);
|
|
114
|
+
if (!properties.desired.screenResolution)
|
|
115
|
+
properties.desired.screenResolution = 'default';
|
|
116
|
+
if (!properties.desired.screenOrientation)
|
|
117
|
+
properties.desired.screenOrientation = 'landscape';
|
|
118
|
+
if (!properties.desired.soundOutput)
|
|
119
|
+
properties.desired.soundOutput = 'hdmi1';
|
|
120
|
+
if (!properties.desired.soundInput)
|
|
121
|
+
properties.desired.soundInput = 'default';
|
|
122
|
+
if (!properties.desired.scale)
|
|
123
|
+
properties.desired.scale = '1.0';
|
|
124
|
+
logger.info('Desired settings retrieved', { desiredSettings: properties.desired });
|
|
125
|
+
logger.info('Setting up config file for wayland composer');
|
|
126
|
+
await (0, sway_js_1.setupSwayConfig)({
|
|
127
|
+
orientation: properties.desired.screenOrientation,
|
|
128
|
+
resolution: properties.desired.screenResolution,
|
|
129
|
+
scale: properties.desired.scale,
|
|
130
|
+
headless: !(0, sway_js_1.isDrmAvailable)(),
|
|
131
|
+
});
|
|
132
|
+
sound.onDevices((devices) => {
|
|
133
|
+
const sound = devices.reduce((prev, { device, card, name }) => ({
|
|
134
|
+
...prev,
|
|
135
|
+
[`${card}:${device}`]: name,
|
|
136
|
+
}), {});
|
|
137
|
+
emitReportedProperties({ sound });
|
|
138
|
+
});
|
|
139
|
+
logger.info('Setting up sound output');
|
|
140
|
+
await sound.setup(properties.desired.soundOutput, properties.desired.soundInput);
|
|
141
|
+
// const onIoUpdated = async ({ inputs, outputs }: { inputs: any[]; outputs: any[] }) => {
|
|
142
|
+
// await mirror.activate(
|
|
143
|
+
// ScreenTransforms(module.settings.screen_orientation),
|
|
144
|
+
// module.settings.screen_resolution,
|
|
145
|
+
// );
|
|
146
|
+
// console.log(`Found ${inputs.length} inputs`);
|
|
147
|
+
// for (const inp of inputs) console.log(` ${inp.name} ${inp.type}`);
|
|
148
|
+
// console.log(`Found ${outputs.length} outputs`);
|
|
149
|
+
// for (const oup of outputs)
|
|
150
|
+
// console.log(` ${oup.name} ${oup.model} ${oup.serial} ${oup.current_mode}`);
|
|
151
|
+
// if (outputs.length === 0) {
|
|
152
|
+
// module.updateTelemetry({ output: null });
|
|
153
|
+
// } else {
|
|
154
|
+
// const {
|
|
155
|
+
// name,
|
|
156
|
+
// current_mode: { width, height } = { width: 0, height: 0 },
|
|
157
|
+
// model,
|
|
158
|
+
// serial,
|
|
159
|
+
// make,
|
|
160
|
+
// } = outputs[0];
|
|
161
|
+
// module.updateTelemetry({
|
|
162
|
+
// output: {
|
|
163
|
+
// name,
|
|
164
|
+
// mode: `${width}x${height}`,
|
|
165
|
+
// model: `${make} ${model}`,
|
|
166
|
+
// serial,
|
|
167
|
+
// },
|
|
168
|
+
// });
|
|
169
|
+
// }
|
|
170
|
+
// };
|
|
171
|
+
// const checkPortOpen = (port: number) =>
|
|
172
|
+
// new Promise<boolean>((resolve) => {
|
|
173
|
+
// const conn = net.createConnection({ port, timeout: 10000 });
|
|
174
|
+
// conn.on('connect', () => {
|
|
175
|
+
// resolve(true);
|
|
176
|
+
// conn.end();
|
|
177
|
+
// });
|
|
178
|
+
// conn.on('error', () => {
|
|
179
|
+
// resolve(false);
|
|
180
|
+
// });
|
|
181
|
+
// });
|
|
182
|
+
// module.onStream('debug', async (ws: any) => module.proxyTcp(ws, 1234));
|
|
183
|
+
// module.onMethod('open', async (data: any) => {
|
|
184
|
+
// const { url } = data;
|
|
185
|
+
// await browser.open(url);
|
|
186
|
+
// return 'Opened';
|
|
187
|
+
// });
|
|
188
|
+
// module.onMethod('kill', async () => {
|
|
189
|
+
// console.log('Kill command received, killing browser');
|
|
190
|
+
// browser.restart();
|
|
191
|
+
// return 'Killing browser';
|
|
192
|
+
// });
|
|
193
|
+
// module.onMethod('reload', async () => {
|
|
194
|
+
// console.log('Reloading browser');
|
|
195
|
+
// browser.reload();
|
|
196
|
+
// return 'Reloading';
|
|
197
|
+
// });
|
|
198
|
+
// module.onMethod('setup', async () => {
|
|
199
|
+
// console.log('Opening setup screen');
|
|
200
|
+
// browser.setup();
|
|
201
|
+
// return 'Toggling setup';
|
|
202
|
+
// });
|
|
203
|
+
// module.onMethod('inputs', () => swayMsg('-t get_inputs'));
|
|
204
|
+
// module.onMethod('outputs', () => swayMsg('-t get_outputs'));
|
|
205
|
+
// module.onMethod('screenshot', async () => {
|
|
206
|
+
// try {
|
|
207
|
+
// console.log('Taking screenshot');
|
|
208
|
+
// const data = await takeScreenshot();
|
|
209
|
+
// const [allWhite, allBlack] = await analyzeScreenshot(data);
|
|
210
|
+
// const resized = await resizeScreenshot(data);
|
|
211
|
+
// const token = await getScreenshotToken({accessKey: `${GRID_DEVICE_ID}`, browserId: `${GRID_DEVICE_ACCESS_KEY}`}); // TODO: renew
|
|
212
|
+
// if (!token) throw new Error('Screenshot: cannot upload, invalid token');
|
|
213
|
+
// const url = await uploadScreenshot(resized, token);
|
|
214
|
+
// return { url, allWhite, allBlack };
|
|
215
|
+
// } catch (error) {
|
|
216
|
+
// console.error('Error during screenshot process:', error);
|
|
217
|
+
// throw new Error('Failed to process screenshot');
|
|
218
|
+
// }
|
|
219
|
+
// });
|
|
220
|
+
// module.onMethod('screenshot', async () => {
|
|
221
|
+
// try {
|
|
222
|
+
// // Take the screenshot
|
|
223
|
+
// const data = await takeScreenshot();
|
|
224
|
+
// // Analyze the screenshot
|
|
225
|
+
// const [allWhite, allBlack] = await analyzeScreenshot(data);
|
|
226
|
+
// // Resize the screenshot
|
|
227
|
+
// const resized = await resizeScreenshot(data);
|
|
228
|
+
// // Emit the screenshot data
|
|
229
|
+
// await module.emit('uplink', {
|
|
230
|
+
// type: 'Screen.Screenshot',
|
|
231
|
+
// uuid: GRID_DEVICE_ID,
|
|
232
|
+
// image: resized.toString('base64'),
|
|
233
|
+
// });
|
|
234
|
+
// // Construct the URL
|
|
235
|
+
// const url = `https://gridhealth.blob.core.windows.net/screens/${GRID_DEVICE_ID}.jpg`;
|
|
236
|
+
// // Return the result
|
|
237
|
+
// return { url, allWhite, allBlack };
|
|
238
|
+
// } catch (error) {
|
|
239
|
+
// console.error('Error during screenshot process:', error);
|
|
240
|
+
// throw new Error('Failed to process screenshot');
|
|
241
|
+
// }
|
|
242
|
+
// });
|
|
243
|
+
// module.onMethod('sound_test', async (params) => {
|
|
244
|
+
// const { output = module.settings.sound_output } = params;
|
|
245
|
+
// const success = await sound.test(output);
|
|
246
|
+
// return success ? 'Success' : 'Failed';
|
|
247
|
+
// });
|
|
248
|
+
const checkHeartbeats = async () => {
|
|
249
|
+
while (true) {
|
|
250
|
+
await delay(60 * 1000);
|
|
251
|
+
if (!lastHeartbeat)
|
|
252
|
+
continue; // no heartbeats ever received
|
|
253
|
+
const now = new Date().getTime();
|
|
254
|
+
if (now - lastHeartbeat <= 60000)
|
|
255
|
+
continue;
|
|
256
|
+
logger.error('Heartbeat not registered for 60 seconds, restarting app');
|
|
257
|
+
lastHeartbeat = 0;
|
|
258
|
+
// await sendCrashReport('no_heartbeat');
|
|
259
|
+
await browser_js_1.default.restart();
|
|
260
|
+
}
|
|
261
|
+
};
|
|
262
|
+
let prevUrl = '';
|
|
263
|
+
const takeScreenshots = async () => {
|
|
264
|
+
let whiteColorCount = 0;
|
|
265
|
+
let blackColorCount = 0;
|
|
266
|
+
await delay(20 * 1000); // Initial delay before starting the loop
|
|
267
|
+
while (true) {
|
|
268
|
+
const { restartIfAllWhite = true, restartIfAllBlack = true } = properties.desired;
|
|
269
|
+
let data = null;
|
|
270
|
+
try {
|
|
271
|
+
data = await (0, screenshot_js_1.takeScreenshot)();
|
|
272
|
+
}
|
|
273
|
+
catch (e) {
|
|
274
|
+
logger.error('Error taking screenshot', { error: e.toString() });
|
|
275
|
+
(0, sway_js_1.restartSway)();
|
|
276
|
+
continue;
|
|
277
|
+
}
|
|
278
|
+
if (!data) {
|
|
279
|
+
logger.error('Screenshot data is not a buffer');
|
|
280
|
+
throw new Error('Screenshot: not a buffer');
|
|
281
|
+
}
|
|
282
|
+
try {
|
|
283
|
+
const [allWhite, allBlack] = await (0, screenshot_js_1.analyzeScreenshot)(data);
|
|
284
|
+
const resized = await (0, screenshot_js_1.resizeScreenshot)(data);
|
|
285
|
+
let url = `https://gridhealth.blob.core.windows.net/screens/${deviceInstance.twin.deviceId}.jpg`;
|
|
286
|
+
try {
|
|
287
|
+
logger.info('Starting new screenshot upload');
|
|
288
|
+
const token = await (0, screenshot_js_1.getScreenshotToken)({
|
|
289
|
+
browserId: deviceInstance.twin.deviceId,
|
|
290
|
+
accessKey: deviceInstance.twin.properties.desired.accessKey
|
|
291
|
+
});
|
|
292
|
+
logger.debug('Screenshot token retrieved', { token });
|
|
293
|
+
if (!token)
|
|
294
|
+
throw new Error('Screenshot: cannot upload, invalid token');
|
|
295
|
+
url = await (0, screenshot_js_1.uploadScreenshot)(resized, token);
|
|
296
|
+
}
|
|
297
|
+
catch (e) {
|
|
298
|
+
logger.error('Error during screenshot upload', { error: e.toString() });
|
|
299
|
+
throw new Error('Failed to upload screenshot');
|
|
300
|
+
}
|
|
301
|
+
try {
|
|
302
|
+
if (url !== prevUrl) {
|
|
303
|
+
prevUrl = url;
|
|
304
|
+
await emitReportedProperties({ screenshot: url });
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
catch (e) {
|
|
308
|
+
logger.error('Error during screenshot telemetry', { error: e.toString() });
|
|
309
|
+
throw new Error('Failed to screenshot telemetry');
|
|
310
|
+
}
|
|
311
|
+
whiteColorCount = allWhite && restartIfAllWhite ? whiteColorCount + 1 : 0;
|
|
312
|
+
blackColorCount = allBlack && restartIfAllBlack ? blackColorCount + 1 : 0;
|
|
313
|
+
if (whiteColorCount)
|
|
314
|
+
logger.warn(`Screenshot: all white (${whiteColorCount} try)`);
|
|
315
|
+
if (blackColorCount)
|
|
316
|
+
logger.warn(`Screenshot: all black (${blackColorCount} try)`);
|
|
317
|
+
if (whiteColorCount > 2) {
|
|
318
|
+
logger.error('Screen detected as all white, restarting browser');
|
|
319
|
+
await emitReportedProperties({ crash: { reason: 'screen_all_white' } });
|
|
320
|
+
browser_js_1.default.restart();
|
|
321
|
+
whiteColorCount = 0;
|
|
322
|
+
}
|
|
323
|
+
if (blackColorCount > 2) {
|
|
324
|
+
logger.error('Screen detected as all black, restarting browser');
|
|
325
|
+
await emitReportedProperties({ crash: { reason: 'screen_all_black' } });
|
|
326
|
+
browser_js_1.default.restart();
|
|
327
|
+
blackColorCount = 0;
|
|
328
|
+
}
|
|
329
|
+
// Wait time adjustments based on screen status
|
|
330
|
+
await delay(whiteColorCount + blackColorCount ? 5 * 1000 : 60 * 1000);
|
|
331
|
+
}
|
|
332
|
+
catch (e) {
|
|
333
|
+
logger.error('Error processing screenshot', { error: e.toString() });
|
|
334
|
+
// Continue the loop even if there is an error during processing
|
|
335
|
+
await delay(5 * 1000); // Shorter delay after an error
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
};
|
|
339
|
+
const isTouchConnected = (info) => info.driver === 'hid-multitouch' && info.action === 'bind';
|
|
340
|
+
const isMouseLikeConnected = (info) => info.action === 'add' &&
|
|
341
|
+
info.subsystem === 'input' &&
|
|
342
|
+
/^\/dev\/input\/mouse[0-9]/.test(info.devname);
|
|
343
|
+
udev_js_1.default.monitor(async (info) => {
|
|
344
|
+
try {
|
|
345
|
+
if (!info)
|
|
346
|
+
return;
|
|
347
|
+
if (info.subsystem === 'bdi')
|
|
348
|
+
return; // ignore zfs mounts
|
|
349
|
+
// when the screen is connected for the 1st time
|
|
350
|
+
if (info.action === 'add' &&
|
|
351
|
+
info.subsystem === 'graphics' &&
|
|
352
|
+
info.devname === '/dev/fb0') {
|
|
353
|
+
const inputs = await (0, sway_js_1.swayMsg)(`-t get_outputs`);
|
|
354
|
+
if (inputs && inputs.every((input) => /^HEADLESS-/.test(input.name))) {
|
|
355
|
+
logger.info('Udev: Screen is connected for the 1st time, restarting module');
|
|
356
|
+
browser_js_1.default.restart();
|
|
357
|
+
return;
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
if (info.action === 'change' &&
|
|
361
|
+
info.subsystem === 'drm' &&
|
|
362
|
+
info.devname === '/dev/dri/card0') {
|
|
363
|
+
const inputs = await (0, sway_js_1.swayMsg)(`-t get_outputs`);
|
|
364
|
+
if (inputs && inputs.every((input) => /^HEADLESS-/.test(input.name))) {
|
|
365
|
+
logger.info('Udev: Screen is connected, restarting module');
|
|
366
|
+
browser_js_1.default.restart();
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
// logger.debug('Udev ignored', { info });
|
|
370
|
+
}
|
|
371
|
+
catch (e) {
|
|
372
|
+
logger.error('Cannot process udev event', { error: e });
|
|
373
|
+
}
|
|
374
|
+
});
|
|
375
|
+
checkHeartbeats();
|
|
376
|
+
takeScreenshots();
|
|
377
|
+
process.on('SIGTERM', async () => {
|
|
378
|
+
logger.info('Received SIGTERM, exiting');
|
|
379
|
+
try {
|
|
380
|
+
await emitReportedProperties({}, twin_types_1.TwinStatusEnum.Exited);
|
|
381
|
+
fs_1.default.unlinkSync(HEARTBEAT_PATH); // Remove heartbeat file only on clean shutdown
|
|
382
|
+
await new Promise((resolve) => child_process_1.default.exec('killall -9 sway', resolve));
|
|
383
|
+
await delay(500);
|
|
384
|
+
}
|
|
385
|
+
catch (e) {
|
|
386
|
+
logger.error('Error during shutdown', { error: e.toString() });
|
|
387
|
+
}
|
|
388
|
+
process.exit(1);
|
|
389
|
+
});
|
|
390
|
+
// module.restartHandler = async () => {
|
|
391
|
+
// console.log('onrestart handler');
|
|
392
|
+
// await new Promise((resolve) => cp.exec('killall -9 sway', resolve));
|
|
393
|
+
// };
|
|
394
|
+
})();
|
package/dist/mirror.js
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const child_process_1 = __importDefault(require("child_process"));
|
|
7
|
+
const sway_js_1 = require("./sway.js");
|
|
8
|
+
const walk = (data, parent) => {
|
|
9
|
+
if (!data)
|
|
10
|
+
return [];
|
|
11
|
+
data.parent = parent;
|
|
12
|
+
const result = [data];
|
|
13
|
+
for (const child of data.nodes) {
|
|
14
|
+
for (const i of walk(child, data))
|
|
15
|
+
result.push(i);
|
|
16
|
+
}
|
|
17
|
+
return result;
|
|
18
|
+
};
|
|
19
|
+
const MIRROR_TITLE = 'wlr dmabuf output mirror';
|
|
20
|
+
async function main(orientation, resolution) {
|
|
21
|
+
const res = await (0, sway_js_1.swayMsg)('-t get_tree');
|
|
22
|
+
const data = walk(res);
|
|
23
|
+
// data.forEach(a => console.log(a.type));
|
|
24
|
+
const outputs = data.filter((a) => a.type === 'output' && a.active);
|
|
25
|
+
console.log(`Found ${outputs.length} outputs`);
|
|
26
|
+
// if (outputs.length <= 1) return;
|
|
27
|
+
const windows = data.filter((a) => a.type === 'con');
|
|
28
|
+
const primaryWindow = windows.filter((win) => win.name !== MIRROR_TITLE).pop();
|
|
29
|
+
if (!primaryWindow)
|
|
30
|
+
throw new Error('primary window not found');
|
|
31
|
+
console.log('Found a primary window');
|
|
32
|
+
if (!primaryWindow.parent)
|
|
33
|
+
throw new Error('primary window got no parent');
|
|
34
|
+
if (!primaryWindow.parent.parent)
|
|
35
|
+
throw new Error('primary window got no output');
|
|
36
|
+
const primaryOutput = primaryWindow.parent.parent;
|
|
37
|
+
if (outputs.length && resolution !== 'default') {
|
|
38
|
+
console.log('Fixing resolution');
|
|
39
|
+
await (0, sway_js_1.swayMsg)(`'output * mode ${resolution}'`);
|
|
40
|
+
}
|
|
41
|
+
for (const out of outputs) {
|
|
42
|
+
if (out === primaryOutput && orientation) {
|
|
43
|
+
console.log('Fixing rotation for primary output', out.name);
|
|
44
|
+
await (0, sway_js_1.swayMsg)(`output ${out.name} transform ${orientation}`);
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
// console.log(out);
|
|
48
|
+
if (out.transform === 'normal')
|
|
49
|
+
continue;
|
|
50
|
+
console.log('Fixing rotation for output', out.name);
|
|
51
|
+
await (0, sway_js_1.swayMsg)(`output ${out.name} transform 0`);
|
|
52
|
+
}
|
|
53
|
+
console.log(`Found ${windows.length} windows`);
|
|
54
|
+
if (windows.length > outputs.length) {
|
|
55
|
+
console.log('Found too many windows, killing all mirrors');
|
|
56
|
+
windows.forEach((win) => console.log(`title: ${win.name}`));
|
|
57
|
+
try {
|
|
58
|
+
child_process_1.default.execSync('killall wdomirror');
|
|
59
|
+
}
|
|
60
|
+
catch (e) { }
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
63
|
+
if (windows.length < outputs.length) {
|
|
64
|
+
// FIXME: implement a proper display search
|
|
65
|
+
const template = `Mirrorable output: ${primaryOutput.make} Model: ${primaryOutput.model}: ID: `;
|
|
66
|
+
console.log(primaryOutput.name, primaryOutput.serial, primaryOutput.model);
|
|
67
|
+
const lines = child_process_1.default
|
|
68
|
+
.execSync('wdomirror 2>&1 || true', {
|
|
69
|
+
env: { ...process.env },
|
|
70
|
+
})
|
|
71
|
+
.toString()
|
|
72
|
+
.trim()
|
|
73
|
+
.split('\n');
|
|
74
|
+
const found = lines.filter((line) => line.includes(template));
|
|
75
|
+
const ids = (found.length ? found : lines)
|
|
76
|
+
.map((line) => /ID: ([0-9]+)$/.exec(line))
|
|
77
|
+
.filter((a) => a)
|
|
78
|
+
.map(([, id]) => id);
|
|
79
|
+
if (!ids.length)
|
|
80
|
+
throw new Error('Unable to find screen id');
|
|
81
|
+
const id = ids[0];
|
|
82
|
+
for (let i = windows.length; i < outputs.length; i++) {
|
|
83
|
+
console.log('Creating a mirror process');
|
|
84
|
+
child_process_1.default.exec(`wdomirror ${id} &`, {
|
|
85
|
+
env: { ...process.env },
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
await new Promise((resolve) => setInterval(resolve, 1000));
|
|
89
|
+
return false;
|
|
90
|
+
}
|
|
91
|
+
const workspaces = data.filter((a) => a.type === 'workspace' && a.output);
|
|
92
|
+
for (const ws of workspaces) {
|
|
93
|
+
const children = ws.nodes.filter((ch) => ch.type === 'con');
|
|
94
|
+
if (children.length <= 1)
|
|
95
|
+
continue;
|
|
96
|
+
for (const ch of children) {
|
|
97
|
+
if (ch.name !== MIRROR_TITLE)
|
|
98
|
+
continue;
|
|
99
|
+
console.log('moving window to a different workspace', ch.id);
|
|
100
|
+
await (0, sway_js_1.swayMsg)(`'[con_id=${ch.id}] focus'`);
|
|
101
|
+
await (0, sway_js_1.swayMsg)(`move container to workspace next`);
|
|
102
|
+
return false;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
for (const con of data.filter((a) => a.type === 'con' && a.fullscreen_mode === 0)) {
|
|
106
|
+
await (0, sway_js_1.swayMsg)(`'[con_id=${con.id}] focus'`);
|
|
107
|
+
await (0, sway_js_1.swayMsg)(`'[con_id=${con.id}] fullscreen on'`);
|
|
108
|
+
await (0, sway_js_1.swayMsg)(`'[con_id=${con.id}] fullscreen off'`);
|
|
109
|
+
await (0, sway_js_1.swayMsg)(`'[con_id=${con.id}] border none'`);
|
|
110
|
+
console.log(`window ${con.id} is fullscreen now`);
|
|
111
|
+
}
|
|
112
|
+
console.log('All good');
|
|
113
|
+
return true;
|
|
114
|
+
}
|
|
115
|
+
async function activate(orientation, resolution) {
|
|
116
|
+
for (let i = 0; i < 20; i++) {
|
|
117
|
+
try {
|
|
118
|
+
if (await main(orientation, resolution))
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
catch (e) {
|
|
122
|
+
console.error(e);
|
|
123
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
exports.default = { activate };
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const global_agent_1 = require("global-agent");
|
|
4
|
+
const { HTTP_PROXY, HTTPS_PROXY, NO_PROXY } = process.env;
|
|
5
|
+
process.env.GLOBAL_AGENT_FORCE_GLOBAL_AGENT = 'false';
|
|
6
|
+
if (HTTP_PROXY)
|
|
7
|
+
process.env.GLOBAL_AGENT_HTTP_PROXY = HTTP_PROXY.replace(/\"/g, '');
|
|
8
|
+
if (HTTPS_PROXY)
|
|
9
|
+
process.env.GLOBAL_AGENT_HTTPS_PROXY = HTTPS_PROXY.replace(/\"/g, '');
|
|
10
|
+
if (NO_PROXY)
|
|
11
|
+
process.env.GLOBAL_AGENT_NO_PROXY = NO_PROXY.replace(/\"/g, '');
|
|
12
|
+
(0, global_agent_1.bootstrap)();
|
package/dist/proxy.js
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const http_proxy_1 = __importDefault(require("http-proxy"));
|
|
7
|
+
const http_1 = __importDefault(require("http"));
|
|
8
|
+
const phy_logger_1 = require("@phystack/phy-logger");
|
|
9
|
+
const logger = new phy_logger_1.PhyLogger({
|
|
10
|
+
logToFile: false,
|
|
11
|
+
logToConsole: true,
|
|
12
|
+
includeTrace: true,
|
|
13
|
+
namespace: 'screen-phyos-proxy',
|
|
14
|
+
});
|
|
15
|
+
// As chromium does not support remote debugging, we need to manually proxy all http/ws requests to it
|
|
16
|
+
const start = ({ port, dport }) => {
|
|
17
|
+
// const proxy = httpProxy.createProxy({
|
|
18
|
+
// ws: true
|
|
19
|
+
// });
|
|
20
|
+
// const target = `localhost:${dport}`;
|
|
21
|
+
// const server = http.createServer((req: any, res: any) => {
|
|
22
|
+
// console.log('>', req.url);
|
|
23
|
+
// proxy.web(req, res, { target: { host: 'localhost', port: dport } }, (e: any) => {
|
|
24
|
+
// if (e) {
|
|
25
|
+
// console.error(e.message);
|
|
26
|
+
// }
|
|
27
|
+
// });
|
|
28
|
+
// })
|
|
29
|
+
// server.on('upgrade', (req: any, res: any) => {
|
|
30
|
+
// console.log('up', req.url);
|
|
31
|
+
// proxy.ws(req, res, { target: 'ws://localhost:3000' }, undefined, (e: any) => {
|
|
32
|
+
// if (e) {
|
|
33
|
+
// console.error(e.message);
|
|
34
|
+
// }
|
|
35
|
+
// });
|
|
36
|
+
// })
|
|
37
|
+
//
|
|
38
|
+
// Setup our server to proxy standard HTTP requests
|
|
39
|
+
// @ts-ignore
|
|
40
|
+
var proxy = new http_proxy_1.default.createProxyServer({
|
|
41
|
+
target: {
|
|
42
|
+
host: 'localhost',
|
|
43
|
+
port: dport
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
var proxyServer = http_1.default.createServer((req, res) => proxy.web(req, res));
|
|
47
|
+
proxyServer.on('upgrade', (req, socket, head) => proxy.ws(req, socket, head));
|
|
48
|
+
proxyServer.listen(port, '127.0.0.1');
|
|
49
|
+
// server.listen(port);
|
|
50
|
+
logger.info('Debugger proxy started', { port, targetPort: dport });
|
|
51
|
+
};
|
|
52
|
+
exports.default = { start };
|
package/dist/schema.js
ADDED
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.getScreenshotToken = exports.uploadScreenshot = exports.analyzeScreenshot = exports.takeScreenshot = exports.resizeScreenshot = void 0;
|
|
7
|
+
const child_process_1 = __importDefault(require("child_process"));
|
|
8
|
+
const child_process_2 = require("child_process");
|
|
9
|
+
const fs_1 = __importDefault(require("fs"));
|
|
10
|
+
const util_1 = __importDefault(require("util"));
|
|
11
|
+
const get_image_colors_1 = __importDefault(require("get-image-colors"));
|
|
12
|
+
const storage_blob_1 = require("@azure/storage-blob");
|
|
13
|
+
const axios_proxy_1 = __importDefault(require("@phystack/axios-proxy"));
|
|
14
|
+
const phy_logger_1 = require("@phystack/phy-logger");
|
|
15
|
+
const execPromise = util_1.default.promisify(child_process_2.exec);
|
|
16
|
+
// import { Token } from './browser.js';
|
|
17
|
+
const SCREENSHOT_TIMEOUT = 60000; // 1min
|
|
18
|
+
const logger = new phy_logger_1.PhyLogger({
|
|
19
|
+
logToFile: false,
|
|
20
|
+
logToConsole: true,
|
|
21
|
+
includeTrace: true,
|
|
22
|
+
namespace: 'screen-phyos-screenshot',
|
|
23
|
+
});
|
|
24
|
+
const takeScreenshot = async () => {
|
|
25
|
+
let result = [];
|
|
26
|
+
const timeout = setTimeout(() => {
|
|
27
|
+
logger.error("takeScreenshot(): Timeout when creating a screenshot, probably sway is not working correctly, exiting now");
|
|
28
|
+
process.exit(1);
|
|
29
|
+
}, SCREENSHOT_TIMEOUT);
|
|
30
|
+
const code = await new Promise((resolve) => {
|
|
31
|
+
const child = child_process_1.default.spawn("grim", ["-"], {
|
|
32
|
+
stdio: ["inherit", "pipe", "inherit"],
|
|
33
|
+
env: { ...process.env },
|
|
34
|
+
});
|
|
35
|
+
child.stdout.on("data", (data) => result.push(data));
|
|
36
|
+
child.on("close", resolve);
|
|
37
|
+
});
|
|
38
|
+
clearTimeout(timeout);
|
|
39
|
+
if (code !== 0) {
|
|
40
|
+
throw new Error("takeScreenshot(): Error encountered when creating a screenshot");
|
|
41
|
+
}
|
|
42
|
+
if (result.length === 0)
|
|
43
|
+
throw new Error("takeScreenshot(): Cannot create screenshot, zero size");
|
|
44
|
+
logger.info("takeScreenshot(): Screenshot done");
|
|
45
|
+
return Buffer.concat(result);
|
|
46
|
+
};
|
|
47
|
+
exports.takeScreenshot = takeScreenshot;
|
|
48
|
+
const resizeScreenshot = async (data) => {
|
|
49
|
+
try {
|
|
50
|
+
// Write the data to a temporary file
|
|
51
|
+
const tempInputFile = "/tmp/screenshot.png";
|
|
52
|
+
const tempOutputFile = "/tmp/screenshot_resized.jpg";
|
|
53
|
+
await fs_1.default.promises.writeFile(tempInputFile, data);
|
|
54
|
+
// Run the convert command
|
|
55
|
+
const command = `/usr/bin/convert ${tempInputFile} -strip -interlace Plane -gaussian-blur 0.05 -quality 45% -thumbnail 600x600 ${tempOutputFile}`;
|
|
56
|
+
await execPromise(command);
|
|
57
|
+
// Read the resized image from the output file
|
|
58
|
+
const resizedData = await fs_1.default.promises.readFile(tempOutputFile);
|
|
59
|
+
// Cleanup temporary files
|
|
60
|
+
await fs_1.default.promises.unlink(tempInputFile);
|
|
61
|
+
await fs_1.default.promises.unlink(tempOutputFile);
|
|
62
|
+
return resizedData;
|
|
63
|
+
}
|
|
64
|
+
catch (error) {
|
|
65
|
+
logger.error("resizeScreenshot(): Error during image resizing", { error });
|
|
66
|
+
throw new Error("Cannot convert screenshot");
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
exports.resizeScreenshot = resizeScreenshot;
|
|
70
|
+
async function getScreenshotToken(creds) {
|
|
71
|
+
logger.info("getScreenshotToken(): getScreenshotToken function started");
|
|
72
|
+
// Use the correct endpoint for QA and DEV
|
|
73
|
+
let url = "https://api.omborigrid.com/devices/healthcheck-config";
|
|
74
|
+
const proxy = process.env.HTTPS_PROXY;
|
|
75
|
+
logger.debug("Environment proxy", { proxy });
|
|
76
|
+
if (proxy?.toLowerCase().includes("portal-dev")) {
|
|
77
|
+
url = "https://api.griddeveloper.com/devices/healthcheck-config";
|
|
78
|
+
logger.info("getScreenshotToken(): Using DEV environment endpoint");
|
|
79
|
+
}
|
|
80
|
+
else if (proxy?.toLowerCase().includes("portal-qa")) {
|
|
81
|
+
url = "https://api-qa.omborigrid.com/devices/healthcheck-config";
|
|
82
|
+
logger.info("getScreenshotToken(): Using QA environment endpoint");
|
|
83
|
+
}
|
|
84
|
+
else {
|
|
85
|
+
logger.info("getScreenshotToken(): Using PROD environment endpoint");
|
|
86
|
+
}
|
|
87
|
+
logger.info("getScreenshotToken(): Using following parameters", {
|
|
88
|
+
url,
|
|
89
|
+
creds,
|
|
90
|
+
proxy,
|
|
91
|
+
});
|
|
92
|
+
logger.info("getScreenshotToken(): Making axios request", { url, requestBody: creds });
|
|
93
|
+
try {
|
|
94
|
+
// Get axios instance with proxy support
|
|
95
|
+
const axiosWithProxy = await (0, axios_proxy_1.default)();
|
|
96
|
+
// Make the request with timeout
|
|
97
|
+
const response = await axiosWithProxy.post(url, creds, {
|
|
98
|
+
timeout: 60000,
|
|
99
|
+
headers: {
|
|
100
|
+
"content-type": "application/json",
|
|
101
|
+
},
|
|
102
|
+
});
|
|
103
|
+
logger.info("getScreenshotToken(): Axios request completed", {
|
|
104
|
+
status: response.status,
|
|
105
|
+
statusText: response.statusText,
|
|
106
|
+
headers: response.headers
|
|
107
|
+
});
|
|
108
|
+
if (response.status > 299) {
|
|
109
|
+
logger.error("getScreenshotToken(): Cannot acquire upload token", {
|
|
110
|
+
statusText: response.statusText,
|
|
111
|
+
status: response.status,
|
|
112
|
+
responseBody: response.data
|
|
113
|
+
});
|
|
114
|
+
return undefined;
|
|
115
|
+
}
|
|
116
|
+
logger.info("getScreenshotToken(): Request successful", { responseData: response.data });
|
|
117
|
+
logger.info("getScreenshotToken(): Function completed successfully");
|
|
118
|
+
return response.data;
|
|
119
|
+
}
|
|
120
|
+
catch (error) {
|
|
121
|
+
logger.error("getScreenshotToken(): Axios request failed", { error });
|
|
122
|
+
if (error.response) {
|
|
123
|
+
// The request was made and the server responded with a status code
|
|
124
|
+
logger.error("getScreenshotToken(): Error response details", {
|
|
125
|
+
status: error.response.status,
|
|
126
|
+
data: error.response.data,
|
|
127
|
+
headers: error.response.headers
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
else if (error.request) {
|
|
131
|
+
// The request was made but no response was received
|
|
132
|
+
logger.error("getScreenshotToken(): No response received", { request: error.request });
|
|
133
|
+
}
|
|
134
|
+
else {
|
|
135
|
+
// Something happened in setting up the request
|
|
136
|
+
logger.error("getScreenshotToken(): Error setting up request", { message: error.message });
|
|
137
|
+
}
|
|
138
|
+
return undefined;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
exports.getScreenshotToken = getScreenshotToken;
|
|
142
|
+
const uploadScreenshot = async (data, { sas_token, sas_token_subject }) => {
|
|
143
|
+
console.log("uploadScreenshot(): Upload screenshot method started");
|
|
144
|
+
console.log("uploadScreenshot(): About to upload", data.length, "bytes");
|
|
145
|
+
console.log("uploadScreenshot(): SAS token subject:", sas_token_subject);
|
|
146
|
+
console.log("uploadScreenshot(): SAS token length:", sas_token.length);
|
|
147
|
+
const [proto, , hostname, container, file] = sas_token_subject.split("/");
|
|
148
|
+
console.log("uploadScreenshot(): Parsed components:", {
|
|
149
|
+
proto,
|
|
150
|
+
hostname,
|
|
151
|
+
container,
|
|
152
|
+
file,
|
|
153
|
+
});
|
|
154
|
+
const account = hostname.split(".")[0];
|
|
155
|
+
console.log("uploadScreenshot(): Account name:", account);
|
|
156
|
+
const target = `https://${account}.blob.core.windows.net?${sas_token}`;
|
|
157
|
+
console.log("uploadScreenshot(): Target URL (without token):", `https://${account}.blob.core.windows.net`);
|
|
158
|
+
try {
|
|
159
|
+
console.log("uploadScreenshot(): Creating BlobServiceClient...");
|
|
160
|
+
const blobServiceClient = new storage_blob_1.BlobServiceClient(target);
|
|
161
|
+
console.log("uploadScreenshot(): Getting container client for:", container);
|
|
162
|
+
const containerClient = blobServiceClient.getContainerClient(container);
|
|
163
|
+
console.log("uploadScreenshot(): Getting block blob client for:", file);
|
|
164
|
+
const blockBlobClient = containerClient.getBlockBlobClient(file);
|
|
165
|
+
console.log("uploadScreenshot(): Starting upload...");
|
|
166
|
+
const uploadBlobResponse = await blockBlobClient.upload(data, data.length, {
|
|
167
|
+
blobHTTPHeaders: { blobContentType: "image/jpeg" },
|
|
168
|
+
});
|
|
169
|
+
console.log(`uploadScreenshot(): Upload block blob ${file} successfully`, uploadBlobResponse.requestId);
|
|
170
|
+
console.log("uploadScreenshot(): Upload response details:", {
|
|
171
|
+
requestId: uploadBlobResponse.requestId,
|
|
172
|
+
version: uploadBlobResponse.version,
|
|
173
|
+
date: uploadBlobResponse.date,
|
|
174
|
+
etag: uploadBlobResponse.etag,
|
|
175
|
+
});
|
|
176
|
+
return sas_token_subject;
|
|
177
|
+
}
|
|
178
|
+
catch (error) {
|
|
179
|
+
console.error("uploadScreenshot(): Upload failed with error:", error);
|
|
180
|
+
console.error("uploadScreenshot(): Error details:", {
|
|
181
|
+
message: error instanceof Error ? error.message : "Unknown error",
|
|
182
|
+
stack: error instanceof Error ? error.stack : undefined,
|
|
183
|
+
});
|
|
184
|
+
throw error;
|
|
185
|
+
}
|
|
186
|
+
};
|
|
187
|
+
exports.uploadScreenshot = uploadScreenshot;
|
|
188
|
+
const analyzeScreenshot = async (data) => {
|
|
189
|
+
try {
|
|
190
|
+
const cl = await (0, get_image_colors_1.default)(data, "image/png");
|
|
191
|
+
if (!cl)
|
|
192
|
+
return [false, false];
|
|
193
|
+
const allWhite = cl.every((c) => c.rgb().every((color) => color > 250));
|
|
194
|
+
const allBlack = cl.every((c) => c.rgb().every((color) => color < 5));
|
|
195
|
+
return [allWhite, allBlack];
|
|
196
|
+
}
|
|
197
|
+
catch (e) {
|
|
198
|
+
console.log("error", e.toString());
|
|
199
|
+
return [false, false];
|
|
200
|
+
}
|
|
201
|
+
};
|
|
202
|
+
exports.analyzeScreenshot = analyzeScreenshot;
|
package/dist/sound.js
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.test = exports.onDevices = exports.setup = void 0;
|
|
7
|
+
const fs_1 = __importDefault(require("fs"));
|
|
8
|
+
const child_process_1 = __importDefault(require("child_process"));
|
|
9
|
+
let onDevicesHandler = () => { };
|
|
10
|
+
async function setup(output, input) {
|
|
11
|
+
let cardId = 0;
|
|
12
|
+
let deviceId = 0;
|
|
13
|
+
const devices = child_process_1.default.execSync("aplay -l")
|
|
14
|
+
.toString().trim().split("\n")
|
|
15
|
+
.map(line => /^card ([0-9]+): .+, device ([0-9]+): (.+) \[(.+)\]$/.exec(line))
|
|
16
|
+
.filter(info => info)
|
|
17
|
+
.map(([_, card, device, name, displayName]) => ({
|
|
18
|
+
card: parseInt(card, 10),
|
|
19
|
+
device: parseInt(device, 10),
|
|
20
|
+
name,
|
|
21
|
+
displayName,
|
|
22
|
+
}));
|
|
23
|
+
onDevicesHandler(devices);
|
|
24
|
+
let device = null;
|
|
25
|
+
if (output === 'default') {
|
|
26
|
+
cardId = 0;
|
|
27
|
+
deviceId = 0;
|
|
28
|
+
}
|
|
29
|
+
else if (output === 'hdmi1') {
|
|
30
|
+
device = devices.find(({ name }) => name === 'HDMI 0')
|
|
31
|
+
|| devices.find(({ displayName }) => displayName === 'HDMI 0')
|
|
32
|
+
|| devices.find(({ name }) => name.indexOf('HDMI 0') >= 0)
|
|
33
|
+
|| devices.find(({ name }) => name.indexOf('HDMI') >= 0);
|
|
34
|
+
}
|
|
35
|
+
else if (output === 'hdmi2') {
|
|
36
|
+
device = devices.find(({ name }) => name === 'HDMI 1')
|
|
37
|
+
|| devices.find(({ displayName }) => displayName === 'HDMI 1')
|
|
38
|
+
|| devices.find(({ name }) => /HDMI 1/i.test(name))
|
|
39
|
+
|| devices.find(({ name }) => /HDMI/i.test(name));
|
|
40
|
+
}
|
|
41
|
+
else if (output === 'jack') {
|
|
42
|
+
device = devices.find(({ name }) => /headphones/i.test(name));
|
|
43
|
+
}
|
|
44
|
+
if (device) {
|
|
45
|
+
cardId = device.card;
|
|
46
|
+
deviceId = device.device;
|
|
47
|
+
}
|
|
48
|
+
const inputDeviceId = input === 'default' ? '0,0' : '1,0';
|
|
49
|
+
const cfg = [
|
|
50
|
+
`defaults.pcm.card ${cardId}`,
|
|
51
|
+
`defaults.pcm.device ${deviceId}`,
|
|
52
|
+
`defaults.ctl.card ${cardId}`,
|
|
53
|
+
`pcm.!default {`,
|
|
54
|
+
` type asym`,
|
|
55
|
+
` playback.pcm`,
|
|
56
|
+
` {`,
|
|
57
|
+
` type plug`,
|
|
58
|
+
` slave.pcm "plughw:${cardId},${deviceId}"`,
|
|
59
|
+
` }`,
|
|
60
|
+
` capture.pcm`,
|
|
61
|
+
` {`,
|
|
62
|
+
` type plug`,
|
|
63
|
+
` slave.pcm "plughw:${inputDeviceId}"`,
|
|
64
|
+
` }`,
|
|
65
|
+
`}`,
|
|
66
|
+
].join('\n');
|
|
67
|
+
await fs_1.default.promises.writeFile("/etc/asound.conf", cfg);
|
|
68
|
+
}
|
|
69
|
+
exports.setup = setup;
|
|
70
|
+
const onDevices = (handler) => { onDevicesHandler = handler; };
|
|
71
|
+
exports.onDevices = onDevices;
|
|
72
|
+
const test = async (output) => {
|
|
73
|
+
let out = output;
|
|
74
|
+
if (out === "jack")
|
|
75
|
+
out = "front";
|
|
76
|
+
if (/^hdmi[0-9]/.test(out))
|
|
77
|
+
out = 'hdmi';
|
|
78
|
+
const code = await new Promise(resolve => child_process_1.default
|
|
79
|
+
.spawn("speaker-test", ["-D", `plug:${out} `, "-c2", "-l", "1", "-t", "wav"], { stdio: 'inherit' })
|
|
80
|
+
.on('exit', resolve));
|
|
81
|
+
return code === 0;
|
|
82
|
+
};
|
|
83
|
+
exports.test = test;
|
package/dist/sway.js
ADDED
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.swayMsg = exports.isDrmAvailable = exports.restartSway = exports.setupSwayConfig = exports.ScreenTransforms = exports.getPanelRotation = void 0;
|
|
7
|
+
const fs_1 = __importDefault(require("fs"));
|
|
8
|
+
const child_process_1 = __importDefault(require("child_process"));
|
|
9
|
+
const path_1 = __importDefault(require("path"));
|
|
10
|
+
const phy_logger_1 = require("@phystack/phy-logger");
|
|
11
|
+
const logger = new phy_logger_1.PhyLogger({
|
|
12
|
+
logToFile: false,
|
|
13
|
+
logToConsole: true,
|
|
14
|
+
includeTrace: true,
|
|
15
|
+
namespace: 'screen-phyos-sway',
|
|
16
|
+
});
|
|
17
|
+
let panelWithRotation = 0;
|
|
18
|
+
// TRICKY: rotation of DSI touch panels on rockchip is not applied correctly, so we're fixing it manually
|
|
19
|
+
if (process.arch === 'arm64') {
|
|
20
|
+
const baseDir = '/sys/firmware/devicetree/base';
|
|
21
|
+
let present = [];
|
|
22
|
+
try {
|
|
23
|
+
const entries = fs_1.default.readdirSync(baseDir);
|
|
24
|
+
for (const entry of entries) {
|
|
25
|
+
if (entry.startsWith('dsi@')) {
|
|
26
|
+
const rotationPath = path_1.default.join(baseDir, entry, 'panel@0', 'rotation');
|
|
27
|
+
if (fs_1.default.existsSync(rotationPath)) {
|
|
28
|
+
present.push(rotationPath);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
catch (error) {
|
|
34
|
+
logger.error('Error accessing device tree', { error });
|
|
35
|
+
}
|
|
36
|
+
if (present.length > 0) {
|
|
37
|
+
const rotationBuffer = fs_1.default.readFileSync(present[0]);
|
|
38
|
+
const rotation = rotationBuffer.readInt32BE();
|
|
39
|
+
logger.info('Found DSI panel with rotation', { rotation });
|
|
40
|
+
panelWithRotation = rotation;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
const getPanelRotation = () => panelWithRotation;
|
|
44
|
+
exports.getPanelRotation = getPanelRotation;
|
|
45
|
+
const transforms = {
|
|
46
|
+
landscape: 0 + panelWithRotation,
|
|
47
|
+
portrait: 90 + panelWithRotation,
|
|
48
|
+
'portrait-left': 270 + panelWithRotation,
|
|
49
|
+
'landscape-inverted': 180 + panelWithRotation,
|
|
50
|
+
};
|
|
51
|
+
const matrices = {
|
|
52
|
+
landscape: '1 0 0 0 1 0',
|
|
53
|
+
portrait: '0 1 0 -1 0 1',
|
|
54
|
+
'portrait-left': '0 -1 1 1 0 0',
|
|
55
|
+
'landscape-inverted': '-1 0 1 0 -1 1',
|
|
56
|
+
};
|
|
57
|
+
const getTouchRotationCmd = (orientation) => {
|
|
58
|
+
const mtx = matrices[orientation];
|
|
59
|
+
if (mtx)
|
|
60
|
+
return `calibration_matrix ${mtx}`;
|
|
61
|
+
return `map_to_output *`;
|
|
62
|
+
};
|
|
63
|
+
const ScreenTransforms = (orientation) => {
|
|
64
|
+
let val = transforms[orientation];
|
|
65
|
+
while (val < 0)
|
|
66
|
+
val += 360;
|
|
67
|
+
while (val >= 360)
|
|
68
|
+
val -= 360;
|
|
69
|
+
if (val % 90 !== 0) {
|
|
70
|
+
logger.error('Weird screen rotation', { rotation: val });
|
|
71
|
+
return 'default';
|
|
72
|
+
}
|
|
73
|
+
if (val === 0)
|
|
74
|
+
return 'normal';
|
|
75
|
+
return val.toString(10);
|
|
76
|
+
};
|
|
77
|
+
exports.ScreenTransforms = ScreenTransforms;
|
|
78
|
+
const call = (cmd) => new Promise((resolve, reject) => child_process_1.default.exec(cmd, (err, out) => (err ? reject(err) : resolve(out))));
|
|
79
|
+
const swayMsg = async (msg) => {
|
|
80
|
+
logger.debug('Sway: sending message', { message: msg });
|
|
81
|
+
const SWAY_SOCK = '/tmp/sway.sock';
|
|
82
|
+
const res = await call(`swaymsg --raw -s ${SWAY_SOCK} ${msg}`);
|
|
83
|
+
return JSON.parse(res);
|
|
84
|
+
};
|
|
85
|
+
exports.swayMsg = swayMsg;
|
|
86
|
+
const detectPhantomOutputs = async () => {
|
|
87
|
+
const outputs = await swayMsg('-t get_outputs');
|
|
88
|
+
// Find outputs with higher resolutions
|
|
89
|
+
const highResOutputs = outputs.filter((output) => output.rect.width > 1024);
|
|
90
|
+
// Find outputs with 1024x768 resolution
|
|
91
|
+
const lowResOutputs = outputs.filter((output) => output.rect.width <= 1024);
|
|
92
|
+
// If there's at least one higher resolution output and exactly one low resolution output
|
|
93
|
+
let phantomOutputs = [];
|
|
94
|
+
if (highResOutputs.length > 0 && lowResOutputs.length > 0) {
|
|
95
|
+
phantomOutputs = lowResOutputs;
|
|
96
|
+
}
|
|
97
|
+
return phantomOutputs;
|
|
98
|
+
};
|
|
99
|
+
async function setupSwayConfig({ resolution, orientation, headless, scale, }) {
|
|
100
|
+
console.log('Setting up sway config');
|
|
101
|
+
// for headless sway, we should specify resolution explicitly
|
|
102
|
+
let res = resolution === 'default' && headless ? '1920x1080' : resolution;
|
|
103
|
+
if (process.arch === 'arm64') {
|
|
104
|
+
if (res === 'default')
|
|
105
|
+
res = '1920x1080'; // on arm platform we limit max resolution to fullhd
|
|
106
|
+
}
|
|
107
|
+
const parsedScale = parseFloat(scale) || '1.0';
|
|
108
|
+
let config = `
|
|
109
|
+
output * bg /var/physcreen/ombori-logo.png center #000000
|
|
110
|
+
output * scale ${parsedScale}
|
|
111
|
+
output * transform ${(0, exports.ScreenTransforms)(orientation)}
|
|
112
|
+
seat * hide_cursor 5000
|
|
113
|
+
bindsym F2 exec chvt 2
|
|
114
|
+
bindsym F1 exec chvt 1
|
|
115
|
+
output * mode ${res}
|
|
116
|
+
|
|
117
|
+
# Touchscreen settings
|
|
118
|
+
input type:touch {
|
|
119
|
+
tap enabled
|
|
120
|
+
natural_scroll enabled
|
|
121
|
+
${getTouchRotationCmd(orientation)}
|
|
122
|
+
}
|
|
123
|
+
`;
|
|
124
|
+
const phantomOutputs = await detectPhantomOutputs();
|
|
125
|
+
console.log({ phantomOutputs });
|
|
126
|
+
for (const output of phantomOutputs) {
|
|
127
|
+
console.log(`Disabling phantom output: ${output.name}`);
|
|
128
|
+
config += `\noutput ${output.name} disable\n`;
|
|
129
|
+
}
|
|
130
|
+
console.log('Writing sway config', config);
|
|
131
|
+
await fs_1.default.promises.writeFile('/etc/sway/config', config);
|
|
132
|
+
await swayMsg('reload');
|
|
133
|
+
}
|
|
134
|
+
exports.setupSwayConfig = setupSwayConfig;
|
|
135
|
+
const restartSway = () => {
|
|
136
|
+
try {
|
|
137
|
+
child_process_1.default.execSync('killall sway');
|
|
138
|
+
}
|
|
139
|
+
catch (e) {
|
|
140
|
+
console.error(e.toString());
|
|
141
|
+
}
|
|
142
|
+
};
|
|
143
|
+
exports.restartSway = restartSway;
|
|
144
|
+
const isNoDrms = () => {
|
|
145
|
+
return fs_1.default.readdirSync('/sys/class/drm').join(' ') === 'version';
|
|
146
|
+
};
|
|
147
|
+
const isDrmAvailable = () => {
|
|
148
|
+
try {
|
|
149
|
+
for (const file of fs_1.default.readdirSync('/sys/class/drm')) {
|
|
150
|
+
const statusFile = `/sys/class/drm/${file}/status`;
|
|
151
|
+
if (!fs_1.default.existsSync(statusFile))
|
|
152
|
+
continue;
|
|
153
|
+
const status = fs_1.default.readFileSync(statusFile).toString().trim();
|
|
154
|
+
if (status === 'connected')
|
|
155
|
+
return true;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
catch (e) {
|
|
159
|
+
console.log('err', e.toString());
|
|
160
|
+
return false;
|
|
161
|
+
}
|
|
162
|
+
};
|
|
163
|
+
exports.isDrmAvailable = isDrmAvailable;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.TwinStatusEnum = exports.TwinTypeEnum = void 0;
|
|
4
|
+
var TwinTypeEnum;
|
|
5
|
+
(function (TwinTypeEnum) {
|
|
6
|
+
TwinTypeEnum["Screen"] = "Screen";
|
|
7
|
+
})(TwinTypeEnum = exports.TwinTypeEnum || (exports.TwinTypeEnum = {}));
|
|
8
|
+
var TwinStatusEnum;
|
|
9
|
+
(function (TwinStatusEnum) {
|
|
10
|
+
TwinStatusEnum["Offline"] = "Offline";
|
|
11
|
+
TwinStatusEnum["Online"] = "Online";
|
|
12
|
+
TwinStatusEnum["Starting"] = "Starting";
|
|
13
|
+
TwinStatusEnum["Exited"] = "Exited";
|
|
14
|
+
TwinStatusEnum["ImageNotFound"] = "ImageNotFound";
|
|
15
|
+
})(TwinStatusEnum = exports.TwinStatusEnum || (exports.TwinStatusEnum = {}));
|
package/dist/udev.js
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const child_process_1 = __importDefault(require("child_process"));
|
|
7
|
+
const phy_logger_1 = require("@phystack/phy-logger");
|
|
8
|
+
const logger = new phy_logger_1.PhyLogger({
|
|
9
|
+
logToFile: false,
|
|
10
|
+
logToConsole: true,
|
|
11
|
+
includeTrace: true,
|
|
12
|
+
namespace: 'screen-phyos-udev',
|
|
13
|
+
});
|
|
14
|
+
async function monitorProcess(cb) {
|
|
15
|
+
await new Promise(resolve => {
|
|
16
|
+
const monitor = child_process_1.default.spawn("udevadm", ["monitor", "--kernel", "--property"], { stdio: 'pipe' });
|
|
17
|
+
monitor.stderr.on('data', (data) => logger.error('Udev monitor error', { error: data.toString().trim() }));
|
|
18
|
+
monitor.stdout.on('data', (line) => cb(line.toString().trim()));
|
|
19
|
+
monitor.on('exit', () => resolve());
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
async function monitor(onChange) {
|
|
23
|
+
while (true) {
|
|
24
|
+
let result = {};
|
|
25
|
+
logger.info('Udev: monitoring devices');
|
|
26
|
+
await monitorProcess(line => {
|
|
27
|
+
`${line}\n`.split('\n').map(chunk => chunk.trim()).forEach(chunk => {
|
|
28
|
+
if (chunk === "") {
|
|
29
|
+
onChange(result);
|
|
30
|
+
result = {};
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
const info = /^([A-Z0-9]+)=(.+)$/.exec(chunk);
|
|
34
|
+
if (!info)
|
|
35
|
+
return;
|
|
36
|
+
const [, name, value] = info;
|
|
37
|
+
result[name.toLowerCase()] = value;
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
logger.info('Udev: monitor exited, restarting');
|
|
41
|
+
await new Promise(resolve => setTimeout(resolve, 5 * 1000));
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
exports.default = { monitor };
|
package/dist/version.js
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const fs_1 = require("fs");
|
|
4
|
+
const path_1 = require("path");
|
|
5
|
+
if (process.argv[2] === '--version' || process.argv[2] === '-v') {
|
|
6
|
+
const packagePath = (0, path_1.join)(__dirname, '../package.json');
|
|
7
|
+
const packageData = JSON.parse((0, fs_1.readFileSync)(packagePath, 'utf8'));
|
|
8
|
+
console.log(`physcreen-manager v${packageData.version}`);
|
|
9
|
+
process.exit(0);
|
|
10
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@phystack/screen-phyos",
|
|
3
|
+
"version": "4.3.40-dev",
|
|
4
|
+
"description": "PhyOS Screen",
|
|
5
|
+
"license": "UNLICENSED",
|
|
6
|
+
"publishConfig": {
|
|
7
|
+
"access": "public"
|
|
8
|
+
},
|
|
9
|
+
"engines": {
|
|
10
|
+
"node": ">=20.0.0"
|
|
11
|
+
},
|
|
12
|
+
"dependencies": {
|
|
13
|
+
"@azure/storage-blob": "^12.23.0",
|
|
14
|
+
"@phystack/axios-proxy": "4.3.40-dev",
|
|
15
|
+
"@phystack/hub-client": "4.3.40-dev",
|
|
16
|
+
"@phystack/phy-logger": "4.3.40-dev",
|
|
17
|
+
"@vercel/ncc": "^0.38.1",
|
|
18
|
+
"get-image-colors": "^4.0.0",
|
|
19
|
+
"global-agent": "^3.0.0",
|
|
20
|
+
"http-proxy": "^1.18.1",
|
|
21
|
+
"node-fetch": "^2.6.1",
|
|
22
|
+
"pkg": "^5.8.1",
|
|
23
|
+
"ws": "^7.4.4"
|
|
24
|
+
},
|
|
25
|
+
"devDependencies": {
|
|
26
|
+
"@babel/preset-react": "^7.18.6",
|
|
27
|
+
"@babel/preset-typescript": "^7.21.0",
|
|
28
|
+
"@types/get-image-colors": "^2.0.0",
|
|
29
|
+
"@types/global-agent": "^2.1.1",
|
|
30
|
+
"@types/http-proxy": "^1.17.8",
|
|
31
|
+
"@types/node-fetch": "^2.6.11",
|
|
32
|
+
"@types/react": "^18.0.30",
|
|
33
|
+
"@types/react-dom": "^18.0.11",
|
|
34
|
+
"@types/styled-components": "^5.1.26",
|
|
35
|
+
"@types/ws": "^7.4.1",
|
|
36
|
+
"antd": "^5.3.3",
|
|
37
|
+
"babel-loader": "^9.1.2",
|
|
38
|
+
"css-loader": "^6.7.3",
|
|
39
|
+
"qrcode.react": "^3.1.0",
|
|
40
|
+
"react": "^18.2.0",
|
|
41
|
+
"react-dom": "^18.2.0",
|
|
42
|
+
"react-simple-keyboard": "^3.5.47",
|
|
43
|
+
"rimraf": "^3.0.2",
|
|
44
|
+
"style-loader": "^3.3.2",
|
|
45
|
+
"styled-components": "^5.3.9",
|
|
46
|
+
"ts-loader": "^9.4.2",
|
|
47
|
+
"typescript": "^4.2.3",
|
|
48
|
+
"uuid": "^8.3.2"
|
|
49
|
+
},
|
|
50
|
+
"bin": {
|
|
51
|
+
"physcreen-manager": "./bin/index.js"
|
|
52
|
+
},
|
|
53
|
+
"files": [
|
|
54
|
+
"bin/**/*",
|
|
55
|
+
"dist/**/*"
|
|
56
|
+
],
|
|
57
|
+
"scripts": {
|
|
58
|
+
"build": "tsc",
|
|
59
|
+
"lint": "eslint --ext .js,.ts src",
|
|
60
|
+
"lint-fix": "tsc --noEmit && eslint --ext .js,.ts src --fix && prettier --write ."
|
|
61
|
+
},
|
|
62
|
+
"gitHead": "53c505b3ecad47ebd4f38819c07b0ec1227214a6"
|
|
63
|
+
}
|