simceptor-cli 1.0.0
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/bin/simceptor.js +166 -0
- package/package.json +17 -0
package/bin/simceptor.js
ADDED
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const { program } = require('commander');
|
|
4
|
+
const WebSocket = require('ws');
|
|
5
|
+
const axios = require('axios');
|
|
6
|
+
|
|
7
|
+
program
|
|
8
|
+
.version('1.0.0')
|
|
9
|
+
.description('Simceptor Webhook Local Relayer CLI Client')
|
|
10
|
+
.command('listen')
|
|
11
|
+
.requiredOption('-t, --token <token>', 'Your Simceptor Personal Access Token (PAT)')
|
|
12
|
+
.option('-o, --to <url>', 'Local application target URL', 'http://localhost:3000')
|
|
13
|
+
.option('-s, --server <url>', 'Simceptor WebSocket server host URL', 'ws://localhost:8080')
|
|
14
|
+
.action((options) => {
|
|
15
|
+
const { token, to, server } = options;
|
|
16
|
+
|
|
17
|
+
// Normalize target URL (ensure it starts with http:// or https://)
|
|
18
|
+
let toUrl = to.trim();
|
|
19
|
+
if (!toUrl.startsWith('http://') && !toUrl.startsWith('https://')) {
|
|
20
|
+
toUrl = 'http://' + toUrl;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Normalize server URL (ensure it starts with ws:// or wss://)
|
|
24
|
+
let serverUrl = server.trim();
|
|
25
|
+
if (!serverUrl.startsWith('ws://') && !serverUrl.startsWith('wss://')) {
|
|
26
|
+
if (serverUrl.startsWith('http://')) {
|
|
27
|
+
serverUrl = serverUrl.replace('http://', 'ws://');
|
|
28
|
+
} else if (serverUrl.startsWith('https://')) {
|
|
29
|
+
serverUrl = serverUrl.replace('https://', 'wss://');
|
|
30
|
+
} else {
|
|
31
|
+
serverUrl = 'ws://' + serverUrl;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Append relay path
|
|
36
|
+
if (!serverUrl.includes('/api/ws/webhook-relay')) {
|
|
37
|
+
serverUrl = serverUrl.replace(/\/+$/, '') + '/api/ws/webhook-relay';
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const wsUrl = `${serverUrl}?token=${token}`;
|
|
41
|
+
console.log(`\n⚡ Simceptor Local Relayer starting...`);
|
|
42
|
+
console.log(` - Server: ${serverUrl}`);
|
|
43
|
+
console.log(` - Forward To: ${toUrl}\n`);
|
|
44
|
+
|
|
45
|
+
// Security check: Warn if using unencrypted ws:// on non-localhost remote servers
|
|
46
|
+
if (serverUrl.startsWith('ws://') && !serverUrl.includes('localhost') && !serverUrl.includes('127.0.0.1') && !serverUrl.includes('0.0.0.0')) {
|
|
47
|
+
console.warn(`⚠️ SECURITY WARNING: You are connecting using unencrypted WebSocket (ws://) to a remote host.`);
|
|
48
|
+
console.warn(` Your Personal Access Token will be transmitted in plain text. Please use secure WebSocket (wss://) in production.`);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
let ws;
|
|
52
|
+
let reconnectTimeout;
|
|
53
|
+
|
|
54
|
+
function connect() {
|
|
55
|
+
console.log(`[${new Date().toLocaleTimeString()}] Connecting to Simceptor server...`);
|
|
56
|
+
|
|
57
|
+
ws = new WebSocket(wsUrl);
|
|
58
|
+
|
|
59
|
+
ws.on('open', () => {
|
|
60
|
+
console.log(`\n🟢 Connected to Simceptor! Listening for local webhooks...`);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
ws.on('message', async (data) => {
|
|
64
|
+
try {
|
|
65
|
+
const message = JSON.parse(data.toString());
|
|
66
|
+
|
|
67
|
+
if (message.type === 'WEBHOOK') {
|
|
68
|
+
const { deliveryLogId, targetUrl, method, payload, headers, queryParams } = message;
|
|
69
|
+
|
|
70
|
+
console.log(`\n[${new Date().toLocaleTimeString()}] 📥 Received Webhook for path: ${targetUrl.replace('simceptor://local', '')}`);
|
|
71
|
+
console.log(` - Log ID: ${deliveryLogId}`);
|
|
72
|
+
console.log(` - Method: ${method}`);
|
|
73
|
+
|
|
74
|
+
// Construct local target URL
|
|
75
|
+
let finalLocalUrl;
|
|
76
|
+
try {
|
|
77
|
+
const urlObj = new URL(targetUrl.replace('simceptor://local', toUrl));
|
|
78
|
+
finalLocalUrl = urlObj.toString();
|
|
79
|
+
} catch (e) {
|
|
80
|
+
finalLocalUrl = toUrl + targetUrl.replace('simceptor://local', '');
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
console.log(` - Relaying to: ${finalLocalUrl}`);
|
|
84
|
+
|
|
85
|
+
// Parse headers
|
|
86
|
+
let reqHeaders = {
|
|
87
|
+
'Content-Type': 'application/json',
|
|
88
|
+
'X-Simceptor-Relayed': 'true'
|
|
89
|
+
};
|
|
90
|
+
if (headers) {
|
|
91
|
+
try {
|
|
92
|
+
const parsedHeaders = JSON.parse(headers);
|
|
93
|
+
reqHeaders = { ...reqHeaders, ...parsedHeaders };
|
|
94
|
+
} catch (e) {
|
|
95
|
+
// Ignore parsing errors
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const startTime = Date.now();
|
|
100
|
+
let responseStatus = null;
|
|
101
|
+
let responseBody = null;
|
|
102
|
+
let errorMessage = null;
|
|
103
|
+
|
|
104
|
+
try {
|
|
105
|
+
const response = await axios({
|
|
106
|
+
method: method,
|
|
107
|
+
url: finalLocalUrl,
|
|
108
|
+
data: payload,
|
|
109
|
+
headers: reqHeaders,
|
|
110
|
+
timeout: 10000 // 10s timeout
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
responseStatus = response.status;
|
|
114
|
+
responseBody = typeof response.data === 'object' ? JSON.stringify(response.data) : String(response.data);
|
|
115
|
+
console.log(`🟢 Local server responded: ${response.status} OK (${Date.now() - startTime}ms)`);
|
|
116
|
+
} catch (err) {
|
|
117
|
+
errorMessage = err.message;
|
|
118
|
+
if (err.response) {
|
|
119
|
+
responseStatus = err.response.status;
|
|
120
|
+
responseBody = typeof err.response.data === 'object' ? JSON.stringify(err.response.data) : String(err.response.data);
|
|
121
|
+
console.log(`🔴 Local server error response: ${err.response.status} (${Date.now() - startTime}ms)`);
|
|
122
|
+
} else {
|
|
123
|
+
console.log(`🔴 Failed to connect to local server: ${err.message}`);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Send ACK back
|
|
128
|
+
const ackMessage = {
|
|
129
|
+
type: 'ACK',
|
|
130
|
+
deliveryLogId: deliveryLogId,
|
|
131
|
+
statusCode: responseStatus,
|
|
132
|
+
responseBody: responseBody ? responseBody.substring(0, 5000) : null, // Truncate very long responses
|
|
133
|
+
errorMessage: errorMessage,
|
|
134
|
+
durationMs: Date.now() - startTime
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
if (ws.readyState === WebSocket.OPEN) {
|
|
138
|
+
ws.send(JSON.stringify(ackMessage));
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
} catch (e) {
|
|
142
|
+
console.error(`🔴 Error processing message:`, e);
|
|
143
|
+
}
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
ws.on('close', (code, reason) => {
|
|
147
|
+
console.log(`\n🔴 Connection closed (Code: ${code}). Reconnecting in 5 seconds...`);
|
|
148
|
+
reconnect();
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
ws.on('error', (err) => {
|
|
152
|
+
console.error(`\n⚠️ Connection error: ${err.message}`);
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function reconnect() {
|
|
157
|
+
clearTimeout(reconnectTimeout);
|
|
158
|
+
reconnectTimeout = setTimeout(() => {
|
|
159
|
+
connect();
|
|
160
|
+
}, 5000);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
connect();
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
program.parse(process.argv);
|
package/package.json
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "simceptor-cli",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Simceptor Webhook Local Relayer CLI Client",
|
|
5
|
+
"main": "bin/simceptor.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"simceptor": "./bin/simceptor.js"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"start": "node bin/simceptor.js"
|
|
11
|
+
},
|
|
12
|
+
"dependencies": {
|
|
13
|
+
"axios": "^1.6.0",
|
|
14
|
+
"commander": "^11.1.0",
|
|
15
|
+
"ws": "^8.14.0"
|
|
16
|
+
}
|
|
17
|
+
}
|