project-foundry 1.0.1 → 1.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/daemon.js +11 -4
- package/src/local-printer-executor.js +120 -0
- package/src/service-manager.js +67 -7
- package/src/tui.js +68 -16
- package/src/tunnel-client.js +10 -42
package/package.json
CHANGED
package/src/daemon.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import http from 'node:http';
|
|
2
|
+
import https from 'node:https';
|
|
2
3
|
import fs from 'node:fs';
|
|
3
4
|
import path from 'node:path';
|
|
4
5
|
import { readConfig } from './config.js';
|
|
@@ -75,10 +76,14 @@ export function startDaemon() {
|
|
|
75
76
|
|
|
76
77
|
fileStream.on('finish', async () => {
|
|
77
78
|
try {
|
|
78
|
-
// Encaminha requisição completa com cabeçalhos para a nuvem
|
|
79
79
|
const cloudUrl = `${config.serverUrl.replace(/\/$/, '')}${url.pathname}`;
|
|
80
|
-
const
|
|
80
|
+
const isHttps = cloudUrl.startsWith('https:');
|
|
81
|
+
const transport = isHttps ? https : http;
|
|
82
|
+
|
|
83
|
+
const cloudReq = transport.request(cloudUrl, {
|
|
81
84
|
method: 'POST',
|
|
85
|
+
family: 4,
|
|
86
|
+
rejectUnauthorized: false,
|
|
82
87
|
headers: {
|
|
83
88
|
...req.headers,
|
|
84
89
|
host: new URL(config.serverUrl).host,
|
|
@@ -111,14 +116,16 @@ export function startDaemon() {
|
|
|
111
116
|
return;
|
|
112
117
|
}
|
|
113
118
|
|
|
114
|
-
// Proxy reverso
|
|
119
|
+
// Proxy reverso para todas as outras requisições
|
|
115
120
|
try {
|
|
116
121
|
const targetUrl = new URL(req.url, config.serverUrl);
|
|
117
122
|
const isHttps = targetUrl.protocol === 'https:';
|
|
118
|
-
const transport = isHttps ?
|
|
123
|
+
const transport = isHttps ? https : http;
|
|
119
124
|
|
|
120
125
|
const proxyReq = transport.request(targetUrl, {
|
|
121
126
|
method: req.method,
|
|
127
|
+
family: 4,
|
|
128
|
+
rejectUnauthorized: false,
|
|
122
129
|
headers: {
|
|
123
130
|
...req.headers,
|
|
124
131
|
host: targetUrl.host,
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
export async function executeLocalPrinterAction({ action, printerId, connectionConfig, payload }) {
|
|
2
|
+
if (!connectionConfig) {
|
|
3
|
+
throw new Error('Configuração de conexão da impressora não foi fornecida pelo servidor');
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
const type = connectionConfig.type;
|
|
7
|
+
const baseUrl = (connectionConfig.url || `http://${connectionConfig.host}:${connectionConfig.port || 80}`).replace(/\/$/, '');
|
|
8
|
+
const apiKey = connectionConfig.apiKey;
|
|
9
|
+
const headers = apiKey ? { 'X-Api-Key': apiKey } : {};
|
|
10
|
+
|
|
11
|
+
// 1. Verificação de Conexão
|
|
12
|
+
if (action === 'connect') {
|
|
13
|
+
if (type === 'klipper') {
|
|
14
|
+
const infoRes = await fetch(`${baseUrl}/server/info`, { headers, signal: AbortSignal.timeout(8000) });
|
|
15
|
+
if (!infoRes.ok) {
|
|
16
|
+
throw new Error(`Moonraker retornou HTTP ${infoRes.status}: ${infoRes.statusText}`);
|
|
17
|
+
}
|
|
18
|
+
const info = await infoRes.json();
|
|
19
|
+
const klippyState = info?.result?.klippy_state || 'ready';
|
|
20
|
+
|
|
21
|
+
let status = klippyState === 'ready' ? 'IDLE' : 'ERROR';
|
|
22
|
+
let message = `Klipper: ${klippyState}`;
|
|
23
|
+
let temperatures = {};
|
|
24
|
+
|
|
25
|
+
try {
|
|
26
|
+
const telRes = await fetch(
|
|
27
|
+
`${baseUrl}/printer/objects/query?print_stats&extruder&heater_bed`,
|
|
28
|
+
{ headers, signal: AbortSignal.timeout(5000) }
|
|
29
|
+
);
|
|
30
|
+
if (telRes.ok) {
|
|
31
|
+
const tel = await telRes.json();
|
|
32
|
+
const stats = tel?.result?.status?.print_stats || {};
|
|
33
|
+
const ext = tel?.result?.status?.extruder || {};
|
|
34
|
+
const bed = tel?.result?.status?.heater_bed || {};
|
|
35
|
+
const st = String(stats.state || '').toLowerCase();
|
|
36
|
+
if (st === 'printing') status = 'PRINTING';
|
|
37
|
+
else if (st === 'paused') status = 'PAUSED';
|
|
38
|
+
|
|
39
|
+
temperatures = {
|
|
40
|
+
hotend: ext.temperature,
|
|
41
|
+
hotendTarget: ext.target,
|
|
42
|
+
bed: bed.temperature,
|
|
43
|
+
bedTarget: bed.target,
|
|
44
|
+
};
|
|
45
|
+
if (stats.message) message = stats.message;
|
|
46
|
+
}
|
|
47
|
+
} catch {}
|
|
48
|
+
|
|
49
|
+
return {
|
|
50
|
+
online: true,
|
|
51
|
+
status,
|
|
52
|
+
message,
|
|
53
|
+
temperatures,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (type === 'octoprint') {
|
|
58
|
+
const verRes = await fetch(`${baseUrl}/api/version`, { headers, signal: AbortSignal.timeout(8000) });
|
|
59
|
+
if (!verRes.ok) {
|
|
60
|
+
throw new Error(`OctoPrint retornou HTTP ${verRes.status}: ${verRes.statusText}`);
|
|
61
|
+
}
|
|
62
|
+
return {
|
|
63
|
+
online: true,
|
|
64
|
+
status: 'IDLE',
|
|
65
|
+
message: 'OctoPrint conectado com sucesso',
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
throw new Error(`Protocolo de impressora "${type}" não suportado para conexão direta`);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// 2. Snapshot de Câmera Local
|
|
73
|
+
if (action === 'camera/snapshot') {
|
|
74
|
+
const camUrl = connectionConfig.cameraSnapshotUrl || connectionConfig.cameraUrl || `${baseUrl}/webcam/?action=snapshot`;
|
|
75
|
+
const camRes = await fetch(camUrl, { signal: AbortSignal.timeout(10000) });
|
|
76
|
+
if (!camRes.ok) {
|
|
77
|
+
throw new Error(`Câmera retornou HTTP ${camRes.status}: ${camRes.statusText}`);
|
|
78
|
+
}
|
|
79
|
+
const buf = await camRes.arrayBuffer();
|
|
80
|
+
return { base64: Buffer.from(buf).toString('base64') };
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// 3. Controle da Impressora (Pausa, Emergência, Motores, etc.)
|
|
84
|
+
if (action === 'control') {
|
|
85
|
+
const cmd = payload?.command;
|
|
86
|
+
if (type === 'klipper') {
|
|
87
|
+
const cmdMap = {
|
|
88
|
+
pause: '/printer/print/pause',
|
|
89
|
+
resume: '/printer/print/resume',
|
|
90
|
+
cancel: '/printer/print/cancel',
|
|
91
|
+
emergency_stop: '/printer/emergency_stop',
|
|
92
|
+
restart: '/printer/restart',
|
|
93
|
+
firmware_restart: '/printer/firmware_restart',
|
|
94
|
+
};
|
|
95
|
+
const endpoint = cmdMap[cmd];
|
|
96
|
+
if (!endpoint) throw new Error(`Comando de controle "${cmd}" não suportado em Klipper`);
|
|
97
|
+
const res = await fetch(`${baseUrl}${endpoint}`, { method: 'POST', headers, signal: AbortSignal.timeout(8000) });
|
|
98
|
+
return { success: res.ok };
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// 4. Comandos de Trabalho (Job Pause / Resume / Cancel)
|
|
103
|
+
if (action.startsWith('job/')) {
|
|
104
|
+
const jobCmd = action.replace('job/', '');
|
|
105
|
+
if (type === 'klipper') {
|
|
106
|
+
const cmdMap = {
|
|
107
|
+
pause: '/printer/print/pause',
|
|
108
|
+
resume: '/printer/print/resume',
|
|
109
|
+
cancel: '/printer/print/cancel',
|
|
110
|
+
};
|
|
111
|
+
const endpoint = cmdMap[jobCmd];
|
|
112
|
+
if (endpoint) {
|
|
113
|
+
const res = await fetch(`${baseUrl}${endpoint}`, { method: 'POST', headers, signal: AbortSignal.timeout(8000) });
|
|
114
|
+
return { success: res.ok };
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
throw new Error(`Ação "${action}" não suportada`);
|
|
120
|
+
}
|
package/src/service-manager.js
CHANGED
|
@@ -2,6 +2,7 @@ import { execSync, spawn } from 'node:child_process';
|
|
|
2
2
|
import fs from 'node:fs';
|
|
3
3
|
import path from 'node:path';
|
|
4
4
|
import os from 'node:os';
|
|
5
|
+
import { getConfigDir } from './config.js';
|
|
5
6
|
|
|
6
7
|
export async function isServerHealthy(port = 3010) {
|
|
7
8
|
try {
|
|
@@ -15,13 +16,71 @@ export async function isServerHealthy(port = 3010) {
|
|
|
15
16
|
}
|
|
16
17
|
}
|
|
17
18
|
|
|
19
|
+
export function stopRunningDaemon() {
|
|
20
|
+
const isWin = process.platform === 'win32';
|
|
21
|
+
if (isWin) {
|
|
22
|
+
try {
|
|
23
|
+
execSync('schtasks /end /tn "ProjectFoundry"', { stdio: 'ignore' });
|
|
24
|
+
} catch {}
|
|
25
|
+
} else {
|
|
26
|
+
try {
|
|
27
|
+
execSync('systemctl stop foundry.service', { stdio: 'ignore' });
|
|
28
|
+
} catch {}
|
|
29
|
+
try {
|
|
30
|
+
execSync('systemctl --user stop foundry.service', { stdio: 'ignore' });
|
|
31
|
+
} catch {}
|
|
32
|
+
try {
|
|
33
|
+
execSync("pkill -f 'foundry.*daemon' || true", { stdio: 'ignore' });
|
|
34
|
+
} catch {}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function restartAutostartService() {
|
|
39
|
+
const isWin = process.platform === 'win32';
|
|
40
|
+
if (isWin) {
|
|
41
|
+
try {
|
|
42
|
+
execSync('schtasks /run /tn "ProjectFoundry"', { stdio: 'ignore' });
|
|
43
|
+
} catch {}
|
|
44
|
+
} else {
|
|
45
|
+
try {
|
|
46
|
+
execSync('systemctl restart foundry.service', { stdio: 'ignore' });
|
|
47
|
+
} catch {
|
|
48
|
+
try {
|
|
49
|
+
execSync('systemctl --user restart foundry.service', { stdio: 'ignore' });
|
|
50
|
+
} catch {}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function ensurePermanentAppPath(entryPath) {
|
|
56
|
+
const rootDir = path.resolve(path.dirname(entryPath), '..');
|
|
57
|
+
|
|
58
|
+
// Se estiver rodando de cache temporário do npx ou temp
|
|
59
|
+
if (entryPath.includes('_npx') || entryPath.includes('npm-cache') || entryPath.includes('Temp') || entryPath.includes('tmp')) {
|
|
60
|
+
const permanentAppDir = path.join(getConfigDir(), 'app');
|
|
61
|
+
if (!fs.existsSync(permanentAppDir)) {
|
|
62
|
+
fs.mkdirSync(permanentAppDir, { recursive: true });
|
|
63
|
+
}
|
|
64
|
+
try {
|
|
65
|
+
// Copia arquivos do pacote para local permanente
|
|
66
|
+
fs.cpSync(rootDir, permanentAppDir, { recursive: true });
|
|
67
|
+
const permanentEntry = path.join(permanentAppDir, 'bin', 'foundry.js');
|
|
68
|
+
if (fs.existsSync(permanentEntry)) {
|
|
69
|
+
return permanentEntry;
|
|
70
|
+
}
|
|
71
|
+
} catch {}
|
|
72
|
+
}
|
|
73
|
+
return entryPath;
|
|
74
|
+
}
|
|
75
|
+
|
|
18
76
|
export function installAutostartService(serverEntryPath) {
|
|
19
77
|
const isWin = process.platform === 'win32';
|
|
78
|
+
const permanentPath = ensurePermanentAppPath(serverEntryPath);
|
|
20
79
|
|
|
21
80
|
if (isWin) {
|
|
22
81
|
try {
|
|
23
82
|
// Cria tarefa no Agendador do Windows com início automático em modo daemon
|
|
24
|
-
const cmd = `schtasks /create /tn "ProjectFoundry" /tr "node \\"${
|
|
83
|
+
const cmd = `schtasks /create /tn "ProjectFoundry" /tr "node \\"${permanentPath}\\" daemon" /sc onstart /f`;
|
|
25
84
|
execSync(cmd, { stdio: 'pipe' });
|
|
26
85
|
return { success: true, type: 'windows-task', name: 'ProjectFoundry' };
|
|
27
86
|
} catch (err) {
|
|
@@ -32,13 +91,14 @@ export function installAutostartService(serverEntryPath) {
|
|
|
32
91
|
// Linux (systemd)
|
|
33
92
|
try {
|
|
34
93
|
const isRoot = typeof process.getuid === 'function' && process.getuid() === 0;
|
|
94
|
+
const nodeBin = process.execPath || '/usr/bin/node';
|
|
35
95
|
const serviceContent = `[Unit]
|
|
36
|
-
Description=Project Foundry Local Server
|
|
96
|
+
Description=Project Foundry Local Farm Server
|
|
37
97
|
After=network.target
|
|
38
98
|
|
|
39
99
|
[Service]
|
|
40
100
|
Type=simple
|
|
41
|
-
ExecStart
|
|
101
|
+
ExecStart=${nodeBin} ${permanentPath} daemon
|
|
42
102
|
Restart=always
|
|
43
103
|
RestartSec=5
|
|
44
104
|
Environment=NODE_ENV=production
|
|
@@ -49,13 +109,13 @@ WantedBy=default.target
|
|
|
49
109
|
|
|
50
110
|
if (isRoot) {
|
|
51
111
|
fs.writeFileSync('/etc/systemd/system/foundry.service', serviceContent, 'utf8');
|
|
52
|
-
execSync('systemctl daemon-reload && systemctl enable foundry.service', { stdio: 'pipe' });
|
|
112
|
+
execSync('systemctl daemon-reload && systemctl enable foundry.service && systemctl restart foundry.service', { stdio: 'pipe' });
|
|
53
113
|
return { success: true, type: 'systemd-system', name: 'foundry.service' };
|
|
54
114
|
} else {
|
|
55
115
|
const userSystemdDir = path.join(os.homedir(), '.config', 'systemd', 'user');
|
|
56
116
|
fs.mkdirSync(userSystemdDir, { recursive: true });
|
|
57
117
|
fs.writeFileSync(path.join(userSystemdDir, 'foundry.service'), serviceContent, 'utf8');
|
|
58
|
-
execSync('systemctl --user daemon-reload && systemctl --user enable foundry.service', { stdio: 'pipe' });
|
|
118
|
+
execSync('systemctl --user daemon-reload && systemctl --user enable foundry.service && systemctl --user restart foundry.service', { stdio: 'pipe' });
|
|
59
119
|
return { success: true, type: 'systemd-user', name: 'foundry.service' };
|
|
60
120
|
}
|
|
61
121
|
} catch (err) {
|
|
@@ -64,8 +124,8 @@ WantedBy=default.target
|
|
|
64
124
|
}
|
|
65
125
|
|
|
66
126
|
export function startDaemonBackground(serverEntryPath) {
|
|
67
|
-
|
|
68
|
-
const child = spawn(process.execPath, [
|
|
127
|
+
const permanentPath = ensurePermanentAppPath(serverEntryPath);
|
|
128
|
+
const child = spawn(process.execPath, [permanentPath, 'daemon'], {
|
|
69
129
|
detached: true,
|
|
70
130
|
stdio: 'ignore',
|
|
71
131
|
});
|
package/src/tui.js
CHANGED
|
@@ -1,12 +1,27 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
import { execSync } from 'node:child_process';
|
|
1
5
|
import * as p from '@clack/prompts';
|
|
2
6
|
import pc from 'picocolors';
|
|
3
7
|
import open from 'open';
|
|
4
8
|
import { readConfig, updateStoragePath } from './config.js';
|
|
5
|
-
import { isServerHealthy } from './service-manager.js';
|
|
9
|
+
import { isServerHealthy, stopRunningDaemon, restartAutostartService } from './service-manager.js';
|
|
10
|
+
|
|
11
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
12
|
+
|
|
13
|
+
function getPackageVersion() {
|
|
14
|
+
try {
|
|
15
|
+
const pkg = JSON.parse(fs.readFileSync(path.join(__dirname, '..', 'package.json'), 'utf8'));
|
|
16
|
+
return `v${pkg.version}`;
|
|
17
|
+
} catch {
|
|
18
|
+
return 'v1.0.3';
|
|
19
|
+
}
|
|
20
|
+
}
|
|
6
21
|
|
|
7
22
|
export async function runInteractiveTui() {
|
|
8
23
|
const config = readConfig();
|
|
9
|
-
const version =
|
|
24
|
+
const version = getPackageVersion();
|
|
10
25
|
const localPort = config?.localPort || 3010;
|
|
11
26
|
const localUrl = `http://localhost:${localPort}`;
|
|
12
27
|
|
|
@@ -33,7 +48,7 @@ export async function runInteractiveTui() {
|
|
|
33
48
|
} else {
|
|
34
49
|
p.note(
|
|
35
50
|
`${pc.yellow('○')} O servidor local não respondeu em ${localUrl}.\n` +
|
|
36
|
-
`
|
|
51
|
+
`O serviço pode estar iniciando em segundo plano ou parado.`,
|
|
37
52
|
'Atenção'
|
|
38
53
|
);
|
|
39
54
|
}
|
|
@@ -43,16 +58,16 @@ export async function runInteractiveTui() {
|
|
|
43
58
|
message: 'Escolha uma opção:',
|
|
44
59
|
options: [
|
|
45
60
|
{ value: 'web', label: 'Web UI (Open in Browser)', hint: 'Abre o painel no navegador' },
|
|
46
|
-
{ value: 'status', label: 'Terminal UI (Status & Health)', hint: 'Verifica conexões locais' },
|
|
61
|
+
{ value: 'status', label: 'Terminal UI (Status & Health)', hint: 'Verifica saúde e conexões locais' },
|
|
47
62
|
{ value: 'storage', label: 'Storage (HD Externo)', hint: 'Visualizar ou alterar pasta de arquivos' },
|
|
48
|
-
{ value: 'update', label: `Update (current: ${version})`, hint: 'Verifica
|
|
49
|
-
{ value: 'hide', label: 'Hide to Tray (Background)', hint: '
|
|
50
|
-
{ value: '
|
|
63
|
+
{ value: 'update', label: `Update (current: ${version})`, hint: 'Verifica e atualiza para versão mais recente' },
|
|
64
|
+
{ value: 'hide', label: 'Hide to Tray (Background)', hint: 'Fecha o console e mantém o servidor rodando' },
|
|
65
|
+
{ value: 'stop', label: 'Stop Server', hint: 'Desliga o servidor local da farm' },
|
|
51
66
|
],
|
|
52
67
|
});
|
|
53
68
|
|
|
54
69
|
if (p.isCancel(choice) || choice === 'hide') {
|
|
55
|
-
p.outro(pc.green('
|
|
70
|
+
p.outro(pc.green('Painel fechado. O servidor da farm continua rodando em segundo plano.'));
|
|
56
71
|
process.exit(0);
|
|
57
72
|
}
|
|
58
73
|
|
|
@@ -100,19 +115,56 @@ export async function runInteractiveTui() {
|
|
|
100
115
|
}
|
|
101
116
|
|
|
102
117
|
if (choice === 'update') {
|
|
103
|
-
|
|
118
|
+
const s = p.spinner();
|
|
119
|
+
s.start('Verificando versão mais recente no npm...');
|
|
120
|
+
try {
|
|
121
|
+
const res = await fetch('https://registry.npmjs.org/project-foundry/latest', {
|
|
122
|
+
signal: AbortSignal.timeout(6000),
|
|
123
|
+
});
|
|
124
|
+
const data = await res.json();
|
|
125
|
+
const latestVersion = `v${data.version}`;
|
|
126
|
+
s.stop();
|
|
127
|
+
|
|
128
|
+
if (latestVersion !== version) {
|
|
129
|
+
p.log.warn(`Nova versão disponível: ${pc.green(latestVersion)} (você está na ${version})`);
|
|
130
|
+
const doUpdate = await p.confirm({
|
|
131
|
+
message: `Deseja atualizar para a versão ${latestVersion}?`,
|
|
132
|
+
initialValue: true,
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
if (doUpdate && !p.isCancel(doUpdate)) {
|
|
136
|
+
s.start('Atualizando pacote e reiniciando serviço...');
|
|
137
|
+
try {
|
|
138
|
+
execSync('npm install -g project-foundry@latest', { stdio: 'pipe' });
|
|
139
|
+
restartAutostartService();
|
|
140
|
+
s.stop(pc.green(`Atualizado para ${latestVersion} com sucesso!`));
|
|
141
|
+
p.outro(pc.cyan('Reinicie o menu com "foundry" ou "npx project-foundry" para aplicar.'));
|
|
142
|
+
process.exit(0);
|
|
143
|
+
} catch (err) {
|
|
144
|
+
s.stop(pc.yellow(`Atualização global não permitida sem sudo. Execute no terminal: npm install -g project-foundry@latest`));
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
} else {
|
|
148
|
+
p.log.success(`Você já está na versão mais recente (${version}).`);
|
|
149
|
+
}
|
|
150
|
+
} catch (err) {
|
|
151
|
+
s.stop(pc.red(`Erro ao verificar: ${err.message}`));
|
|
152
|
+
}
|
|
104
153
|
}
|
|
105
154
|
|
|
106
|
-
if (choice === '
|
|
155
|
+
if (choice === 'stop') {
|
|
107
156
|
const shouldStop = await p.confirm({
|
|
108
|
-
message: '
|
|
109
|
-
active: '
|
|
110
|
-
inactive: '
|
|
111
|
-
initialValue:
|
|
157
|
+
message: 'Atenção: deseja realmente parar o servidor local da farm?',
|
|
158
|
+
active: 'Sim, parar servidor completamente',
|
|
159
|
+
inactive: 'Não, manter ligado',
|
|
160
|
+
initialValue: false,
|
|
112
161
|
});
|
|
113
162
|
|
|
114
|
-
|
|
115
|
-
|
|
163
|
+
if (shouldStop && !p.isCancel(shouldStop)) {
|
|
164
|
+
stopRunningDaemon();
|
|
165
|
+
p.outro(pc.yellow('Servidor local encerrado. Para religar, execute "foundry" no terminal.'));
|
|
166
|
+
process.exit(0);
|
|
167
|
+
}
|
|
116
168
|
}
|
|
117
169
|
}
|
|
118
170
|
}
|
package/src/tunnel-client.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import WebSocket from 'ws';
|
|
2
2
|
import { readConfig } from './config.js';
|
|
3
|
+
import { executeLocalPrinterAction } from './local-printer-executor.js';
|
|
3
4
|
|
|
4
5
|
export class EdgeTunnelClient {
|
|
5
6
|
constructor() {
|
|
@@ -90,52 +91,19 @@ export class EdgeTunnelClient {
|
|
|
90
91
|
}
|
|
91
92
|
|
|
92
93
|
async handleCloudCommand(msg) {
|
|
93
|
-
const config = readConfig();
|
|
94
|
-
const localPort = config?.localPort || 3010;
|
|
95
|
-
const localBase = `http://127.0.0.1:${localPort}/api/v1`;
|
|
96
|
-
const headers = {
|
|
97
|
-
'Content-Type': 'application/json',
|
|
98
|
-
'X-Api-Key': config.apiKey,
|
|
99
|
-
};
|
|
100
|
-
|
|
101
94
|
try {
|
|
102
95
|
let resultData = null;
|
|
103
96
|
|
|
104
|
-
if (msg.
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
} else if (msg.action === 'control') {
|
|
112
|
-
const cRes = await fetch(`${localBase}/printers/${msg.printerId}/control`, {
|
|
113
|
-
method: 'POST',
|
|
114
|
-
headers,
|
|
115
|
-
body: JSON.stringify(msg.payload || {}),
|
|
116
|
-
});
|
|
117
|
-
const cJson = await cRes.json();
|
|
118
|
-
if (!cRes.ok) throw new Error(cJson?.error?.message || 'Falha no comando de controle');
|
|
119
|
-
resultData = cJson.data;
|
|
120
|
-
} else if (msg.action === 'connect') {
|
|
121
|
-
const connRes = await fetch(`${localBase}/printers/${msg.printerId}/connect`, {
|
|
122
|
-
method: 'POST',
|
|
123
|
-
headers,
|
|
124
|
-
body: JSON.stringify({}),
|
|
125
|
-
});
|
|
126
|
-
const connJson = await connRes.json();
|
|
127
|
-
if (!connRes.ok) throw new Error(connJson?.error?.message || 'Falha ao conectar à impressora local');
|
|
128
|
-
resultData = connJson.data;
|
|
129
|
-
} else if (msg.action.startsWith('job/')) {
|
|
130
|
-
const jobAction = msg.action.replace('job/', '');
|
|
131
|
-
const jRes = await fetch(`${localBase}/jobs/${msg.payload?.jobId}/${jobAction}`, {
|
|
132
|
-
method: 'POST',
|
|
133
|
-
headers,
|
|
134
|
-
body: JSON.stringify({}),
|
|
97
|
+
if (msg.payload?.connectionConfig) {
|
|
98
|
+
// Executa diretamente na rede local (LAN) da farm
|
|
99
|
+
resultData = await executeLocalPrinterAction({
|
|
100
|
+
action: msg.action,
|
|
101
|
+
printerId: msg.printerId,
|
|
102
|
+
connectionConfig: msg.payload.connectionConfig,
|
|
103
|
+
payload: msg.payload,
|
|
135
104
|
});
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
resultData = jJson.data;
|
|
105
|
+
} else {
|
|
106
|
+
throw new Error('Configuração de conexão da impressora não foi fornecida pelo servidor central');
|
|
139
107
|
}
|
|
140
108
|
|
|
141
109
|
if (this.ws && this.ws.readyState === WebSocket.OPEN) {
|