@syncbridge/ngrok 0.4.1
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/LICENSE +15 -0
- package/README.md +1 -0
- package/components/ngrok-tcp-client.component.d.ts +33 -0
- package/components/ngrok-tcp-client.component.js +202 -0
- package/components/ngrok-tcp-client.variables.d.ts +8 -0
- package/components/ngrok-tcp-client.variables.js +25 -0
- package/components.d.ts +1 -0
- package/components.js +1 -0
- package/constants.d.ts +4 -0
- package/constants.js +7 -0
- package/index.d.ts +5 -0
- package/index.js +9 -0
- package/package.json +33 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
Copyright (c) 2025 Panates (Panates Teknoloji Yatirim A.S.). All rights reserved.
|
|
2
|
+
|
|
3
|
+
This software and associated documentation files (the "Software") are
|
|
4
|
+
the proprietary property of Panates and are protected by copyright laws
|
|
5
|
+
and international copyright treaties.
|
|
6
|
+
|
|
7
|
+
Unauthorized copying, distribution, modification, reverse engineering,
|
|
8
|
+
or use of this software, in whole or in part, is strictly prohibited
|
|
9
|
+
without the express written permission of Panates.
|
|
10
|
+
|
|
11
|
+
This Software is provided solely for use in accordance with the terms
|
|
12
|
+
of a valid license agreement. Any unauthorized use may result in
|
|
13
|
+
civil and/or criminal penalties.
|
|
14
|
+
|
|
15
|
+
For licensing inquiries, please contact: info@panates.com
|
package/README.md
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# SyncBridge Ngrok Connectors
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { Buffer } from 'node:buffer';
|
|
2
|
+
import net from 'node:net';
|
|
3
|
+
import ngrok from '@ngrok/ngrok';
|
|
4
|
+
import { IoClientBaseComponent } from '@syncbridge/builtins';
|
|
5
|
+
import { NgrokTcpClientVariables } from './ngrok-tcp-client.variables.js';
|
|
6
|
+
export declare class NgrokTcpClientComponent<TEvents extends NgrokTcpClientComponent.Events = NgrokTcpClientComponent.Events> extends IoClientBaseComponent<TEvents> {
|
|
7
|
+
readonly protocol: string;
|
|
8
|
+
protected _ngrokListener?: ngrok.Listener;
|
|
9
|
+
protected _server?: net.Server;
|
|
10
|
+
protected _socket?: net.Socket;
|
|
11
|
+
protected _stopping?: boolean;
|
|
12
|
+
protected _reconnectTimer?: NodeJS.Timeout;
|
|
13
|
+
values: NgrokTcpClientVariables;
|
|
14
|
+
get connected(): boolean;
|
|
15
|
+
protected _start(abortSignal: AbortSignal): Promise<void>;
|
|
16
|
+
protected _stop(): Promise<void>;
|
|
17
|
+
write(data: string | Buffer): void;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
/**
|
|
21
|
+
* @namespace
|
|
22
|
+
*/
|
|
23
|
+
export declare namespace NgrokTcpClientComponent {
|
|
24
|
+
interface Events extends IoClientBaseComponent.Events {
|
|
25
|
+
connect: [];
|
|
26
|
+
disconnect: [error: Error | undefined];
|
|
27
|
+
data: [data: Buffer];
|
|
28
|
+
reconnecting: [n: number, delay: number];
|
|
29
|
+
write: [data: Buffer | string];
|
|
30
|
+
}
|
|
31
|
+
const Variables: typeof NgrokTcpClientVariables;
|
|
32
|
+
type Variables = NgrokTcpClientVariables;
|
|
33
|
+
}
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
import { __decorate, __metadata } from "tslib";
|
|
2
|
+
import net from 'node:net';
|
|
3
|
+
import ngrok from '@ngrok/ngrok';
|
|
4
|
+
import { IoClientBaseComponent } from '@syncbridge/builtins';
|
|
5
|
+
import { Component, ServiceStatus } from '@syncbridge/common';
|
|
6
|
+
import colors from 'ansi-colors';
|
|
7
|
+
import { splitString } from 'fast-tokenizer';
|
|
8
|
+
import { noOp } from '../constants.js';
|
|
9
|
+
import { NgrokTcpClientVariables } from './ngrok-tcp-client.variables.js';
|
|
10
|
+
let NgrokTcpClientComponent = class NgrokTcpClientComponent extends IoClientBaseComponent {
|
|
11
|
+
protocol = 'tcp';
|
|
12
|
+
_ngrokListener;
|
|
13
|
+
_server;
|
|
14
|
+
_socket;
|
|
15
|
+
_stopping;
|
|
16
|
+
_reconnectTimer;
|
|
17
|
+
get connected() {
|
|
18
|
+
return !!this._socket;
|
|
19
|
+
}
|
|
20
|
+
async _start(abortSignal) {
|
|
21
|
+
await super._start(abortSignal);
|
|
22
|
+
this._stopping = false;
|
|
23
|
+
if (this._server) {
|
|
24
|
+
this._server.close();
|
|
25
|
+
}
|
|
26
|
+
let tries = -1;
|
|
27
|
+
const onSocketData = (data) => {
|
|
28
|
+
this.emit('data', data);
|
|
29
|
+
};
|
|
30
|
+
const onSocketClose = () => {
|
|
31
|
+
this._socket = undefined;
|
|
32
|
+
};
|
|
33
|
+
const startServer = async () => {
|
|
34
|
+
try {
|
|
35
|
+
this.logger?.debug(`Creating local listener`);
|
|
36
|
+
this._server = await createServer(this.values.localPort, socket => {
|
|
37
|
+
// The last connected socket is ngok agent, we will use it to read and write operations
|
|
38
|
+
if (this._socket) {
|
|
39
|
+
this._socket.off('data', onSocketData);
|
|
40
|
+
this._socket.off('close', onSocketClose);
|
|
41
|
+
}
|
|
42
|
+
this._socket = socket;
|
|
43
|
+
socket.on('data', onSocketData);
|
|
44
|
+
socket.on('close', onSocketClose);
|
|
45
|
+
}).catch(noOp);
|
|
46
|
+
tries = -1;
|
|
47
|
+
}
|
|
48
|
+
catch (error) {
|
|
49
|
+
tries++;
|
|
50
|
+
error.message = `Can't create local listener. ${error?.message}`;
|
|
51
|
+
this.logger?.info(error.message +
|
|
52
|
+
'\n' +
|
|
53
|
+
` Retry attempt (${colors.cyan(String(tries + 1))}) will be made in ${colors.cyan('5 secs')}`);
|
|
54
|
+
this.setStatus(ServiceStatus.unhealthy, error.message);
|
|
55
|
+
this._reconnectTimer = setTimeout(() => {
|
|
56
|
+
startServer();
|
|
57
|
+
}, 5000);
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
/* */
|
|
61
|
+
const _ngrokStatusChange = (status) => {
|
|
62
|
+
if (status === 'closed') {
|
|
63
|
+
if (this._stopping)
|
|
64
|
+
this.emit('disconnect');
|
|
65
|
+
else {
|
|
66
|
+
const error = new Error('Ngrok tunnel disconnected');
|
|
67
|
+
this.setStatus(ServiceStatus.unhealthy, error.message);
|
|
68
|
+
this.emit('disconnect', error);
|
|
69
|
+
}
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
if (status === 'connected') {
|
|
73
|
+
tries = -1;
|
|
74
|
+
if (this._ngrokListener) {
|
|
75
|
+
this.logger?.info(`Ngrok tunnel reconnected: ${colors.cyan(this._ngrokListener.url())}`);
|
|
76
|
+
}
|
|
77
|
+
this.setStatus(ServiceStatus.started);
|
|
78
|
+
this.emit('connect');
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
/* */
|
|
82
|
+
const _ngrokLogEvent = (event) => {
|
|
83
|
+
if (this._stopping)
|
|
84
|
+
return;
|
|
85
|
+
const log = parseNgrokLog(event);
|
|
86
|
+
if (log.msg?.includes(' not yet '))
|
|
87
|
+
return;
|
|
88
|
+
if (log.lvl === 'warn') {
|
|
89
|
+
if (log.err)
|
|
90
|
+
this.logger?.error(log.err);
|
|
91
|
+
}
|
|
92
|
+
else if (log.lvl === 'error') {
|
|
93
|
+
this.logger?.error(log.msg);
|
|
94
|
+
}
|
|
95
|
+
else
|
|
96
|
+
this.logger?.debug(`lvl=${log.lvl} | ${log.msg}`);
|
|
97
|
+
};
|
|
98
|
+
/* */
|
|
99
|
+
const startNgrok = async () => {
|
|
100
|
+
this.logger?.trace('Starting Ngrok tunnel');
|
|
101
|
+
/** Close previous tunnels */
|
|
102
|
+
if (this._ngrokListener) {
|
|
103
|
+
this.logger?.debug('Disconnecting previous tunnel');
|
|
104
|
+
await this._ngrokListener.close();
|
|
105
|
+
}
|
|
106
|
+
this._ngrokListener = undefined;
|
|
107
|
+
const { port } = this._server.address();
|
|
108
|
+
/** Create tunnel */
|
|
109
|
+
try {
|
|
110
|
+
this.logger?.debug(`Creating Ngrok tunnel`);
|
|
111
|
+
this._ngrokListener = await ngrok.forward({
|
|
112
|
+
proto: 'tcp',
|
|
113
|
+
authtoken: this.values.authToken,
|
|
114
|
+
addr: port,
|
|
115
|
+
onStatusChange: _ngrokStatusChange,
|
|
116
|
+
onLogEvent: _ngrokLogEvent,
|
|
117
|
+
});
|
|
118
|
+
this.logger?.info(`Ngrok tunnel connected: ${colors.blueBright(this._ngrokListener.url())}`);
|
|
119
|
+
}
|
|
120
|
+
catch (err) {
|
|
121
|
+
tries++;
|
|
122
|
+
this.logger?.error(`Can't crate Ngrok tunnel. ${err?.message}\n` +
|
|
123
|
+
` Retry attempt (${colors.cyan(String(tries))}) will be made in ${colors.cyan('5 secs')}`);
|
|
124
|
+
this._reconnectTimer = setTimeout(() => {
|
|
125
|
+
startNgrok();
|
|
126
|
+
}, 5000);
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
};
|
|
130
|
+
startServer()
|
|
131
|
+
.then(() => startNgrok())
|
|
132
|
+
.catch(noOp);
|
|
133
|
+
}
|
|
134
|
+
async _stop() {
|
|
135
|
+
this._stopping = true;
|
|
136
|
+
if (this._ngrokListener) {
|
|
137
|
+
this.logger?.debug('Disconnecting previous tunnel');
|
|
138
|
+
await this._ngrokListener.close().catch(noOp);
|
|
139
|
+
this._ngrokListener = undefined;
|
|
140
|
+
}
|
|
141
|
+
if (this._server?.listening) {
|
|
142
|
+
await new Promise(resolve => {
|
|
143
|
+
this._server.close(() => resolve());
|
|
144
|
+
}).catch(noOp);
|
|
145
|
+
this._server = undefined;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
write(data) {
|
|
149
|
+
if (!this.connected) {
|
|
150
|
+
throw new Error(`Can't write while TCP socket is not connected`);
|
|
151
|
+
}
|
|
152
|
+
this.emit('write', data);
|
|
153
|
+
this._socket?.write(data);
|
|
154
|
+
}
|
|
155
|
+
};
|
|
156
|
+
__decorate([
|
|
157
|
+
Component.UseVariables(),
|
|
158
|
+
__metadata("design:type", NgrokTcpClientVariables)
|
|
159
|
+
], NgrokTcpClientComponent.prototype, "values", void 0);
|
|
160
|
+
NgrokTcpClientComponent = __decorate([
|
|
161
|
+
Component({
|
|
162
|
+
displayName: 'Ngrok TCP Client Tunnel',
|
|
163
|
+
description: 'Tcp client tunnel for Ngrok (https://ngrok.com)',
|
|
164
|
+
tags: ['tcp', 'client', 'socket', 'connection', 'ngrok'],
|
|
165
|
+
})
|
|
166
|
+
], NgrokTcpClientComponent);
|
|
167
|
+
export { NgrokTcpClientComponent };
|
|
168
|
+
/**
|
|
169
|
+
/**
|
|
170
|
+
* @namespace
|
|
171
|
+
*/
|
|
172
|
+
(function (NgrokTcpClientComponent) {
|
|
173
|
+
NgrokTcpClientComponent.Variables = NgrokTcpClientVariables;
|
|
174
|
+
})(NgrokTcpClientComponent || (NgrokTcpClientComponent = {}));
|
|
175
|
+
/**
|
|
176
|
+
* Creates and initializes a server instance on the first available port within the specified range.
|
|
177
|
+
*/
|
|
178
|
+
function createServer(port = 0, connectionListener) {
|
|
179
|
+
return new Promise((resolve, reject) => {
|
|
180
|
+
const server = net.createServer(connectionListener);
|
|
181
|
+
server.on('error', reject);
|
|
182
|
+
server.listen(port, '127.0.0.1', () => {
|
|
183
|
+
resolve(server);
|
|
184
|
+
});
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
function parseNgrokLog(line) {
|
|
188
|
+
const splitted = splitString(line, {
|
|
189
|
+
delimiters: ' ',
|
|
190
|
+
keepQuotes: true,
|
|
191
|
+
quotes: true,
|
|
192
|
+
});
|
|
193
|
+
return splitted.reduce((a, s) => {
|
|
194
|
+
const v = splitString(s, {
|
|
195
|
+
delimiters: '=',
|
|
196
|
+
keepQuotes: false,
|
|
197
|
+
quotes: true,
|
|
198
|
+
});
|
|
199
|
+
a[v[0]] = v[1];
|
|
200
|
+
return a;
|
|
201
|
+
}, {});
|
|
202
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { __decorate, __metadata } from "tslib";
|
|
2
|
+
import { IoClientBaseComponent } from '@syncbridge/builtins';
|
|
3
|
+
import { DefineVariable, VariableType } from '@syncbridge/common';
|
|
4
|
+
/**
|
|
5
|
+
* Variables
|
|
6
|
+
*/
|
|
7
|
+
export class NgrokTcpClientVariables extends IoClientBaseComponent.Variables {
|
|
8
|
+
}
|
|
9
|
+
__decorate([
|
|
10
|
+
DefineVariable({
|
|
11
|
+
label: 'Ngrok Auth Token',
|
|
12
|
+
type: VariableType.Secret,
|
|
13
|
+
description: 'Ngrok auth token',
|
|
14
|
+
required: true,
|
|
15
|
+
}),
|
|
16
|
+
__metadata("design:type", String)
|
|
17
|
+
], NgrokTcpClientVariables.prototype, "authToken", void 0);
|
|
18
|
+
__decorate([
|
|
19
|
+
DefineVariable({
|
|
20
|
+
label: 'Local Port',
|
|
21
|
+
description: 'Local port number which ngrok process connection to be served. If not set, a port number will be automatically selected',
|
|
22
|
+
default: 0,
|
|
23
|
+
}),
|
|
24
|
+
__metadata("design:type", Number)
|
|
25
|
+
], NgrokTcpClientVariables.prototype, "localPort", void 0);
|
package/components.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './components/ngrok-tcp-client.component.js';
|
package/components.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './components/ngrok-tcp-client.component.js';
|
package/constants.d.ts
ADDED
package/constants.js
ADDED
package/index.d.ts
ADDED
package/index.js
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { makeExtensionPackage } from '@syncbridge/common';
|
|
2
|
+
import * as components from './components.js';
|
|
3
|
+
export { components };
|
|
4
|
+
export * from './components.js';
|
|
5
|
+
const cfg = makeExtensionPackage({
|
|
6
|
+
processors: [],
|
|
7
|
+
components: Object.values(components),
|
|
8
|
+
});
|
|
9
|
+
export default cfg;
|
package/package.json
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@syncbridge/ngrok",
|
|
3
|
+
"version": "0.4.1",
|
|
4
|
+
"description": "SyncBridge Ngrok component",
|
|
5
|
+
"author": "Panates Inc",
|
|
6
|
+
"license": "UNLICENSED",
|
|
7
|
+
"dependencies": {
|
|
8
|
+
"@ngrok/ngrok": "^1.7.0",
|
|
9
|
+
"ansi-colors": "^4.1.3",
|
|
10
|
+
"fast-tokenizer": "^1.9.0",
|
|
11
|
+
"node-events-async": "^1.5.0"
|
|
12
|
+
},
|
|
13
|
+
"peerDependencies": {
|
|
14
|
+
"@syncbridge/common": "^0.4.1",
|
|
15
|
+
"@syncbridge/builtins": "^0.4.1"
|
|
16
|
+
},
|
|
17
|
+
"exports": {
|
|
18
|
+
".": {
|
|
19
|
+
"types": "./index.d.ts",
|
|
20
|
+
"default": "./index.js"
|
|
21
|
+
},
|
|
22
|
+
"./package.json": "./package.json"
|
|
23
|
+
},
|
|
24
|
+
"type": "module",
|
|
25
|
+
"module": "./index.js",
|
|
26
|
+
"types": "./index.d.ts",
|
|
27
|
+
"engines": {
|
|
28
|
+
"node": ">=20.0"
|
|
29
|
+
},
|
|
30
|
+
"publishConfig": {
|
|
31
|
+
"access": "public"
|
|
32
|
+
}
|
|
33
|
+
}
|