@synergenius/flow-weaver 0.10.10 → 0.10.11
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/cli/commands/tunnel.d.ts +20 -0
- package/dist/cli/commands/tunnel.js +156 -0
- package/dist/cli/flow-weaver.mjs +3804 -41
- package/dist/cli/index.js +17 -0
- package/package.json +3 -1
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* tunnel command — Relay Studio RPC calls from cloud to a local dev server.
|
|
3
|
+
*
|
|
4
|
+
* 1. Connects to the local server via Socket.IO (same as listen command).
|
|
5
|
+
* 2. Opens a WebSocket to the cloud server's /api/tunnel endpoint.
|
|
6
|
+
* 3. Relays tunnel:request messages from cloud → local Socket.IO → cloud.
|
|
7
|
+
*/
|
|
8
|
+
import { io as socketIO } from 'socket.io-client';
|
|
9
|
+
import WebSocket from 'ws';
|
|
10
|
+
export interface TunnelOptions {
|
|
11
|
+
key: string;
|
|
12
|
+
cloud?: string;
|
|
13
|
+
server?: string;
|
|
14
|
+
/** Override WebSocket factory (for testing) */
|
|
15
|
+
createWs?: (url: string) => WebSocket;
|
|
16
|
+
/** Override socket.io-client factory (for testing) */
|
|
17
|
+
ioFactory?: typeof socketIO;
|
|
18
|
+
}
|
|
19
|
+
export declare function tunnelCommand(options: TunnelOptions): Promise<void>;
|
|
20
|
+
//# sourceMappingURL=tunnel.d.ts.map
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* tunnel command — Relay Studio RPC calls from cloud to a local dev server.
|
|
3
|
+
*
|
|
4
|
+
* 1. Connects to the local server via Socket.IO (same as listen command).
|
|
5
|
+
* 2. Opens a WebSocket to the cloud server's /api/tunnel endpoint.
|
|
6
|
+
* 3. Relays tunnel:request messages from cloud → local Socket.IO → cloud.
|
|
7
|
+
*/
|
|
8
|
+
import { io as socketIO } from 'socket.io-client';
|
|
9
|
+
import WebSocket from 'ws';
|
|
10
|
+
import { logger } from '../utils/logger.js';
|
|
11
|
+
import { DEFAULT_SERVER_URL } from '../../defaults.js';
|
|
12
|
+
export async function tunnelCommand(options) {
|
|
13
|
+
const cloudUrl = options.cloud || 'https://flowweaver.dev';
|
|
14
|
+
const localUrl = options.server || DEFAULT_SERVER_URL;
|
|
15
|
+
const createWs = options.createWs ?? ((url) => new WebSocket(url));
|
|
16
|
+
const ioFactory = options.ioFactory ?? socketIO;
|
|
17
|
+
logger.section('Flow Weaver Tunnel');
|
|
18
|
+
logger.info(`Cloud: ${cloudUrl}`);
|
|
19
|
+
logger.info(`Local: ${localUrl}`);
|
|
20
|
+
logger.newline();
|
|
21
|
+
// -----------------------------------------------------------------------
|
|
22
|
+
// 1. Connect to local server via Socket.IO
|
|
23
|
+
// -----------------------------------------------------------------------
|
|
24
|
+
logger.info('Connecting to local server...');
|
|
25
|
+
const localSocket = ioFactory(`${localUrl}`, {
|
|
26
|
+
query: { clientType: 'tunnel' },
|
|
27
|
+
transports: ['websocket', 'polling'],
|
|
28
|
+
reconnection: true,
|
|
29
|
+
reconnectionDelay: 1000,
|
|
30
|
+
reconnectionAttempts: Infinity,
|
|
31
|
+
});
|
|
32
|
+
await new Promise((resolve, reject) => {
|
|
33
|
+
const timeout = setTimeout(() => {
|
|
34
|
+
localSocket.disconnect();
|
|
35
|
+
reject(new Error(`Local server connection timeout (10s). Is it running on ${localUrl}?`));
|
|
36
|
+
}, 10_000);
|
|
37
|
+
localSocket.on('connect', () => {
|
|
38
|
+
clearTimeout(timeout);
|
|
39
|
+
logger.success(`Connected to local server (${localUrl})`);
|
|
40
|
+
resolve();
|
|
41
|
+
});
|
|
42
|
+
localSocket.on('connect_error', (err) => {
|
|
43
|
+
clearTimeout(timeout);
|
|
44
|
+
localSocket.disconnect();
|
|
45
|
+
reject(new Error(`Cannot connect to local server: ${err.message}`));
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
// -----------------------------------------------------------------------
|
|
49
|
+
// 2. Connect to cloud server via WebSocket
|
|
50
|
+
// -----------------------------------------------------------------------
|
|
51
|
+
logger.info('Connecting to cloud server...');
|
|
52
|
+
const wsProtocol = cloudUrl.startsWith('https') ? 'wss' : 'ws';
|
|
53
|
+
const wsHost = cloudUrl.replace(/^https?:\/\//, '');
|
|
54
|
+
const wsUrl = `${wsProtocol}://${wsHost}/api/tunnel?token=${encodeURIComponent(options.key)}`;
|
|
55
|
+
const cloudWs = createWs(wsUrl);
|
|
56
|
+
await new Promise((resolve, reject) => {
|
|
57
|
+
const timeout = setTimeout(() => {
|
|
58
|
+
cloudWs.close();
|
|
59
|
+
reject(new Error('Cloud server connection timeout (10s)'));
|
|
60
|
+
}, 10_000);
|
|
61
|
+
cloudWs.on('open', () => {
|
|
62
|
+
clearTimeout(timeout);
|
|
63
|
+
});
|
|
64
|
+
cloudWs.on('message', (raw) => {
|
|
65
|
+
try {
|
|
66
|
+
const msg = JSON.parse(raw.toString());
|
|
67
|
+
if (msg.type === 'tunnel:hello') {
|
|
68
|
+
logger.success('Connected to cloud server');
|
|
69
|
+
resolve();
|
|
70
|
+
}
|
|
71
|
+
else if (msg.type === 'error') {
|
|
72
|
+
clearTimeout(timeout);
|
|
73
|
+
cloudWs.close();
|
|
74
|
+
reject(new Error(`Cloud server rejected connection: ${msg.message}`));
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
catch {
|
|
78
|
+
// Ignore parse errors during handshake
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
cloudWs.on('error', (err) => {
|
|
82
|
+
clearTimeout(timeout);
|
|
83
|
+
reject(new Error(`Cannot connect to cloud server: ${err.message}`));
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
logger.newline();
|
|
87
|
+
logger.success('Tunnel active — local development mode enabled');
|
|
88
|
+
logger.info('Press Ctrl+C to disconnect');
|
|
89
|
+
logger.newline();
|
|
90
|
+
let requestCount = 0;
|
|
91
|
+
// -----------------------------------------------------------------------
|
|
92
|
+
// 3. Relay: cloud → local → cloud
|
|
93
|
+
// -----------------------------------------------------------------------
|
|
94
|
+
cloudWs.on('message', (raw) => {
|
|
95
|
+
let msg;
|
|
96
|
+
try {
|
|
97
|
+
msg = JSON.parse(raw.toString());
|
|
98
|
+
}
|
|
99
|
+
catch {
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
if (msg.type === 'ping') {
|
|
103
|
+
cloudWs.send(JSON.stringify({ type: 'pong' }));
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
if (msg.type === 'tunnel:request') {
|
|
107
|
+
const req = msg;
|
|
108
|
+
requestCount++;
|
|
109
|
+
logger.debug(`[${requestCount}] → ${req.method}`);
|
|
110
|
+
localSocket.emit('method', { id: req.id, method: req.method, params: req.params }, (response) => {
|
|
111
|
+
cloudWs.send(JSON.stringify({
|
|
112
|
+
type: 'tunnel:response',
|
|
113
|
+
requestId: req.requestId,
|
|
114
|
+
id: response.id,
|
|
115
|
+
success: response.success,
|
|
116
|
+
result: response.result,
|
|
117
|
+
error: response.error,
|
|
118
|
+
}));
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
// -----------------------------------------------------------------------
|
|
123
|
+
// 4. Handle disconnections
|
|
124
|
+
// -----------------------------------------------------------------------
|
|
125
|
+
localSocket.on('disconnect', (reason) => {
|
|
126
|
+
logger.warn(`Local server disconnected: ${reason}`);
|
|
127
|
+
if (reason === 'io server disconnect') {
|
|
128
|
+
logger.info('Attempting to reconnect...');
|
|
129
|
+
}
|
|
130
|
+
});
|
|
131
|
+
localSocket.on('connect', () => {
|
|
132
|
+
logger.success('Reconnected to local server');
|
|
133
|
+
});
|
|
134
|
+
cloudWs.on('close', (code, reason) => {
|
|
135
|
+
logger.warn(`Cloud server disconnected: ${code} ${reason.toString()}`);
|
|
136
|
+
logger.info('Shutting down tunnel...');
|
|
137
|
+
localSocket.disconnect();
|
|
138
|
+
process.exit(code === 4001 ? 1 : 0);
|
|
139
|
+
});
|
|
140
|
+
cloudWs.on('error', (err) => {
|
|
141
|
+
logger.error(`Cloud WebSocket error: ${err.message}`);
|
|
142
|
+
});
|
|
143
|
+
// -----------------------------------------------------------------------
|
|
144
|
+
// 5. Graceful shutdown
|
|
145
|
+
// -----------------------------------------------------------------------
|
|
146
|
+
process.on('SIGINT', () => {
|
|
147
|
+
logger.newline();
|
|
148
|
+
logger.info(`Shutting down tunnel (${requestCount} requests relayed)...`);
|
|
149
|
+
cloudWs.close();
|
|
150
|
+
localSocket.disconnect();
|
|
151
|
+
process.exit(0);
|
|
152
|
+
});
|
|
153
|
+
// Keep alive until SIGINT or cloud disconnect
|
|
154
|
+
await new Promise(() => { });
|
|
155
|
+
}
|
|
156
|
+
//# sourceMappingURL=tunnel.js.map
|