@modelriver/cli 1.1.0 → 1.2.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/package.json
CHANGED
package/src/commands/forward.js
CHANGED
package/src/commands/listen.js
CHANGED
|
@@ -4,9 +4,10 @@ const CLIWebSocketClient = require('../lib/cli-websocket-client');
|
|
|
4
4
|
const WebhookVerifier = require('../lib/webhook-verifier');
|
|
5
5
|
const Logger = require('../utils/logger');
|
|
6
6
|
const Formatter = require('../utils/formatter');
|
|
7
|
+
const { parseForwardUrl } = require('../utils/url-helpers');
|
|
7
8
|
|
|
8
9
|
async function listenCommand(options) {
|
|
9
|
-
let { apiKey, apiUrl, print, port, verbose, forward } = options;
|
|
10
|
+
let { apiKey, apiUrl, print, port, verbose, forward, forwardUrl } = options;
|
|
10
11
|
const forwardOnly = forward === true;
|
|
11
12
|
|
|
12
13
|
const spinner = Logger.spinner('Setting up webhook forwarding...');
|
|
@@ -206,16 +207,25 @@ async function listenCommand(options) {
|
|
|
206
207
|
|
|
207
208
|
// Forward to local server
|
|
208
209
|
const http = require('http');
|
|
210
|
+
const https = require('https');
|
|
209
211
|
const postData = JSON.stringify({
|
|
210
212
|
channel_id,
|
|
211
213
|
timestamp,
|
|
212
214
|
data
|
|
213
215
|
});
|
|
214
216
|
|
|
215
|
-
|
|
217
|
+
// Parse the forward URL or use localhost defaults
|
|
218
|
+
const parsedUrl = parseForwardUrl(forwardUrl) || {
|
|
216
219
|
hostname: 'localhost',
|
|
217
220
|
port: localPort,
|
|
218
|
-
path: '/webhook',
|
|
221
|
+
path: '/webhook/modelriver',
|
|
222
|
+
protocol: 'http'
|
|
223
|
+
};
|
|
224
|
+
|
|
225
|
+
const options = {
|
|
226
|
+
hostname: parsedUrl.hostname,
|
|
227
|
+
port: parsedUrl.port,
|
|
228
|
+
path: parsedUrl.path,
|
|
219
229
|
method: 'POST',
|
|
220
230
|
headers: {
|
|
221
231
|
'Content-Type': 'application/json',
|
|
@@ -226,15 +236,19 @@ async function listenCommand(options) {
|
|
|
226
236
|
}
|
|
227
237
|
};
|
|
228
238
|
|
|
229
|
-
|
|
239
|
+
// Use https or http based on protocol
|
|
240
|
+
const requestModule = parsedUrl.protocol === 'https' ? https : http;
|
|
241
|
+
const targetUrl = `${parsedUrl.protocol}://${parsedUrl.hostname}:${parsedUrl.port}${parsedUrl.path}`;
|
|
242
|
+
|
|
243
|
+
const req = requestModule.request(options, (res) => {
|
|
230
244
|
// Webhook forwarded successfully
|
|
231
245
|
if (print) {
|
|
232
|
-
Logger.info(` → Forwarded to
|
|
246
|
+
Logger.info(` → Forwarded to ${targetUrl} (status: ${res.statusCode})`);
|
|
233
247
|
}
|
|
234
248
|
});
|
|
235
249
|
|
|
236
250
|
req.on('error', (error) => {
|
|
237
|
-
Logger.error(`Failed to forward webhook to
|
|
251
|
+
Logger.error(`Failed to forward webhook to ${targetUrl}: ${error.message}`);
|
|
238
252
|
});
|
|
239
253
|
|
|
240
254
|
req.write(postData);
|
package/src/utils/url-helpers.js
CHANGED
|
@@ -91,8 +91,29 @@ function isValidApiKeyFormat(apiKey) {
|
|
|
91
91
|
return apiKey.startsWith('mr_live_') || apiKey.startsWith('mr_test_');
|
|
92
92
|
}
|
|
93
93
|
|
|
94
|
+
/**
|
|
95
|
+
* Parse a forward URL into its components
|
|
96
|
+
* @param {string} url - The URL to parse
|
|
97
|
+
* @returns {object|null} - Object with hostname, port, path, protocol or null if invalid
|
|
98
|
+
*/
|
|
99
|
+
function parseForwardUrl(url) {
|
|
100
|
+
if (!url) return null;
|
|
101
|
+
try {
|
|
102
|
+
const urlObj = new URL(url);
|
|
103
|
+
return {
|
|
104
|
+
hostname: urlObj.hostname,
|
|
105
|
+
port: parseInt(urlObj.port, 10) || (urlObj.protocol === 'https:' ? 443 : 80),
|
|
106
|
+
path: urlObj.pathname || '/webhook/modelriver',
|
|
107
|
+
protocol: urlObj.protocol.replace(':', '')
|
|
108
|
+
};
|
|
109
|
+
} catch (error) {
|
|
110
|
+
return null;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
94
114
|
module.exports = {
|
|
95
115
|
normalizeWebhookUrl,
|
|
96
116
|
extractPort,
|
|
97
|
-
isValidApiKeyFormat
|
|
117
|
+
isValidApiKeyFormat,
|
|
118
|
+
parseForwardUrl
|
|
98
119
|
};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// Tests for URL helper utilities
|
|
2
|
-
const { normalizeWebhookUrl, extractPort, isValidApiKeyFormat } = require('./url-helpers');
|
|
2
|
+
const { normalizeWebhookUrl, extractPort, isValidApiKeyFormat, parseForwardUrl } = require('./url-helpers');
|
|
3
3
|
|
|
4
4
|
describe('URL Helpers', () => {
|
|
5
5
|
describe('normalizeWebhookUrl', () => {
|
|
@@ -81,4 +81,54 @@ describe('URL Helpers', () => {
|
|
|
81
81
|
expect(isValidApiKeyFormat(undefined)).toBe(false);
|
|
82
82
|
});
|
|
83
83
|
});
|
|
84
|
+
|
|
85
|
+
describe('parseForwardUrl', () => {
|
|
86
|
+
it('should parse localhost URL with port', () => {
|
|
87
|
+
const result = parseForwardUrl('http://localhost:4000/webhook/modelriver');
|
|
88
|
+
expect(result).toEqual({
|
|
89
|
+
hostname: 'localhost',
|
|
90
|
+
port: 4000,
|
|
91
|
+
path: '/webhook/modelriver',
|
|
92
|
+
protocol: 'http'
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it('should parse external URL with port', () => {
|
|
97
|
+
const result = parseForwardUrl('http://myserver.com:3001/webhook/modelriver');
|
|
98
|
+
expect(result).toEqual({
|
|
99
|
+
hostname: 'myserver.com',
|
|
100
|
+
port: 3001,
|
|
101
|
+
path: '/webhook/modelriver',
|
|
102
|
+
protocol: 'http'
|
|
103
|
+
});
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it('should default to port 80 for http without explicit port', () => {
|
|
107
|
+
const result = parseForwardUrl('http://example.com/webhook/modelriver');
|
|
108
|
+
expect(result.port).toBe(80);
|
|
109
|
+
expect(result.hostname).toBe('example.com');
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
it('should default to port 443 for https without explicit port', () => {
|
|
113
|
+
const result = parseForwardUrl('https://example.com/webhook/modelriver');
|
|
114
|
+
expect(result.port).toBe(443);
|
|
115
|
+
expect(result.protocol).toBe('https');
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
it('should handle URLs with different paths', () => {
|
|
119
|
+
const result = parseForwardUrl('http://localhost:5000/custom/path');
|
|
120
|
+
expect(result.path).toBe('/custom/path');
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it('should return null for empty or falsy input', () => {
|
|
124
|
+
expect(parseForwardUrl('')).toBeNull();
|
|
125
|
+
expect(parseForwardUrl(null)).toBeNull();
|
|
126
|
+
expect(parseForwardUrl(undefined)).toBeNull();
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it('should return null for invalid URLs', () => {
|
|
130
|
+
expect(parseForwardUrl('not-a-url')).toBeNull();
|
|
131
|
+
expect(parseForwardUrl('just-text')).toBeNull();
|
|
132
|
+
});
|
|
133
|
+
});
|
|
84
134
|
});
|