@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 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,8 @@
1
+ import { IoClientBaseComponent } from '@syncbridge/builtins';
2
+ /**
3
+ * Variables
4
+ */
5
+ export declare class PinggyTcpClientVariables extends IoClientBaseComponent.Variables {
6
+ authToken: string;
7
+ localPort: number;
8
+ }
@@ -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);
@@ -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
@@ -0,0 +1,4 @@
1
+ import { AuthorMetadata } from '@syncbridge/common';
2
+ export declare const version = "1";
3
+ export declare const noOp: () => undefined;
4
+ export declare const panatesAuthor: AuthorMetadata;
package/constants.js ADDED
@@ -0,0 +1,7 @@
1
+ export const version = '0.4.1';
2
+ export const noOp = () => undefined;
3
+ export const panatesAuthor = {
4
+ name: 'Panates Technology AS',
5
+ email: 'info@panates.com',
6
+ url: 'https://www.panates.com',
7
+ };
package/index.d.ts ADDED
@@ -0,0 +1,5 @@
1
+ import * as components from './components.js';
2
+ export { components };
3
+ export * from './components.js';
4
+ declare const cfg: import("@syncbridge/common").IExtensionPackage;
5
+ export default cfg;
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
+ }