hooksdash 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/hooksdash.js +3 -0
- package/dist/commands/forward.d.ts +7 -0
- package/dist/commands/forward.js +85 -0
- package/dist/commands/login.d.ts +6 -0
- package/dist/commands/login.js +103 -0
- package/dist/commands/logout.d.ts +1 -0
- package/dist/commands/logout.js +16 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +53 -0
- package/dist/lib/config.d.ts +13 -0
- package/dist/lib/config.js +38 -0
- package/dist/lib/websocket.d.ts +18 -0
- package/dist/lib/websocket.js +119 -0
- package/package.json +69 -0
package/bin/hooksdash.js
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.forward = forward;
|
|
7
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
8
|
+
const ora_1 = __importDefault(require("ora"));
|
|
9
|
+
const axios_1 = __importDefault(require("axios"));
|
|
10
|
+
const config_1 = require("../lib/config");
|
|
11
|
+
const websocket_1 = require("../lib/websocket");
|
|
12
|
+
async function forward(options) {
|
|
13
|
+
if (!(0, config_1.isLoggedIn)()) {
|
|
14
|
+
console.error(chalk_1.default.red('Error: You must be logged in to forward webhooks'));
|
|
15
|
+
console.log(chalk_1.default.gray('Run: hooksdash login --api-key <key>'));
|
|
16
|
+
process.exit(1);
|
|
17
|
+
}
|
|
18
|
+
const apiKey = (0, config_1.getApiKey)();
|
|
19
|
+
const apiUrl = (0, config_1.getApiUrl)();
|
|
20
|
+
// If no endpoint specified, we need to get/create one
|
|
21
|
+
let endpointSlug = options.endpoint;
|
|
22
|
+
if (!endpointSlug) {
|
|
23
|
+
const spinner = (0, ora_1.default)('Fetching your endpoints...').start();
|
|
24
|
+
try {
|
|
25
|
+
const response = await axios_1.default.get(`${apiUrl}/endpoints`, {
|
|
26
|
+
headers: { 'X-API-Key': apiKey },
|
|
27
|
+
});
|
|
28
|
+
const endpoints = response.data.data || [];
|
|
29
|
+
if (endpoints.length === 0) {
|
|
30
|
+
spinner.text = 'Creating a new endpoint...';
|
|
31
|
+
const createResponse = await axios_1.default.post(`${apiUrl}/endpoints`, { name: 'CLI Endpoint' }, {
|
|
32
|
+
headers: {
|
|
33
|
+
'Content-Type': 'application/json',
|
|
34
|
+
'X-API-Key': apiKey,
|
|
35
|
+
},
|
|
36
|
+
});
|
|
37
|
+
endpointSlug = createResponse.data.data.slug;
|
|
38
|
+
spinner.succeed(`Created endpoint: ${endpointSlug}`);
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
endpointSlug = endpoints[0].slug;
|
|
42
|
+
spinner.succeed(`Using endpoint: ${endpointSlug}`);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
catch (error) {
|
|
46
|
+
spinner.fail('Failed to connect to API');
|
|
47
|
+
if (error instanceof Error) {
|
|
48
|
+
console.error(chalk_1.default.gray(error.message));
|
|
49
|
+
}
|
|
50
|
+
process.exit(1);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
if (!endpointSlug) {
|
|
54
|
+
console.error(chalk_1.default.red('Error: Could not determine endpoint'));
|
|
55
|
+
process.exit(1);
|
|
56
|
+
}
|
|
57
|
+
console.log(chalk_1.default.cyan(`\nForwarding webhooks to ${options.host}:${options.port}`));
|
|
58
|
+
const client = new websocket_1.TunnelClient({
|
|
59
|
+
apiKey,
|
|
60
|
+
apiUrl,
|
|
61
|
+
endpointSlug,
|
|
62
|
+
localPort: options.port,
|
|
63
|
+
localHost: options.host,
|
|
64
|
+
});
|
|
65
|
+
// Handle graceful shutdown
|
|
66
|
+
process.on('SIGINT', () => {
|
|
67
|
+
console.log(chalk_1.default.yellow('\nDisconnecting...'));
|
|
68
|
+
client.disconnect();
|
|
69
|
+
process.exit(0);
|
|
70
|
+
});
|
|
71
|
+
process.on('SIGTERM', () => {
|
|
72
|
+
client.disconnect();
|
|
73
|
+
process.exit(0);
|
|
74
|
+
});
|
|
75
|
+
try {
|
|
76
|
+
await client.connect();
|
|
77
|
+
}
|
|
78
|
+
catch (error) {
|
|
79
|
+
console.error(chalk_1.default.red('Failed to establish tunnel'));
|
|
80
|
+
if (error instanceof Error) {
|
|
81
|
+
console.error(chalk_1.default.gray(error.message));
|
|
82
|
+
}
|
|
83
|
+
process.exit(1);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.login = login;
|
|
7
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
8
|
+
const ora_1 = __importDefault(require("ora"));
|
|
9
|
+
const open_1 = __importDefault(require("open"));
|
|
10
|
+
const axios_1 = __importDefault(require("axios"));
|
|
11
|
+
const config_1 = require("../lib/config");
|
|
12
|
+
async function validateApiKey(apiKey) {
|
|
13
|
+
try {
|
|
14
|
+
const response = await axios_1.default.get(`${(0, config_1.getApiUrl)()}/users/validate`, {
|
|
15
|
+
headers: {
|
|
16
|
+
'X-API-Key': apiKey,
|
|
17
|
+
},
|
|
18
|
+
});
|
|
19
|
+
return response.data.data;
|
|
20
|
+
}
|
|
21
|
+
catch (error) {
|
|
22
|
+
throw new Error('Invalid API key');
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
async function browserAuthentication() {
|
|
26
|
+
const spinner = (0, ora_1.default)('Creating authentication session...').start();
|
|
27
|
+
try {
|
|
28
|
+
// Create CLI session
|
|
29
|
+
const createResponse = await axios_1.default.post(`${(0, config_1.getApiUrl)()}/auth/cli-session`);
|
|
30
|
+
const sessionData = createResponse.data;
|
|
31
|
+
spinner.succeed('Authentication session created');
|
|
32
|
+
console.log(chalk_1.default.cyan('Opening browser for authentication...'));
|
|
33
|
+
console.log(chalk_1.default.gray(`If browser doesn't open, visit: ${sessionData.data.authUrl}`));
|
|
34
|
+
// Open browser
|
|
35
|
+
await (0, open_1.default)(sessionData.data.authUrl);
|
|
36
|
+
// Poll for completion
|
|
37
|
+
const pollSpinner = (0, ora_1.default)('Waiting for authentication in browser...').start();
|
|
38
|
+
const startTime = Date.now();
|
|
39
|
+
const timeout = sessionData.data.expiresIn * 1000; // Convert to milliseconds
|
|
40
|
+
const pollInterval = 2000; // 2 seconds
|
|
41
|
+
while (Date.now() - startTime < timeout) {
|
|
42
|
+
// Wait before polling
|
|
43
|
+
await new Promise((resolve) => setTimeout(resolve, pollInterval));
|
|
44
|
+
// Check session status
|
|
45
|
+
const statusResponse = await axios_1.default.get(`${(0, config_1.getApiUrl)()}/auth/cli-session/${sessionData.data.sessionId}`);
|
|
46
|
+
const statusData = statusResponse.data.data;
|
|
47
|
+
if (statusData.status === 'completed' && statusData.apiKey) {
|
|
48
|
+
pollSpinner.succeed('Authentication completed!');
|
|
49
|
+
return statusData.apiKey;
|
|
50
|
+
}
|
|
51
|
+
if (statusData.status === 'expired') {
|
|
52
|
+
pollSpinner.fail('Authentication session expired');
|
|
53
|
+
throw new Error('Session expired. Please try again.');
|
|
54
|
+
}
|
|
55
|
+
// Continue polling (status is 'pending')
|
|
56
|
+
}
|
|
57
|
+
// Timeout reached
|
|
58
|
+
pollSpinner.fail('Authentication timed out');
|
|
59
|
+
throw new Error('Authentication timed out. Please try again.');
|
|
60
|
+
}
|
|
61
|
+
catch (error) {
|
|
62
|
+
spinner.fail('Authentication failed');
|
|
63
|
+
throw error;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
async function login(options) {
|
|
67
|
+
const { apiKey, apiUrl } = options;
|
|
68
|
+
// Set API URL if provided
|
|
69
|
+
if (apiUrl) {
|
|
70
|
+
(0, config_1.setApiUrl)(apiUrl);
|
|
71
|
+
}
|
|
72
|
+
try {
|
|
73
|
+
let finalApiKey;
|
|
74
|
+
if (apiKey) {
|
|
75
|
+
// Legacy flow: manual API key
|
|
76
|
+
const spinner = (0, ora_1.default)('Validating API key...').start();
|
|
77
|
+
const userData = await validateApiKey(apiKey);
|
|
78
|
+
spinner.succeed('Logged in successfully');
|
|
79
|
+
finalApiKey = apiKey;
|
|
80
|
+
console.log(chalk_1.default.green(`Welcome! You're logged in as ${userData.email}`));
|
|
81
|
+
console.log(chalk_1.default.gray(`Tier: ${userData.tier}`));
|
|
82
|
+
}
|
|
83
|
+
else {
|
|
84
|
+
// New flow: browser authentication
|
|
85
|
+
finalApiKey = await browserAuthentication();
|
|
86
|
+
// Validate the API key to get user info
|
|
87
|
+
const spinner = (0, ora_1.default)('Fetching user information...').start();
|
|
88
|
+
const userData = await validateApiKey(finalApiKey);
|
|
89
|
+
spinner.succeed('Successfully authenticated!');
|
|
90
|
+
console.log(chalk_1.default.green(`Welcome! You're logged in as ${userData.email}`));
|
|
91
|
+
console.log(chalk_1.default.gray(`Tier: ${userData.tier}`));
|
|
92
|
+
}
|
|
93
|
+
// Save API key
|
|
94
|
+
(0, config_1.setApiKey)(finalApiKey);
|
|
95
|
+
}
|
|
96
|
+
catch (error) {
|
|
97
|
+
console.error(chalk_1.default.red('Could not connect to HooksDash API'));
|
|
98
|
+
if (error instanceof Error) {
|
|
99
|
+
console.error(chalk_1.default.gray(error.message));
|
|
100
|
+
}
|
|
101
|
+
process.exit(1);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function logout(): void;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.logout = logout;
|
|
7
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
8
|
+
const config_1 = require("../lib/config");
|
|
9
|
+
function logout() {
|
|
10
|
+
if (!(0, config_1.isLoggedIn)()) {
|
|
11
|
+
console.log(chalk_1.default.yellow('You are not logged in'));
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
(0, config_1.clearApiKey)();
|
|
15
|
+
console.log(chalk_1.default.green('Logged out successfully'));
|
|
16
|
+
}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
4
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
5
|
+
};
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
8
|
+
const commander_1 = require("commander");
|
|
9
|
+
const login_1 = require("./commands/login");
|
|
10
|
+
const logout_1 = require("./commands/logout");
|
|
11
|
+
const forward_1 = require("./commands/forward");
|
|
12
|
+
const config_1 = require("./lib/config");
|
|
13
|
+
const program = new commander_1.Command();
|
|
14
|
+
program
|
|
15
|
+
.name('hooksdash')
|
|
16
|
+
.description('HooksDash CLI - Forward webhooks to your local machine')
|
|
17
|
+
.version('1.0.0');
|
|
18
|
+
program
|
|
19
|
+
.command('login')
|
|
20
|
+
.description('Login via browser or with API key')
|
|
21
|
+
.option('-k, --api-key <key>', 'Your HooksDash API key (optional, defaults to browser login)')
|
|
22
|
+
.option('--api-url <url>', 'Custom API URL (for development)')
|
|
23
|
+
.action(login_1.login);
|
|
24
|
+
program.command('logout').description('Logout and clear stored credentials').action(logout_1.logout);
|
|
25
|
+
program
|
|
26
|
+
.command('forward')
|
|
27
|
+
.description('Forward webhooks to your local server')
|
|
28
|
+
.option('-p, --port <port>', 'Local port to forward to', '3000')
|
|
29
|
+
.option('-h, --host <host>', 'Local host to forward to', 'localhost')
|
|
30
|
+
.option('-e, --endpoint <slug>', 'Endpoint slug to use')
|
|
31
|
+
.action((options) => {
|
|
32
|
+
(0, forward_1.forward)({
|
|
33
|
+
port: parseInt(options.port, 10),
|
|
34
|
+
host: options.host,
|
|
35
|
+
endpoint: options.endpoint,
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
program
|
|
39
|
+
.command('status')
|
|
40
|
+
.description('Check login status')
|
|
41
|
+
.action(() => {
|
|
42
|
+
if ((0, config_1.isLoggedIn)()) {
|
|
43
|
+
const apiKey = (0, config_1.getApiKey)();
|
|
44
|
+
const maskedKey = apiKey ? `${apiKey.slice(0, 7)}****${apiKey.slice(-4)}` : '';
|
|
45
|
+
console.log(chalk_1.default.green('Logged in'));
|
|
46
|
+
console.log(chalk_1.default.gray(`API Key: ${maskedKey}`));
|
|
47
|
+
}
|
|
48
|
+
else {
|
|
49
|
+
console.log(chalk_1.default.yellow('Not logged in'));
|
|
50
|
+
console.log(chalk_1.default.gray('Run: hooksdash login'));
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
program.parse();
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import Conf from 'conf';
|
|
2
|
+
interface ConfigSchema {
|
|
3
|
+
apiKey?: string;
|
|
4
|
+
apiUrl: string;
|
|
5
|
+
}
|
|
6
|
+
declare const config: Conf<ConfigSchema>;
|
|
7
|
+
export declare function getApiKey(): string | undefined;
|
|
8
|
+
export declare function setApiKey(apiKey: string): void;
|
|
9
|
+
export declare function clearApiKey(): void;
|
|
10
|
+
export declare function getApiUrl(): string;
|
|
11
|
+
export declare function setApiUrl(url: string): void;
|
|
12
|
+
export declare function isLoggedIn(): boolean;
|
|
13
|
+
export { config };
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.config = void 0;
|
|
7
|
+
exports.getApiKey = getApiKey;
|
|
8
|
+
exports.setApiKey = setApiKey;
|
|
9
|
+
exports.clearApiKey = clearApiKey;
|
|
10
|
+
exports.getApiUrl = getApiUrl;
|
|
11
|
+
exports.setApiUrl = setApiUrl;
|
|
12
|
+
exports.isLoggedIn = isLoggedIn;
|
|
13
|
+
const conf_1 = __importDefault(require("conf"));
|
|
14
|
+
const config = new conf_1.default({
|
|
15
|
+
projectName: 'hooksdash',
|
|
16
|
+
defaults: {
|
|
17
|
+
apiUrl: 'https://api.hooksdash.ibadsiddiqui.dev/api',
|
|
18
|
+
},
|
|
19
|
+
});
|
|
20
|
+
exports.config = config;
|
|
21
|
+
function getApiKey() {
|
|
22
|
+
return config.get('apiKey');
|
|
23
|
+
}
|
|
24
|
+
function setApiKey(apiKey) {
|
|
25
|
+
config.set('apiKey', apiKey);
|
|
26
|
+
}
|
|
27
|
+
function clearApiKey() {
|
|
28
|
+
config.delete('apiKey');
|
|
29
|
+
}
|
|
30
|
+
function getApiUrl() {
|
|
31
|
+
return config.get('apiUrl');
|
|
32
|
+
}
|
|
33
|
+
function setApiUrl(url) {
|
|
34
|
+
config.set('apiUrl', url);
|
|
35
|
+
}
|
|
36
|
+
function isLoggedIn() {
|
|
37
|
+
return !!getApiKey();
|
|
38
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
interface TunnelOptions {
|
|
2
|
+
apiKey: string;
|
|
3
|
+
apiUrl: string;
|
|
4
|
+
endpointSlug: string;
|
|
5
|
+
localPort: number;
|
|
6
|
+
localHost: string;
|
|
7
|
+
}
|
|
8
|
+
export declare class TunnelClient {
|
|
9
|
+
private socket;
|
|
10
|
+
private options;
|
|
11
|
+
private reconnectAttempts;
|
|
12
|
+
constructor(options: TunnelOptions);
|
|
13
|
+
connect(): Promise<void>;
|
|
14
|
+
private authenticate;
|
|
15
|
+
private forwardRequest;
|
|
16
|
+
disconnect(): void;
|
|
17
|
+
}
|
|
18
|
+
export {};
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.TunnelClient = void 0;
|
|
7
|
+
const socket_io_client_1 = require("socket.io-client");
|
|
8
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
9
|
+
const axios_1 = __importDefault(require("axios"));
|
|
10
|
+
const shared_1 = require("@hooksdash/shared");
|
|
11
|
+
class TunnelClient {
|
|
12
|
+
socket = null;
|
|
13
|
+
options;
|
|
14
|
+
reconnectAttempts = 0;
|
|
15
|
+
constructor(options) {
|
|
16
|
+
this.options = options;
|
|
17
|
+
}
|
|
18
|
+
async connect() {
|
|
19
|
+
return new Promise((resolve, reject) => {
|
|
20
|
+
const wsUrl = this.options.apiUrl.replace('http', 'ws');
|
|
21
|
+
this.socket = (0, socket_io_client_1.io)(`${wsUrl}/tunnel`, {
|
|
22
|
+
transports: ['websocket'],
|
|
23
|
+
reconnection: true,
|
|
24
|
+
reconnectionDelay: shared_1.WEBSOCKET.RECONNECT_DELAY,
|
|
25
|
+
reconnectionAttempts: shared_1.WEBSOCKET.MAX_RECONNECT_ATTEMPTS,
|
|
26
|
+
});
|
|
27
|
+
this.socket.on('connect', () => {
|
|
28
|
+
console.log(chalk_1.default.green('Connected to HooksDash'));
|
|
29
|
+
this.authenticate();
|
|
30
|
+
this.reconnectAttempts = 0;
|
|
31
|
+
});
|
|
32
|
+
this.socket.on(shared_1.TUNNEL_MESSAGE_TYPE.AUTH_SUCCESS, (data) => {
|
|
33
|
+
console.log(chalk_1.default.green(`Authenticated! Forwarding to localhost:${this.options.localPort}`));
|
|
34
|
+
console.log(chalk_1.default.cyan(`Webhook URL: https://hooksdash.ibadsiddiqui.dev/api/h/${data.endpoint.slug}`));
|
|
35
|
+
console.log(chalk_1.default.gray('Press Ctrl+C to stop'));
|
|
36
|
+
resolve();
|
|
37
|
+
});
|
|
38
|
+
this.socket.on(shared_1.TUNNEL_MESSAGE_TYPE.AUTH_ERROR, (data) => {
|
|
39
|
+
console.error(chalk_1.default.red(`Authentication failed: ${data.message}`));
|
|
40
|
+
this.disconnect();
|
|
41
|
+
reject(new Error(data.message));
|
|
42
|
+
});
|
|
43
|
+
this.socket.on('request', async (payload, callback) => {
|
|
44
|
+
console.log(chalk_1.default.cyan(`← ${payload.method} ${payload.path}`));
|
|
45
|
+
try {
|
|
46
|
+
const response = await this.forwardRequest(payload);
|
|
47
|
+
console.log(chalk_1.default.green(`→ ${response.statusCode}`));
|
|
48
|
+
callback(response);
|
|
49
|
+
}
|
|
50
|
+
catch (error) {
|
|
51
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
52
|
+
console.error(chalk_1.default.red(`→ Error: ${errorMessage}`));
|
|
53
|
+
callback({
|
|
54
|
+
requestId: payload.requestId,
|
|
55
|
+
statusCode: 502,
|
|
56
|
+
headers: {},
|
|
57
|
+
body: JSON.stringify({ error: errorMessage }),
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
this.socket.on(shared_1.TUNNEL_MESSAGE_TYPE.HEARTBEAT, () => {
|
|
62
|
+
// Respond to heartbeat
|
|
63
|
+
});
|
|
64
|
+
this.socket.on('disconnect', (reason) => {
|
|
65
|
+
if (reason === 'io server disconnect') {
|
|
66
|
+
console.log(chalk_1.default.yellow('Disconnected by server'));
|
|
67
|
+
}
|
|
68
|
+
else {
|
|
69
|
+
console.log(chalk_1.default.yellow('Connection lost, attempting to reconnect...'));
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
this.socket.on('connect_error', (error) => {
|
|
73
|
+
this.reconnectAttempts++;
|
|
74
|
+
if (this.reconnectAttempts >= shared_1.WEBSOCKET.MAX_RECONNECT_ATTEMPTS) {
|
|
75
|
+
console.error(chalk_1.default.red('Failed to connect after multiple attempts'));
|
|
76
|
+
reject(error);
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
this.socket.on('error', (error) => {
|
|
80
|
+
console.error(chalk_1.default.red(`Socket error: ${error}`));
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
authenticate() {
|
|
85
|
+
const payload = {
|
|
86
|
+
apiKey: this.options.apiKey,
|
|
87
|
+
endpointSlug: this.options.endpointSlug,
|
|
88
|
+
};
|
|
89
|
+
this.socket?.emit(shared_1.TUNNEL_MESSAGE_TYPE.AUTH, payload);
|
|
90
|
+
}
|
|
91
|
+
async forwardRequest(payload) {
|
|
92
|
+
const url = `http://${this.options.localHost}:${this.options.localPort}${payload.path}`;
|
|
93
|
+
try {
|
|
94
|
+
const response = await (0, axios_1.default)({
|
|
95
|
+
method: payload.method.toLowerCase(),
|
|
96
|
+
url,
|
|
97
|
+
headers: payload.headers,
|
|
98
|
+
data: payload.body || undefined,
|
|
99
|
+
validateStatus: () => true, // Accept any status code
|
|
100
|
+
});
|
|
101
|
+
return {
|
|
102
|
+
requestId: payload.requestId,
|
|
103
|
+
statusCode: response.status,
|
|
104
|
+
headers: response.headers,
|
|
105
|
+
body: typeof response.data === 'string' ? response.data : JSON.stringify(response.data),
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
catch (error) {
|
|
109
|
+
throw new Error(error instanceof Error ? error.message : 'Request forwarding failed');
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
disconnect() {
|
|
113
|
+
if (this.socket) {
|
|
114
|
+
this.socket.disconnect();
|
|
115
|
+
this.socket = null;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
exports.TunnelClient = TunnelClient;
|
package/package.json
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "hooksdash",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "CLI tool for HooksDash - Inspect, debug, and forward webhooks to your local development environment",
|
|
5
|
+
"bin": {
|
|
6
|
+
"hooksdash": "bin/hooksdash.js",
|
|
7
|
+
"hd": "bin/hooksdash.js"
|
|
8
|
+
},
|
|
9
|
+
"main": "./dist/index.js",
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"files": [
|
|
12
|
+
"bin",
|
|
13
|
+
"dist",
|
|
14
|
+
"README.md",
|
|
15
|
+
"LICENSE"
|
|
16
|
+
],
|
|
17
|
+
"scripts": {
|
|
18
|
+
"build": "tsc",
|
|
19
|
+
"clean": "rm -rf dist",
|
|
20
|
+
"dev": "tsc --watch",
|
|
21
|
+
"prepublishOnly": "pnpm run build"
|
|
22
|
+
},
|
|
23
|
+
"dependencies": {
|
|
24
|
+
"@hooksdash/shared": "workspace:*",
|
|
25
|
+
"axios": "^1.13.3",
|
|
26
|
+
"chalk": "^4.1.2",
|
|
27
|
+
"commander": "^11.1.0",
|
|
28
|
+
"conf": "^10.2.0",
|
|
29
|
+
"open": "^11.0.0",
|
|
30
|
+
"ora": "^5.4.1",
|
|
31
|
+
"socket.io-client": "^4.7.0"
|
|
32
|
+
},
|
|
33
|
+
"devDependencies": {
|
|
34
|
+
"@types/node": "^20.11.0",
|
|
35
|
+
"typescript": "^5.3.0"
|
|
36
|
+
},
|
|
37
|
+
"keywords": [
|
|
38
|
+
"webhook",
|
|
39
|
+
"webhooks",
|
|
40
|
+
"cli",
|
|
41
|
+
"tunnel",
|
|
42
|
+
"hooksdash",
|
|
43
|
+
"webhook-testing",
|
|
44
|
+
"webhook-debugging",
|
|
45
|
+
"localhost-tunnel",
|
|
46
|
+
"ngrok-alternative",
|
|
47
|
+
"webhook-inspector",
|
|
48
|
+
"webhook-forwarding",
|
|
49
|
+
"development-tools",
|
|
50
|
+
"api-testing"
|
|
51
|
+
],
|
|
52
|
+
"repository": {
|
|
53
|
+
"type": "git",
|
|
54
|
+
"url": "git+https://github.com/ibadsiddiqui/hooksdash.git",
|
|
55
|
+
"directory": "packages/cli"
|
|
56
|
+
},
|
|
57
|
+
"bugs": {
|
|
58
|
+
"url": "https://github.com/ibadsiddiqui/hooksdash/issues"
|
|
59
|
+
},
|
|
60
|
+
"homepage": "https://hooksdash.ibadsiddiqui.dev",
|
|
61
|
+
"author": "Ibad Siddiqui <ibad@hooksdash.ibadsiddiqui.dev> (https://hooksdash.ibadsiddiqui.dev)",
|
|
62
|
+
"license": "MIT",
|
|
63
|
+
"engines": {
|
|
64
|
+
"node": ">=18.0.0"
|
|
65
|
+
},
|
|
66
|
+
"publishConfig": {
|
|
67
|
+
"access": "public"
|
|
68
|
+
}
|
|
69
|
+
}
|