nstantpage-agent 0.8.11 → 0.8.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/commands/login.js +12 -1
- package/dist/commands/start.js +79 -62
- package/dist/commands/status.d.ts +6 -1
- package/dist/commands/status.js +81 -31
- package/dist/tunnel.d.ts +1 -0
- package/dist/tunnel.js +4 -4
- package/package.json +1 -1
package/dist/commands/login.js
CHANGED
|
@@ -5,6 +5,7 @@ import chalk from 'chalk';
|
|
|
5
5
|
import open from 'open';
|
|
6
6
|
import http from 'http';
|
|
7
7
|
import { getConfig } from '../config.js';
|
|
8
|
+
import { serviceInstallCommand } from './service.js';
|
|
8
9
|
/**
|
|
9
10
|
* Resolve the frontend URL based on gateway.
|
|
10
11
|
* - If gateway points to localhost → frontend is http://localhost:5001
|
|
@@ -178,6 +179,16 @@ export async function loginCommand(options = {}) {
|
|
|
178
179
|
if (email)
|
|
179
180
|
console.log(chalk.gray(` Account: ${email}`));
|
|
180
181
|
console.log(chalk.gray(` Server: ${isLocal ? 'localhost (dev)' : 'nstantpage.com'}`));
|
|
181
|
-
|
|
182
|
+
// Auto-install background service with the new token
|
|
183
|
+
const gateway = conf.get('gatewayUrl') || 'wss://webprev.live';
|
|
184
|
+
try {
|
|
185
|
+
await serviceInstallCommand({ gateway });
|
|
186
|
+
console.log(chalk.green(' ✓ Agent is running as a background service.'));
|
|
187
|
+
console.log(chalk.gray(' Use "nstantpage logs" to view agent output.'));
|
|
188
|
+
}
|
|
189
|
+
catch (err) {
|
|
190
|
+
console.log(chalk.yellow(` ⚠ Could not install background service: ${err.message}`));
|
|
191
|
+
console.log(chalk.gray(' Run "nstantpage run" to start manually.'));
|
|
192
|
+
}
|
|
182
193
|
}
|
|
183
194
|
//# sourceMappingURL=login.js.map
|
package/dist/commands/start.js
CHANGED
|
@@ -579,11 +579,13 @@ async function startStandbyMode(token, options, backendUrl, deviceId) {
|
|
|
579
579
|
existing.tunnel.disconnect();
|
|
580
580
|
await existing.localServer.stop();
|
|
581
581
|
activeProjects.delete(pid);
|
|
582
|
-
// Wipe the project directory
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
582
|
+
// Wipe the project directory (only for non-LocalRepo)
|
|
583
|
+
if (!opts?.localFolderPath) {
|
|
584
|
+
const projectDir = resolveProjectDir('.', pid);
|
|
585
|
+
if (fs.existsSync(projectDir)) {
|
|
586
|
+
console.log(chalk.gray(` Wiping project directory: ${projectDir}`));
|
|
587
|
+
fs.rmSync(projectDir, { recursive: true, force: true });
|
|
588
|
+
}
|
|
587
589
|
}
|
|
588
590
|
}
|
|
589
591
|
else if (activeProjects.has(pid)) {
|
|
@@ -592,6 +594,7 @@ async function startStandbyMode(token, options, backendUrl, deviceId) {
|
|
|
592
594
|
}
|
|
593
595
|
const result = await startAdditionalProject(pid, {
|
|
594
596
|
token, backendUrl, gatewayUrl: options.gateway, deviceId, noDev: options.noDev,
|
|
597
|
+
localFolderPath: opts?.localFolderPath,
|
|
595
598
|
onProgress: (phase, message) => {
|
|
596
599
|
standbyTunnel.sendSetupProgress(pid, phase, message);
|
|
597
600
|
},
|
|
@@ -678,6 +681,7 @@ async function startStandbyMode(token, options, backendUrl, deviceId) {
|
|
|
678
681
|
}
|
|
679
682
|
const result = await startAdditionalProject(projectId, {
|
|
680
683
|
token, backendUrl, gatewayUrl: options.gateway, deviceId, noDev: options.noDev,
|
|
684
|
+
localFolderPath: opts?.localFolderPath,
|
|
681
685
|
onProgress: (phase, message) => {
|
|
682
686
|
standbyTunnel.sendSetupProgress(projectId, phase, message);
|
|
683
687
|
},
|
|
@@ -736,29 +740,37 @@ async function startAdditionalProject(projectId, opts) {
|
|
|
736
740
|
const progress = opts.onProgress || (() => { });
|
|
737
741
|
const timings = {};
|
|
738
742
|
const t0 = Date.now();
|
|
739
|
-
|
|
743
|
+
const isLocalRepo = !!opts.localFolderPath;
|
|
744
|
+
console.log(chalk.blue(`\n 📦 Starting project ${projectId}${isLocalRepo ? ' (LocalRepo)' : ''}...`));
|
|
740
745
|
try {
|
|
741
746
|
const allocated = allocatePortsForProject(projectId);
|
|
742
|
-
const projectDir = resolveProjectDir('.', projectId);
|
|
747
|
+
const projectDir = isLocalRepo ? opts.localFolderPath : resolveProjectDir('.', projectId);
|
|
743
748
|
if (!fs.existsSync(projectDir))
|
|
744
749
|
fs.mkdirSync(projectDir, { recursive: true });
|
|
745
750
|
// Ensure ports are clear for this project before starting
|
|
746
751
|
cleanupPreviousAgent(projectId, allocated.apiPort, allocated.devPort);
|
|
747
752
|
await new Promise(r => setTimeout(r, 50));
|
|
748
753
|
// ── Phase 1: API server + file fetch + tunnel connect (all parallel) ──
|
|
749
|
-
|
|
750
|
-
|
|
754
|
+
if (!isLocalRepo) {
|
|
755
|
+
progress('fetching-files', 'Fetching project files...');
|
|
756
|
+
}
|
|
757
|
+
else {
|
|
758
|
+
progress('starting-server', 'Starting local server...');
|
|
759
|
+
}
|
|
760
|
+
// Provision database if PostgreSQL is available (skip for LocalRepo)
|
|
751
761
|
let databaseUrl = null;
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
762
|
+
if (!isLocalRepo) {
|
|
763
|
+
try {
|
|
764
|
+
const hasPg = await probeLocalPostgres();
|
|
765
|
+
if (hasPg) {
|
|
766
|
+
databaseUrl = await ensureLocalProjectDb(projectId);
|
|
767
|
+
if (databaseUrl) {
|
|
768
|
+
console.log(chalk.green(` ✓ Database ready: project_${projectId}`));
|
|
769
|
+
}
|
|
758
770
|
}
|
|
759
771
|
}
|
|
772
|
+
catch { }
|
|
760
773
|
}
|
|
761
|
-
catch { }
|
|
762
774
|
const serverEnv = {};
|
|
763
775
|
if (databaseUrl)
|
|
764
776
|
serverEnv['DATABASE_URL'] = databaseUrl;
|
|
@@ -799,17 +811,20 @@ async function startAdditionalProject(projectId, opts) {
|
|
|
799
811
|
tunnel.startBackgroundReconnect();
|
|
800
812
|
}
|
|
801
813
|
})();
|
|
814
|
+
// Fetch project files from backend (skip for LocalRepo — files are on disk)
|
|
802
815
|
const fileStart = Date.now();
|
|
803
816
|
let fetchedVersionId;
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
817
|
+
if (!isLocalRepo) {
|
|
818
|
+
try {
|
|
819
|
+
const { versionId } = await fetchProjectFiles(opts.backendUrl, projectId, projectDir, opts.token);
|
|
820
|
+
fetchedVersionId = versionId;
|
|
821
|
+
timings['fetch-files'] = Date.now() - fileStart;
|
|
822
|
+
progress('fetching-files', `Files downloaded (${timings['fetch-files']}ms)`);
|
|
823
|
+
}
|
|
824
|
+
catch (err) {
|
|
825
|
+
console.log(chalk.yellow(` ⚠ Could not fetch files: ${err.message}`));
|
|
826
|
+
progress('fetching-files', `Warning: ${err.message}`);
|
|
827
|
+
}
|
|
813
828
|
}
|
|
814
829
|
// Write DATABASE_URL to .env AFTER file fetch (so it doesn't get overwritten)
|
|
815
830
|
if (databaseUrl) {
|
|
@@ -822,49 +837,51 @@ async function startAdditionalProject(projectId, opts) {
|
|
|
822
837
|
if (fetchedVersionId) {
|
|
823
838
|
localServer.markSynced(fetchedVersionId);
|
|
824
839
|
}
|
|
825
|
-
// ── Phase 2: Install deps (if needed)
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
await installer.ensureDependencies();
|
|
834
|
-
timings['install'] = Date.now() - installStart;
|
|
835
|
-
console.log(chalk.green(` ✓ Dependencies installed (${timings['install']}ms)`));
|
|
836
|
-
progress('installing-deps', `Dependencies installed (${timings['install']}ms)`);
|
|
837
|
-
}
|
|
838
|
-
catch (err) {
|
|
839
|
-
timings['install'] = Date.now() - installStart;
|
|
840
|
-
console.log(chalk.red(` ✗ Install failed: ${err.message}`));
|
|
841
|
-
console.log(chalk.yellow(` ⚠ Dev server may fail — retrying install on first request`));
|
|
842
|
-
progress('installing-deps', `Install failed: ${err.message}`);
|
|
843
|
-
}
|
|
844
|
-
}
|
|
845
|
-
else {
|
|
846
|
-
progress('installing-deps', 'Dependencies already installed (cached)');
|
|
847
|
-
}
|
|
848
|
-
// ── Phase 3: Start dev server ────────────────────────────────────
|
|
849
|
-
if (!opts.noDev) {
|
|
850
|
-
if (installer.areDependenciesInstalled()) {
|
|
851
|
-
progress('starting-dev', 'Starting dev server...');
|
|
840
|
+
// ── Phase 2: Install deps (if needed) — skip for LocalRepo ──
|
|
841
|
+
if (!isLocalRepo) {
|
|
842
|
+
const installer = new PackageInstaller({ projectDir });
|
|
843
|
+
const needsInstall = !installer.areDependenciesInstalled() && fs.existsSync(path.join(projectDir, 'package.json'));
|
|
844
|
+
if (needsInstall) {
|
|
845
|
+
const installStart = Date.now();
|
|
846
|
+
console.log(chalk.gray(` Installing dependencies...`));
|
|
847
|
+
progress('installing-deps', 'Installing npm dependencies...');
|
|
852
848
|
try {
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
timings['
|
|
856
|
-
|
|
857
|
-
progress('starting-dev', `Dev server on port ${allocated.devPort}`);
|
|
849
|
+
await installer.ensureDependencies();
|
|
850
|
+
timings['install'] = Date.now() - installStart;
|
|
851
|
+
console.log(chalk.green(` ✓ Dependencies installed (${timings['install']}ms)`));
|
|
852
|
+
progress('installing-deps', `Dependencies installed (${timings['install']}ms)`);
|
|
858
853
|
}
|
|
859
854
|
catch (err) {
|
|
860
|
-
|
|
861
|
-
|
|
855
|
+
timings['install'] = Date.now() - installStart;
|
|
856
|
+
console.log(chalk.red(` ✗ Install failed: ${err.message}`));
|
|
857
|
+
console.log(chalk.yellow(` ⚠ Dev server may fail — retrying install on first request`));
|
|
858
|
+
progress('installing-deps', `Install failed: ${err.message}`);
|
|
862
859
|
}
|
|
863
860
|
}
|
|
864
861
|
else {
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
862
|
+
progress('installing-deps', 'Dependencies already installed (cached)');
|
|
863
|
+
}
|
|
864
|
+
// ── Phase 3: Start dev server — skip for LocalRepo ──
|
|
865
|
+
if (!opts.noDev) {
|
|
866
|
+
if (installer.areDependenciesInstalled()) {
|
|
867
|
+
progress('starting-dev', 'Starting dev server...');
|
|
868
|
+
try {
|
|
869
|
+
const devStart = Date.now();
|
|
870
|
+
await localServer.getDevServer().start();
|
|
871
|
+
timings['dev-server'] = Date.now() - devStart;
|
|
872
|
+
console.log(chalk.green(` ✓ Dev server on port ${allocated.devPort} (${timings['dev-server']}ms)`));
|
|
873
|
+
progress('starting-dev', `Dev server on port ${allocated.devPort}`);
|
|
874
|
+
}
|
|
875
|
+
catch (err) {
|
|
876
|
+
console.log(chalk.yellow(` ⚠ Dev server: ${err.message}`));
|
|
877
|
+
progress('starting-dev', `Warning: ${err.message}`);
|
|
878
|
+
}
|
|
879
|
+
}
|
|
880
|
+
else {
|
|
881
|
+
console.log(chalk.yellow(` ⚠ Skipping dev server — dependencies not installed`));
|
|
882
|
+
console.log(chalk.gray(` Dev server will start when browser opens the project`));
|
|
883
|
+
progress('starting-dev', 'Skipped — dependencies not installed');
|
|
884
|
+
}
|
|
868
885
|
}
|
|
869
886
|
}
|
|
870
887
|
// Ensure tunnel is connected before declaring ready
|
|
@@ -1,4 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Status command — show agent connection status
|
|
2
|
+
* Status command — show agent connection status.
|
|
3
|
+
*
|
|
4
|
+
* Checks three sources:
|
|
5
|
+
* 1. Status server at localhost:18999 (most accurate — shows live tunnel state)
|
|
6
|
+
* 2. OS service (launchd on macOS, systemd on Linux)
|
|
7
|
+
* 3. Stored config (auth, gateway)
|
|
3
8
|
*/
|
|
4
9
|
export declare function statusCommand(): Promise<void>;
|
package/dist/commands/status.js
CHANGED
|
@@ -1,53 +1,103 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Status command — show agent connection status
|
|
2
|
+
* Status command — show agent connection status.
|
|
3
|
+
*
|
|
4
|
+
* Checks three sources:
|
|
5
|
+
* 1. Status server at localhost:18999 (most accurate — shows live tunnel state)
|
|
6
|
+
* 2. OS service (launchd on macOS, systemd on Linux)
|
|
7
|
+
* 3. Stored config (auth, gateway)
|
|
3
8
|
*/
|
|
4
9
|
import chalk from 'chalk';
|
|
10
|
+
import os from 'os';
|
|
11
|
+
import { execSync } from 'child_process';
|
|
5
12
|
import { getConfig } from '../config.js';
|
|
13
|
+
import { getPackageVersion } from '../version.js';
|
|
14
|
+
const STATUS_PORT = 18999;
|
|
15
|
+
async function fetchAgentStatus() {
|
|
16
|
+
try {
|
|
17
|
+
const controller = new AbortController();
|
|
18
|
+
const timeout = setTimeout(() => controller.abort(), 2000);
|
|
19
|
+
const res = await fetch(`http://localhost:${STATUS_PORT}/status`, { signal: controller.signal });
|
|
20
|
+
clearTimeout(timeout);
|
|
21
|
+
if (!res.ok)
|
|
22
|
+
return null;
|
|
23
|
+
return await res.json();
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
function checkOsService() {
|
|
30
|
+
const platform = os.platform();
|
|
31
|
+
if (platform === 'darwin') {
|
|
32
|
+
try {
|
|
33
|
+
const result = execSync('launchctl list | grep com.nstantpage.agent', { encoding: 'utf-8' }).trim();
|
|
34
|
+
if (result) {
|
|
35
|
+
const pid = result.split('\t')[0];
|
|
36
|
+
return { installed: true, pid: pid !== '-' ? pid : undefined };
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
catch { }
|
|
40
|
+
}
|
|
41
|
+
else if (platform === 'linux') {
|
|
42
|
+
try {
|
|
43
|
+
const result = execSync('systemctl --user is-active nstantpage-agent 2>/dev/null', { encoding: 'utf-8' }).trim();
|
|
44
|
+
return { installed: true, pid: result === 'active' ? 'active' : undefined };
|
|
45
|
+
}
|
|
46
|
+
catch { }
|
|
47
|
+
}
|
|
48
|
+
return { installed: false };
|
|
49
|
+
}
|
|
50
|
+
function formatUptime(ms) {
|
|
51
|
+
const s = Math.floor(ms / 1000);
|
|
52
|
+
if (s < 60)
|
|
53
|
+
return `${s}s`;
|
|
54
|
+
const m = Math.floor(s / 60);
|
|
55
|
+
if (m < 60)
|
|
56
|
+
return `${m}m ${s % 60}s`;
|
|
57
|
+
const h = Math.floor(m / 60);
|
|
58
|
+
return `${h}h ${m % 60}m`;
|
|
59
|
+
}
|
|
6
60
|
export async function statusCommand() {
|
|
7
61
|
const conf = getConfig();
|
|
8
62
|
const token = conf.get('token');
|
|
9
|
-
const
|
|
10
|
-
const
|
|
11
|
-
const
|
|
12
|
-
|
|
13
|
-
const apiPort = conf.get('apiPort') || 18924;
|
|
14
|
-
console.log(chalk.blue('\n nstantpage agent v0.2.0\n'));
|
|
63
|
+
const email = conf.get('email');
|
|
64
|
+
const gateway = conf.get('gatewayUrl') || 'wss://webprev.live';
|
|
65
|
+
const isLocal = /^wss?:\/\/(localhost|127\.0\.0\.1)/.test(gateway);
|
|
66
|
+
console.log(chalk.blue(`\n nstantpage agent v${getPackageVersion()}\n`));
|
|
15
67
|
// Auth
|
|
16
68
|
if (token) {
|
|
17
|
-
console.log(chalk.green(
|
|
69
|
+
console.log(chalk.green(` ✓ Authenticated${email ? ` (${email})` : ''}`));
|
|
18
70
|
}
|
|
19
71
|
else {
|
|
20
72
|
console.log(chalk.red(' ✗ Not authenticated (run "nstantpage login")'));
|
|
21
73
|
}
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
console.log(chalk.gray(
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
console.log(chalk.gray(`
|
|
36
|
-
console.log(chalk.gray(` API server: http://localhost:${apiPort}`));
|
|
74
|
+
console.log(chalk.gray(` Server: ${isLocal ? 'localhost (dev)' : 'nstantpage.com'}`));
|
|
75
|
+
// Try live status from the status server
|
|
76
|
+
const live = await fetchAgentStatus();
|
|
77
|
+
const service = checkOsService();
|
|
78
|
+
if (live) {
|
|
79
|
+
console.log(chalk.green(` ✓ Agent running`));
|
|
80
|
+
console.log(chalk.gray(` Tunnel: ${live.tunnelStatus}`));
|
|
81
|
+
console.log(chalk.gray(` Uptime: ${formatUptime(live.uptime)}`));
|
|
82
|
+
console.log(chalk.gray(` Gateway: ${live.gatewayUrl}`));
|
|
83
|
+
if (live.activeProjects.length > 0) {
|
|
84
|
+
console.log(chalk.gray(` Projects: ${live.activeProjects.map(p => p.projectId).join(', ')}`));
|
|
85
|
+
}
|
|
86
|
+
else {
|
|
87
|
+
console.log(chalk.gray(` Projects: none (standby mode)`));
|
|
37
88
|
}
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
89
|
+
}
|
|
90
|
+
else if (service.installed) {
|
|
91
|
+
console.log(chalk.yellow(` ⚠ Service installed but agent not responding`));
|
|
92
|
+
if (service.pid) {
|
|
93
|
+
console.log(chalk.gray(` PID: ${service.pid}`));
|
|
41
94
|
}
|
|
95
|
+
console.log(chalk.gray(' Try: nstantpage logs'));
|
|
42
96
|
}
|
|
43
97
|
else {
|
|
44
98
|
console.log(chalk.gray(' Agent not running'));
|
|
99
|
+
console.log(chalk.gray(' Run "nstantpage run" to start'));
|
|
45
100
|
}
|
|
46
|
-
// Last connected
|
|
47
|
-
if (lastConnected) {
|
|
48
|
-
console.log(chalk.gray(` Last connected: ${lastConnected}`));
|
|
49
|
-
}
|
|
50
|
-
console.log(chalk.gray(`\n Mode: Agent (replaces cloud containers)`));
|
|
51
101
|
console.log('');
|
|
52
102
|
}
|
|
53
103
|
//# sourceMappingURL=status.js.map
|
package/dist/tunnel.d.ts
CHANGED
|
@@ -27,6 +27,7 @@ interface TunnelClientOptions {
|
|
|
27
27
|
/** Callback when gateway requests starting a new project on this device */
|
|
28
28
|
onStartProject?: (projectId: string, options?: {
|
|
29
29
|
clean?: boolean;
|
|
30
|
+
localFolderPath?: string;
|
|
30
31
|
}) => Promise<void>;
|
|
31
32
|
/** Callback for setup progress — lets the standby tunnel relay phases to gateway */
|
|
32
33
|
onSetupProgress?: (projectId: string, phase: string, message: string) => void;
|
package/dist/tunnel.js
CHANGED
|
@@ -403,8 +403,8 @@ export class TunnelClient {
|
|
|
403
403
|
* Handle start-project: gateway wants this agent to start serving a new project.
|
|
404
404
|
* Called when user clicks "Connect" in the web editor's Cloud panel.
|
|
405
405
|
*/
|
|
406
|
-
async handleStartProject(projectId, clean) {
|
|
407
|
-
console.log(` [Tunnel] Received start-project command for ${projectId}${clean ? ' (clean)' : ''}`);
|
|
406
|
+
async handleStartProject(projectId, clean, localFolderPath) {
|
|
407
|
+
console.log(` [Tunnel] Received start-project command for ${projectId}${clean ? ' (clean)' : ''}${localFolderPath ? ` (local: ${localFolderPath})` : ''}`);
|
|
408
408
|
if (!projectId) {
|
|
409
409
|
this.send({ type: 'start-project-result', success: false, error: 'Missing projectId' });
|
|
410
410
|
return;
|
|
@@ -413,7 +413,7 @@ export class TunnelClient {
|
|
|
413
413
|
try {
|
|
414
414
|
// Send initial progress
|
|
415
415
|
this.sendSetupProgress(projectId, 'starting', 'Starting project setup...');
|
|
416
|
-
await this.options.onStartProject(projectId, clean ? { clean: true } :
|
|
416
|
+
await this.options.onStartProject(projectId, { ...(clean ? { clean: true } : {}), ...(localFolderPath ? { localFolderPath } : {}) });
|
|
417
417
|
this.sendSetupProgress(projectId, 'ready', 'Project is live!');
|
|
418
418
|
this.send({ type: 'start-project-result', projectId, success: true });
|
|
419
419
|
}
|
|
@@ -474,7 +474,7 @@ export class TunnelClient {
|
|
|
474
474
|
this.handleWsClose(msg.wsId);
|
|
475
475
|
break;
|
|
476
476
|
case 'start-project':
|
|
477
|
-
this.handleStartProject(msg.projectId, msg.clean);
|
|
477
|
+
this.handleStartProject(msg.projectId, msg.clean, msg.localFolderPath);
|
|
478
478
|
break;
|
|
479
479
|
case 'force-disconnect':
|
|
480
480
|
console.log(' [Tunnel] Received force-disconnect — will not reconnect');
|
package/package.json
CHANGED