@syncbridge/pinggy 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/pinggy-tcp-client.component.d.ts +34 -0
- package/components/pinggy-tcp-client.component.js +209 -0
- package/components/pinggy-tcp-client.variables.d.ts +8 -0
- package/components/pinggy-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,34 @@
|
|
|
1
|
+
import { Buffer } from 'node:buffer';
|
|
2
|
+
import net from 'node:net';
|
|
3
|
+
import { TunnelInstance } from '@pinggy/pinggy';
|
|
4
|
+
import { IoClientBaseComponent } from '@syncbridge/builtins';
|
|
5
|
+
import { PinggyTcpClientVariables } from './pinggy-tcp-client.variables.js';
|
|
6
|
+
export declare class PinggyTcpClientComponent<TEvents extends PinggyTcpClientComponent.Events = PinggyTcpClientComponent.Events> extends IoClientBaseComponent<TEvents> {
|
|
7
|
+
readonly protocol: string;
|
|
8
|
+
protected _tunnel?: TunnelInstance;
|
|
9
|
+
protected _tunnelHealthcheckTimer?: NodeJS.Timeout;
|
|
10
|
+
protected _server?: net.Server;
|
|
11
|
+
protected _socket?: net.Socket;
|
|
12
|
+
protected _stopping?: boolean;
|
|
13
|
+
protected _reconnectTimer?: NodeJS.Timeout;
|
|
14
|
+
values: PinggyTcpClientVariables;
|
|
15
|
+
get connected(): boolean;
|
|
16
|
+
protected _start(abortSignal: AbortSignal): Promise<void>;
|
|
17
|
+
protected _stop(): Promise<void>;
|
|
18
|
+
write(data: string | Buffer): void;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
/**
|
|
22
|
+
* @namespace
|
|
23
|
+
*/
|
|
24
|
+
export declare namespace PinggyTcpClientComponent {
|
|
25
|
+
interface Events extends IoClientBaseComponent.Events {
|
|
26
|
+
connect: [];
|
|
27
|
+
disconnect: [error: Error | undefined];
|
|
28
|
+
data: [data: Buffer];
|
|
29
|
+
reconnecting: [n: number, delay: number];
|
|
30
|
+
write: [data: Buffer | string];
|
|
31
|
+
}
|
|
32
|
+
const Variables: typeof PinggyTcpClientVariables;
|
|
33
|
+
type Variables = PinggyTcpClientVariables;
|
|
34
|
+
}
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
import { __decorate, __metadata } from "tslib";
|
|
2
|
+
import net from 'node:net';
|
|
3
|
+
import { clearInterval } from 'node:timers';
|
|
4
|
+
import { pinggy } from '@pinggy/pinggy';
|
|
5
|
+
import { IoClientBaseComponent } from '@syncbridge/builtins';
|
|
6
|
+
import { Component, ServiceStatus } from '@syncbridge/common';
|
|
7
|
+
import colors from 'ansi-colors';
|
|
8
|
+
import { noOp } from '../constants.js';
|
|
9
|
+
import { PinggyTcpClientVariables } from './pinggy-tcp-client.variables.js';
|
|
10
|
+
let PinggyTcpClientComponent = class PinggyTcpClientComponent extends IoClientBaseComponent {
|
|
11
|
+
protocol = 'tcp';
|
|
12
|
+
_tunnel;
|
|
13
|
+
_tunnelHealthcheckTimer;
|
|
14
|
+
_server;
|
|
15
|
+
_socket;
|
|
16
|
+
_stopping;
|
|
17
|
+
_reconnectTimer;
|
|
18
|
+
get connected() {
|
|
19
|
+
return !!this._socket;
|
|
20
|
+
}
|
|
21
|
+
async _start(abortSignal) {
|
|
22
|
+
await super._start(abortSignal);
|
|
23
|
+
this._stopping = false;
|
|
24
|
+
if (this._server) {
|
|
25
|
+
this._server.close();
|
|
26
|
+
}
|
|
27
|
+
const onSocketData = (data) => {
|
|
28
|
+
this.emit('data', data);
|
|
29
|
+
};
|
|
30
|
+
const onSocketClose = () => {
|
|
31
|
+
this._socket = undefined;
|
|
32
|
+
};
|
|
33
|
+
let tries = 0;
|
|
34
|
+
const startLocalListener = async () => {
|
|
35
|
+
try {
|
|
36
|
+
this.logger?.debug(`Creating local listener`);
|
|
37
|
+
this._server = await createServer(this.values.localPort, socket => {
|
|
38
|
+
// The last connected socket is ngok agent, we will use it to read and write operations
|
|
39
|
+
if (this._socket) {
|
|
40
|
+
this._socket.off('data', onSocketData);
|
|
41
|
+
this._socket.off('close', onSocketClose);
|
|
42
|
+
}
|
|
43
|
+
this._socket = socket;
|
|
44
|
+
socket.on('data', onSocketData);
|
|
45
|
+
socket.on('close', onSocketClose);
|
|
46
|
+
}).catch(noOp);
|
|
47
|
+
tries = 0;
|
|
48
|
+
}
|
|
49
|
+
catch (error) {
|
|
50
|
+
tries++;
|
|
51
|
+
error.message = `Can't create local listener. ${error?.message}`;
|
|
52
|
+
this.logger?.info(error.message +
|
|
53
|
+
'\n' +
|
|
54
|
+
` Retry attempt (${colors.cyan(String(tries))}) will be made in ${colors.cyan('5 secs')}`);
|
|
55
|
+
this.setStatus(ServiceStatus.unhealthy, error.message);
|
|
56
|
+
this._reconnectTimer = setTimeout(() => {
|
|
57
|
+
startLocalListener();
|
|
58
|
+
}, 5000);
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
/* */
|
|
62
|
+
const startTunnel = async () => {
|
|
63
|
+
/** Close previous tunnels */
|
|
64
|
+
if (this._tunnel) {
|
|
65
|
+
this.logger?.debug('Closing previous tunnel');
|
|
66
|
+
await this._tunnel.stop().catch(noOp);
|
|
67
|
+
}
|
|
68
|
+
this._tunnel = undefined;
|
|
69
|
+
const { port } = this._server.address();
|
|
70
|
+
/** Create the tunnel */
|
|
71
|
+
try {
|
|
72
|
+
this.logger?.debug(`Creating tcp tunnel`);
|
|
73
|
+
this._tunnel = await pinggy.forward({
|
|
74
|
+
token: this.values.authToken,
|
|
75
|
+
forwarding: 'tcp://localhost:' + port,
|
|
76
|
+
autoReconnect: true,
|
|
77
|
+
reconnectInterval: 5,
|
|
78
|
+
maxReconnectAttempts: Number.MAX_SAFE_INTEGER,
|
|
79
|
+
});
|
|
80
|
+
tries = 0;
|
|
81
|
+
this._tunnel.setTunnelErrorCallback(payload => {
|
|
82
|
+
const { errorNo, error, recoverable } = payload;
|
|
83
|
+
const err = new Error(error);
|
|
84
|
+
err.code = errorNo;
|
|
85
|
+
err.recoverable = recoverable;
|
|
86
|
+
this.emit('error', err);
|
|
87
|
+
if (!recoverable) {
|
|
88
|
+
setTimeout(() => {
|
|
89
|
+
startTunnel().catch(noOp);
|
|
90
|
+
}, 1000);
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
const urls = await this._tunnel.urls();
|
|
94
|
+
this.logger?.info(`Tunnel started: tcp://${colors.blueBright(urls.join(', '))}`);
|
|
95
|
+
}
|
|
96
|
+
catch (err) {
|
|
97
|
+
tries++;
|
|
98
|
+
this.logger?.error(`Can't crate tunnel. ${err?.message}\n` +
|
|
99
|
+
` Retry attempt (${colors.cyan(String(tries))}) will be made in ${colors.cyan('5 secs')}`);
|
|
100
|
+
this._reconnectTimer = setTimeout(() => {
|
|
101
|
+
startTunnel();
|
|
102
|
+
}, 5000);
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
const tunnelHealthCheck = async () => {
|
|
107
|
+
// c8 ignore next
|
|
108
|
+
if (!this._tunnel)
|
|
109
|
+
return;
|
|
110
|
+
let lastStatus = await this._tunnel.getStatus();
|
|
111
|
+
this._tunnelHealthcheckTimer = setInterval(() => {
|
|
112
|
+
if (!this._tunnel) {
|
|
113
|
+
clearInterval(this._tunnelHealthcheckTimer);
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
this._tunnel.getStatus()
|
|
117
|
+
.then(async (status) => {
|
|
118
|
+
if (status !== lastStatus) {
|
|
119
|
+
lastStatus = status;
|
|
120
|
+
await tunnelStatusChanged(status);
|
|
121
|
+
}
|
|
122
|
+
})
|
|
123
|
+
.catch(e => this.emit('error', e));
|
|
124
|
+
}, 100).unref();
|
|
125
|
+
};
|
|
126
|
+
/* */
|
|
127
|
+
const tunnelStatusChanged = async (status) => {
|
|
128
|
+
if (status === 'closed') {
|
|
129
|
+
if (this._stopping)
|
|
130
|
+
this.emit('disconnect');
|
|
131
|
+
else {
|
|
132
|
+
const error = new Error('Tunnel disconnected');
|
|
133
|
+
this.setStatus(ServiceStatus.unhealthy, error.message);
|
|
134
|
+
this.emit('disconnect', error);
|
|
135
|
+
}
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
if (status === 'live' && this._tunnel) {
|
|
139
|
+
tries = 0;
|
|
140
|
+
const urls = await this._tunnel.urls();
|
|
141
|
+
this.logger?.info(`Tunnel reconnected: ${colors.cyan(urls.join(', '))}`);
|
|
142
|
+
this.setStatus(ServiceStatus.started);
|
|
143
|
+
this.emit('connect');
|
|
144
|
+
}
|
|
145
|
+
};
|
|
146
|
+
startLocalListener()
|
|
147
|
+
.then(() => startTunnel())
|
|
148
|
+
.then(() => tunnelHealthCheck())
|
|
149
|
+
.catch(noOp);
|
|
150
|
+
}
|
|
151
|
+
async _stop() {
|
|
152
|
+
this._stopping = true;
|
|
153
|
+
clearInterval(this._tunnelHealthcheckTimer);
|
|
154
|
+
if (this._tunnel) {
|
|
155
|
+
this.logger?.debug('Disconnecting previous tunnel');
|
|
156
|
+
try {
|
|
157
|
+
await this._tunnel.stop();
|
|
158
|
+
}
|
|
159
|
+
catch (err) {
|
|
160
|
+
this.logger?.error(err);
|
|
161
|
+
}
|
|
162
|
+
this._tunnel = undefined;
|
|
163
|
+
}
|
|
164
|
+
if (this._server?.listening) {
|
|
165
|
+
await new Promise(resolve => {
|
|
166
|
+
this._server.close(() => resolve());
|
|
167
|
+
}).catch(noOp);
|
|
168
|
+
this._server = undefined;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
write(data) {
|
|
172
|
+
if (!this.connected) {
|
|
173
|
+
throw new Error(`Can't write while TCP socket is not connected`);
|
|
174
|
+
}
|
|
175
|
+
this.emit('write', data);
|
|
176
|
+
this._socket?.write(data);
|
|
177
|
+
}
|
|
178
|
+
};
|
|
179
|
+
__decorate([
|
|
180
|
+
Component.UseVariables(),
|
|
181
|
+
__metadata("design:type", PinggyTcpClientVariables)
|
|
182
|
+
], PinggyTcpClientComponent.prototype, "values", void 0);
|
|
183
|
+
PinggyTcpClientComponent = __decorate([
|
|
184
|
+
Component({
|
|
185
|
+
displayName: 'Pinggy Tcp Tunnel Client',
|
|
186
|
+
description: 'A Tcp client component which creates a tunnel using Pinggy (https://pinggy.io)',
|
|
187
|
+
tags: ['tcp', 'client', 'socket', 'connection', 'pinggy', 'tunnel'],
|
|
188
|
+
})
|
|
189
|
+
], PinggyTcpClientComponent);
|
|
190
|
+
export { PinggyTcpClientComponent };
|
|
191
|
+
/**
|
|
192
|
+
/**
|
|
193
|
+
* @namespace
|
|
194
|
+
*/
|
|
195
|
+
(function (PinggyTcpClientComponent) {
|
|
196
|
+
PinggyTcpClientComponent.Variables = PinggyTcpClientVariables;
|
|
197
|
+
})(PinggyTcpClientComponent || (PinggyTcpClientComponent = {}));
|
|
198
|
+
/**
|
|
199
|
+
* Creates and initializes a server instance on the first available port within the specified range.
|
|
200
|
+
*/
|
|
201
|
+
function createServer(port = 0, connectionListener) {
|
|
202
|
+
return new Promise((resolve, reject) => {
|
|
203
|
+
const server = net.createServer(connectionListener);
|
|
204
|
+
server.on('error', reject);
|
|
205
|
+
server.listen(port, '127.0.0.1', () => {
|
|
206
|
+
resolve(server);
|
|
207
|
+
});
|
|
208
|
+
});
|
|
209
|
+
}
|
|
@@ -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 PinggyTcpClientVariables extends IoClientBaseComponent.Variables {
|
|
8
|
+
}
|
|
9
|
+
__decorate([
|
|
10
|
+
DefineVariable({
|
|
11
|
+
label: 'Pingy Auth Token',
|
|
12
|
+
type: VariableType.Secret,
|
|
13
|
+
description: 'Pingy auth token. https://dashboard.pinggy.io/managetokens',
|
|
14
|
+
required: true,
|
|
15
|
+
}),
|
|
16
|
+
__metadata("design:type", String)
|
|
17
|
+
], PinggyTcpClientVariables.prototype, "authToken", void 0);
|
|
18
|
+
__decorate([
|
|
19
|
+
DefineVariable({
|
|
20
|
+
label: 'Local Port',
|
|
21
|
+
description: 'Local port number which pinggy 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
|
+
], PinggyTcpClientVariables.prototype, "localPort", void 0);
|
package/components.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './components/pinggy-tcp-client.component.js';
|
package/components.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './components/pinggy-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/pinggy",
|
|
3
|
+
"version": "0.4.1",
|
|
4
|
+
"description": "SyncBridge Pinggy component",
|
|
5
|
+
"author": "Panates Inc",
|
|
6
|
+
"license": "UNLICENSED",
|
|
7
|
+
"dependencies": {
|
|
8
|
+
"@pinggy/pinggy": "^0.3.2",
|
|
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
|
+
}
|