ssh_tunnel_proxy 2.1.3 → 3.0.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.
Files changed (109) hide show
  1. package/LICENSE +19 -3
  2. package/MIGRATION.md +227 -0
  3. package/README.md +360 -212
  4. package/build/bridge.d.ts +14 -0
  5. package/build/bridge.d.ts.map +1 -0
  6. package/build/bridge.js +12 -0
  7. package/build/bridge.js.map +1 -0
  8. package/build/connection.d.ts +60 -0
  9. package/build/connection.d.ts.map +1 -0
  10. package/build/connection.js +198 -0
  11. package/build/connection.js.map +1 -0
  12. package/build/credentials.d.ts +24 -0
  13. package/build/credentials.d.ts.map +1 -0
  14. package/build/credentials.js +102 -0
  15. package/build/credentials.js.map +1 -0
  16. package/build/errors.d.ts +19 -0
  17. package/build/errors.d.ts.map +1 -0
  18. package/build/errors.js +64 -0
  19. package/build/errors.js.map +1 -0
  20. package/build/forward-spec.d.ts +34 -0
  21. package/build/forward-spec.d.ts.map +1 -0
  22. package/build/forward-spec.js +175 -0
  23. package/build/forward-spec.js.map +1 -0
  24. package/build/index.d.ts +19 -0
  25. package/build/index.d.ts.map +1 -0
  26. package/build/index.js +71 -0
  27. package/build/index.js.map +1 -0
  28. package/build/legacy.d.ts +70 -0
  29. package/build/legacy.d.ts.map +1 -0
  30. package/build/legacy.js +188 -0
  31. package/build/legacy.js.map +1 -0
  32. package/build/local-forward.d.ts +31 -0
  33. package/build/local-forward.d.ts.map +1 -0
  34. package/build/local-forward.js +231 -0
  35. package/build/local-forward.js.map +1 -0
  36. package/build/model.d.ts +126 -0
  37. package/build/model.d.ts.map +1 -0
  38. package/build/model.js +55 -0
  39. package/build/model.js.map +1 -0
  40. package/build/port-policy.d.ts +43 -0
  41. package/build/port-policy.d.ts.map +1 -0
  42. package/build/port-policy.js +138 -0
  43. package/build/port-policy.js.map +1 -0
  44. package/build/remote-forward.d.ts +40 -0
  45. package/build/remote-forward.d.ts.map +1 -0
  46. package/build/remote-forward.js +226 -0
  47. package/build/remote-forward.js.map +1 -0
  48. package/build/tunnel.d.ts +121 -0
  49. package/build/tunnel.d.ts.map +1 -0
  50. package/build/tunnel.js +599 -0
  51. package/build/tunnel.js.map +1 -0
  52. package/examples/config.example.json +86 -0
  53. package/examples/setup_ssh.sh +12 -0
  54. package/examples/tunnel-cli.js +461 -0
  55. package/examples/tunnel-worker.js +98 -0
  56. package/examples/zrok-termux.ts +147 -0
  57. package/package.json +43 -53
  58. package/proto/sshtunnel/v1/tunnel.proto +343 -0
  59. package/dist/TestMessage.d.ts +0 -13
  60. package/dist/TestMessage.js +0 -32
  61. package/dist/TestMessage.js.map +0 -1
  62. package/dist/cjs/TestMessage.d.ts +0 -13
  63. package/dist/cjs/TestMessage.js +0 -32
  64. package/dist/cjs/TestMessage.js.map +0 -1
  65. package/dist/cjs/index.d.ts +0 -73
  66. package/dist/cjs/index.js +0 -621
  67. package/dist/cjs/index.js.map +0 -1
  68. package/dist/cjs/keypair_storage.d.ts +0 -12
  69. package/dist/cjs/keypair_storage.js +0 -143
  70. package/dist/cjs/keypair_storage.js.map +0 -1
  71. package/dist/cjs/ngrok_service.d.ts +0 -11
  72. package/dist/cjs/ngrok_service.js +0 -40
  73. package/dist/cjs/ngrok_service.js.map +0 -1
  74. package/dist/cjs/ssh2-node.d.ts +0 -1
  75. package/dist/cjs/ssh2-node.js +0 -221
  76. package/dist/cjs/ssh2-node.js.map +0 -1
  77. package/dist/esm/TestMessage.d.ts +0 -13
  78. package/dist/esm/TestMessage.js +0 -29
  79. package/dist/esm/TestMessage.js.map +0 -1
  80. package/dist/esm/index.d.ts +0 -73
  81. package/dist/esm/index.js +0 -616
  82. package/dist/esm/index.js.map +0 -1
  83. package/dist/esm/keypair_storage.d.ts +0 -12
  84. package/dist/esm/keypair_storage.js +0 -117
  85. package/dist/esm/keypair_storage.js.map +0 -1
  86. package/dist/esm/ngrok_service.d.ts +0 -11
  87. package/dist/esm/ngrok_service.js +0 -37
  88. package/dist/esm/ngrok_service.js.map +0 -1
  89. package/dist/esm/ssh2-node.d.ts +0 -1
  90. package/dist/esm/ssh2-node.js +0 -194
  91. package/dist/esm/ssh2-node.js.map +0 -1
  92. package/dist/index.d.ts +0 -73
  93. package/dist/index.js +0 -620
  94. package/dist/index.js.map +0 -1
  95. package/dist/keypair_storage.d.ts +0 -12
  96. package/dist/keypair_storage.js +0 -120
  97. package/dist/keypair_storage.js.map +0 -1
  98. package/dist/ngrok_service.d.ts +0 -11
  99. package/dist/ngrok_service.js +0 -40
  100. package/dist/ngrok_service.js.map +0 -1
  101. package/dist/ssh2-node.d.ts +0 -1
  102. package/dist/ssh2-node.js +0 -188
  103. package/dist/ssh2-node.js.map +0 -1
  104. package/dist/types/TestMessage.d.ts +0 -13
  105. package/dist/types/index.d.ts +0 -73
  106. package/dist/types/keypair_storage.d.ts +0 -12
  107. package/dist/types/ngrok_service.d.ts +0 -11
  108. package/dist/types/ssh2-node.d.ts +0 -1
  109. package/ssh2-node +0 -2
@@ -0,0 +1,147 @@
1
+ /*
2
+ * examples/zrok-termux.ts
3
+ *
4
+ * The topology this refactor was built against:
5
+ *
6
+ * workstation phone (Termux + proot)
7
+ * ─────────── ──────────────────────
8
+ * browser -> 127.0.0.1:8280 ─┐
9
+ * ssh -> 127.0.0.1:8122 ─┤
10
+ * │ SSH over a private zrok TCP share
11
+ * └─ 127.0.0.1:9191 ══════════════> sshd on 127.0.0.1:8022
12
+ * ├─ 127.0.0.1:8080 (web app in proot)
13
+ * └─ 192.168.1.1:22 (LAN router)
14
+ *
15
+ * Bring the relay up first — this library never creates it:
16
+ *
17
+ * phone: zrok share private --backend-mode tcpTunnel 127.0.0.1:8022
18
+ * workstation: zrok access private --bind 127.0.0.1:9191 <share-token>
19
+ * workstation: nc 127.0.0.1 9191 # must print SSH-2.0-... before running this
20
+ *
21
+ * Then: npx tsc && node build-examples/zrok-termux.js
22
+ *
23
+ * License: MIT
24
+ */
25
+
26
+ import { SSHTunnel, credential, isTunnelError } from '../src/index';
27
+
28
+ async function main(): Promise<void> {
29
+ const tunnel = new SSHTunnel(
30
+ {
31
+ id: 'phone',
32
+ transport: {
33
+ endpoint: { host: '127.0.0.1', port: 9191 },
34
+ username: 'user',
35
+ privateKey: credential.file('~/.ssh/id_ed25519'),
36
+ // Documentation only. The library treats every path as a plain address.
37
+ reachability: 'zrok',
38
+ keepaliveIntervalMs: 10_000,
39
+ },
40
+ localForwards: [
41
+ {
42
+ id: 'app',
43
+ listen: { host: '127.0.0.1', port: 8280 },
44
+ target: { host: '127.0.0.1', port: 8080 },
45
+ },
46
+ {
47
+ id: 'router-ssh',
48
+ listen: { host: '127.0.0.1', port: 8122 },
49
+ target: { host: '192.168.1.1', port: 22 },
50
+ },
51
+ ],
52
+ portPolicy: {
53
+ // 192.168.1.1:22 is privileged, so it has to be named explicitly.
54
+ allowedPrivilegedPorts: [22],
55
+ allowedLocalListenPorts: [8280, 8122],
56
+ allowedRemoteHosts: ['127.0.0.1', '192.168.1.1'],
57
+ },
58
+ reconnect: {
59
+ enabled: true,
60
+ initialDelayMs: 1_000,
61
+ maxDelayMs: 30_000,
62
+ maxAttempts: 0, // retry forever; the phone's link comes and goes
63
+ },
64
+ },
65
+ { connectionIdleTimeoutMs: 0, debugSsh: false },
66
+ );
67
+
68
+ tunnel.on('ready', (status) => {
69
+ console.log(`tunnel ready via ${status.endpoint.host}:${status.endpoint.port}`);
70
+ for (const forward of status.forwards) {
71
+ console.log(
72
+ ` ${forward.direction === 'local' ? 'L' : 'R'} ` +
73
+ `${forward.listen.host}:${forward.assignedPort ?? forward.listen.port}` +
74
+ ` -> ${forward.target.host}:${forward.target.port} [${forward.state}]`,
75
+ );
76
+ }
77
+ });
78
+
79
+ tunnel.on('forward', ({ status, previous }) => {
80
+ if (status.state !== previous) {
81
+ console.log(`forward ${status.forwardId}: ${previous} -> ${status.state}`);
82
+ }
83
+ });
84
+
85
+ tunnel.on('reconnect', ({ attempt, delayMs, cause }) => {
86
+ console.warn(`link lost (${cause ?? 'unknown'}); retry ${attempt} in ${delayMs}ms`);
87
+ });
88
+
89
+ tunnel.on('error', (err) => {
90
+ console.error(`[${err.code}] ${err.message}`);
91
+ if (err.code === 'TARGET_UNREACHABLE') {
92
+ console.error(' is the service actually running on the phone side?');
93
+ }
94
+ });
95
+
96
+ tunnel.on('close', () => console.log('tunnel closed'));
97
+
98
+ try {
99
+ await tunnel.connect();
100
+ } catch (err) {
101
+ if (isTunnelError(err) && err.code === 'AUTH_FAILED') {
102
+ console.error('check that ~/.ssh/id_ed25519.pub is in the phone\'s authorized_keys');
103
+ }
104
+ throw err;
105
+ }
106
+
107
+ const app = tunnel.resolveEndpoint('app');
108
+ console.log(`open http://${app.endpoint.host}:${app.endpoint.port}`);
109
+
110
+ // Confirm the far side is the host we think it is.
111
+ const { stdout } = await tunnel.exec('id -un; uname -m');
112
+ console.log(`peer identity: ${stdout.trim().replace(/\n/g, ' ')}`);
113
+
114
+ // A reverse forward: expose a service running here to the phone.
115
+ const reverse = await tunnel.addRemoteForward({
116
+ id: 'workstation-api',
117
+ bind: { host: '127.0.0.1', port: 0 }, // let the phone pick the port
118
+ target: { host: '127.0.0.1', port: 3000 },
119
+ });
120
+ console.log(`phone can reach this host's :3000 at 127.0.0.1:${reverse.assignedPort}`);
121
+
122
+ // Periodic accounting, which v1 could not report at all.
123
+ const reporter = setInterval(() => {
124
+ const connections = tunnel.listConnections();
125
+ if (connections.length === 0) return;
126
+ console.log(`${connections.length} open connection(s):`);
127
+ for (const connection of connections) {
128
+ console.log(
129
+ ` ${connection.forwardId} ${connection.state} ` +
130
+ `↑${connection.bytesToTarget}B ↓${connection.bytesFromTarget}B`,
131
+ );
132
+ }
133
+ }, 30_000);
134
+
135
+ const shutdown = async () => {
136
+ clearInterval(reporter);
137
+ await tunnel.close();
138
+ process.exit(0);
139
+ };
140
+ process.on('SIGINT', shutdown);
141
+ process.on('SIGTERM', shutdown);
142
+ }
143
+
144
+ main().catch((err) => {
145
+ console.error(err);
146
+ process.exit(1);
147
+ });
package/package.json CHANGED
@@ -1,63 +1,53 @@
1
1
  {
2
2
  "name": "ssh_tunnel_proxy",
3
- "author": "Autonomous",
4
- "license": "MIT",
5
- "version": "2.1.3",
6
- "description": "setup ssh tunnel with local and remote port forwarding, node ssh cli interface, use ngrok to obtain host:port",
7
- "main": "dist/cjs/index.js",
8
- "module": "dist/esm/index.js",
9
- "types": "typings/index.d.ts",
10
- "scripts": {
11
- "build": "npm run build:cjs && npm run build:esm && npm run build:types && npm run build:testbench",
12
- "build:cjs": "node tools/cleanup cjs && tsc -p config/tsconfig.cjs.json",
13
- "build:esm": "node tools/cleanup esm && tsc -p config/tsconfig.esm.json",
14
- "build:types": "node tools/cleanup types && tsc -p config/tsconfig.types.json",
15
- "build:testbench": "tsc -p config/tsconfig.testbench.json",
16
- "lint": "eslint \"src/*.ts\"",
17
- "prepare": "npm run build",
18
- "prepublishOnly": "npm test && npm run lint",
19
- "preversion": "npm run lint",
20
- "version": "git add -A src",
21
- "postversion": "git push && git push --tags",
22
- "test": "mocha --exit"
23
- },
24
- "repository": {
25
- "type": "git",
26
- "url": "git+https://github.com/autonomous1/ssh_tunnel_proxy"
27
- },
3
+ "version": "3.0.1",
4
+ "description": "Programmatic local and reverse TCP port forwarding over SSH for Node.js, built for outbound-only and constrained-device deployments.",
5
+ "main": "build/index.js",
6
+ "types": "build/index.d.ts",
28
7
  "files": [
29
- "dist/**/*",
30
- "ssh2-node"
8
+ "build",
9
+ "proto",
10
+ "examples",
11
+ "README.md",
12
+ "MIGRATION.md",
13
+ "LICENSE"
31
14
  ],
32
- "bin": {
33
- "ssh2-node": "./ssh2-node"
15
+ "engines": {
16
+ "node": ">=18"
34
17
  },
18
+ "scripts": {
19
+ "clean": "rm -rf build",
20
+ "build": "tsc -p tsconfig.json",
21
+ "prepublishOnly": "npm run clean && npm run build",
22
+ "test": "npm run build && mocha --timeout 20000 test/**/*.test.js",
23
+ "test:unit": "npm run build && mocha --timeout 5000 test/unit/*.test.js",
24
+ "test:integration": "npm run build && mocha --timeout 20000 test/integration/*.test.js"
25
+ },
26
+ "keywords": [
27
+ "ssh",
28
+ "ssh2",
29
+ "tunnel",
30
+ "ssh-tunnel",
31
+ "reverse-tunnel",
32
+ "reverse-ssh",
33
+ "port-forwarding",
34
+ "tcp-proxy",
35
+ "nat-traversal",
36
+ "zrok",
37
+ "termux",
38
+ "iot",
39
+ "edge",
40
+ "nodejs"
41
+ ],
42
+ "author": "Autonomous",
43
+ "license": "MIT",
35
44
  "dependencies": {
36
- "@ngrok/ngrok-api": "^0.9.0",
37
- "bluebird": "^3.7.2",
38
- "commander": "^10.0.0",
39
- "electron-ssh2": "^0.1.2",
40
- "form-data": "^4.0.0",
41
- "fs": "^0.0.1-security",
42
- "keytar": "^7.9.0",
43
- "net": "^1.0.2",
44
- "os": "^0.1.2",
45
- "split": "^1.0.1",
46
- "sshpk": "^1.17.0",
47
- "through": "^2.3.8",
48
- "through2": "^4.0.2"
45
+ "ssh2": "^1.15.0"
49
46
  },
50
47
  "devDependencies": {
51
- "@types/node": "^18.13.0",
52
- "@typescript-eslint/eslint-plugin": "^5.52.0",
53
- "@typescript-eslint/parser": "^5.52.0",
54
- "eslint": "^8.34.0",
55
- "eslint-config-standard-with-typescript": "^34.0.0",
56
- "eslint-plugin-import": "^2.27.5",
57
- "eslint-plugin-n": "^15.6.1",
58
- "eslint-plugin-promise": "^6.1.1",
59
- "mocha": "^10.2.0",
60
- "sinon": "^15.0.1",
61
- "typescript": "^4.9.5"
48
+ "@types/node": "^20.11.0",
49
+ "@types/ssh2": "^1.15.0",
50
+ "mocha": "^12.0.0",
51
+ "typescript": "^5.4.0"
62
52
  }
63
53
  }
@@ -0,0 +1,343 @@
1
+ // sshtunnel/v1/tunnel.proto
2
+ //
3
+ // Language-neutral data model for programmatic SSH TCP port forwarding.
4
+ //
5
+ // Design rules for this file:
6
+ // 1. It describes CONFIGURATION, STATE and EVENTS only. It never describes
7
+ // implementation mechanics (sockets, channels, event emitters, promises).
8
+ // 2. It contains no credential material. Secrets are referenced indirectly
9
+ // through a CredentialRef so that a graph/config file can be committed,
10
+ // diffed and shared without leaking keys.
11
+ // 3. It is the source of truth shared by the TypeScript runtime, a future
12
+ // Python runtime, and any control-plane API placed in front of them.
13
+ //
14
+ // License: MIT
15
+
16
+ syntax = "proto3";
17
+
18
+ package sshtunnel.v1;
19
+
20
+ option go_package = "github.com/autonomous/ssh_tunnel_proxy/gen/sshtunnel/v1;sshtunnelv1";
21
+ option java_multiple_files = true;
22
+ option java_package = "org.sshtunnel.v1";
23
+
24
+ // ---------------------------------------------------------------------------
25
+ // Primitives
26
+ // ---------------------------------------------------------------------------
27
+
28
+ // A TCP endpoint. `host` is an IP literal or DNS name; it is never a URL.
29
+ message Endpoint {
30
+ string host = 1;
31
+ uint32 port = 2; // 1..65535
32
+ }
33
+
34
+ // Indirect reference to secret material. The runtime resolves the reference at
35
+ // connect time; the model itself never carries the secret.
36
+ message CredentialRef {
37
+ enum Source {
38
+ SOURCE_UNSPECIFIED = 0;
39
+ SOURCE_FILE = 1; // value = filesystem path, "~" expanded by runtime
40
+ SOURCE_ENV = 2; // value = environment variable name
41
+ SOURCE_INLINE = 3; // value = literal (test fixtures only)
42
+ SOURCE_AGENT = 4; // value = agent socket path, or empty for SSH_AUTH_SOCK
43
+ SOURCE_CALLBACK = 5; // value = host-defined resolver name
44
+ }
45
+ Source source = 1;
46
+ string value = 2;
47
+ }
48
+
49
+ // ---------------------------------------------------------------------------
50
+ // Port policy
51
+ // ---------------------------------------------------------------------------
52
+
53
+ // Port authorization is an explicit caller policy, not a library surprise.
54
+ // Local listen ports and remote target ports are separate decisions because
55
+ // they protect different things: local binding rights vs. what the SSH peer is
56
+ // permitted to reach.
57
+ message PortPolicy {
58
+ // Ports below 1024 are rejected unless listed here.
59
+ repeated uint32 allowed_privileged_ports = 1;
60
+
61
+ // If non-empty, a local listener may only bind one of these ports.
62
+ repeated uint32 allowed_local_listen_ports = 2;
63
+
64
+ // If non-empty, a forward may only target one of these remote ports.
65
+ repeated uint32 allowed_remote_target_ports = 3;
66
+
67
+ // If non-empty, a forward may only target one of these hosts, as written.
68
+ repeated string allowed_remote_hosts = 4;
69
+ }
70
+
71
+ // ---------------------------------------------------------------------------
72
+ // Forwards
73
+ // ---------------------------------------------------------------------------
74
+
75
+ // ssh -L : listen on this machine, deliver to a host reachable from the SSH peer.
76
+ message LocalForward {
77
+ string id = 1; // stable, caller-chosen; used in events and lookups
78
+ Endpoint listen = 2; // bound on the machine running this runtime
79
+ Endpoint target = 3; // resolved by the SSH peer
80
+ bool exclusive = 4; // fail instead of reusing an existing listener
81
+ uint32 max_connections = 5; // 0 = unlimited
82
+ }
83
+
84
+ // ssh -R : the SSH peer listens, connections are delivered to a host reachable
85
+ // from this machine.
86
+ message RemoteForward {
87
+ string id = 1;
88
+ Endpoint bind = 2; // bound on the SSH peer; port 0 = peer assigns
89
+ Endpoint target = 3; // resolved by the machine running this runtime
90
+ uint32 max_connections = 4; // 0 = unlimited
91
+ }
92
+
93
+ // ---------------------------------------------------------------------------
94
+ // Transport
95
+ // ---------------------------------------------------------------------------
96
+
97
+ // How to reach the SSH listener. This model deliberately assumes an SSH
98
+ // listener already exists at `endpoint` — it never provisions a relay. A
99
+ // reachability provider such as zrok, ngrok or a VPS is an external concern
100
+ // recorded only as documentation metadata.
101
+ message SshTransport {
102
+ Endpoint endpoint = 1;
103
+ string username = 2;
104
+ CredentialRef private_key = 3;
105
+ CredentialRef passphrase = 4;
106
+ CredentialRef password = 5;
107
+
108
+ uint32 keepalive_interval_ms = 6; // default 10000
109
+ uint32 keepalive_count_max = 7; // default 3
110
+ uint32 ready_timeout_ms = 8; // default 20000
111
+ repeated string host_key_fingerprints = 9; // sha256:... ; empty = accept any
112
+
113
+ enum Reachability {
114
+ REACHABILITY_UNSPECIFIED = 0;
115
+ REACHABILITY_DIRECT = 1;
116
+ REACHABILITY_ZROK = 2;
117
+ REACHABILITY_NGROK = 3;
118
+ REACHABILITY_CUSTOM = 4;
119
+ }
120
+ Reachability reachability = 10; // documentation only; no behavioral effect
121
+ }
122
+
123
+ message ReconnectPolicy {
124
+ bool enabled = 1; // default true
125
+ uint32 max_attempts = 2; // 0 = unlimited
126
+ uint32 initial_delay_ms = 3; // default 1000
127
+ uint32 max_delay_ms = 4; // default 30000
128
+ double backoff_multiplier = 5; // default 2.0
129
+ double jitter_ratio = 6; // 0.0..1.0, default 0.2
130
+ }
131
+
132
+ // The complete, portable definition of one tunnel.
133
+ message TunnelConfig {
134
+ string id = 1;
135
+ string label = 2;
136
+ SshTransport transport = 3;
137
+ PortPolicy port_policy = 4;
138
+ ReconnectPolicy reconnect = 5;
139
+ repeated LocalForward local_forwards = 6;
140
+ repeated RemoteForward remote_forwards = 7;
141
+ map<string, string> metadata = 8;
142
+ }
143
+
144
+ // ---------------------------------------------------------------------------
145
+ // State
146
+ // ---------------------------------------------------------------------------
147
+
148
+ enum TunnelState {
149
+ TUNNEL_STATE_UNSPECIFIED = 0;
150
+ TUNNEL_STATE_IDLE = 1;
151
+ TUNNEL_STATE_CONNECTING = 2;
152
+ TUNNEL_STATE_READY = 3;
153
+ TUNNEL_STATE_RECONNECTING = 4;
154
+ TUNNEL_STATE_CLOSING = 5;
155
+ TUNNEL_STATE_CLOSED = 6;
156
+ TUNNEL_STATE_FAILED = 7;
157
+ }
158
+
159
+ enum ForwardState {
160
+ FORWARD_STATE_UNSPECIFIED = 0;
161
+ FORWARD_STATE_PENDING = 1;
162
+ FORWARD_STATE_OPENING = 2;
163
+ FORWARD_STATE_ACTIVE = 3;
164
+ FORWARD_STATE_DEGRADED = 4; // registered, but transport is down
165
+ FORWARD_STATE_CLOSING = 5;
166
+ FORWARD_STATE_CLOSED = 6;
167
+ FORWARD_STATE_FAILED = 7;
168
+ }
169
+
170
+ // Lifecycle of a single proxied TCP connection carried by a forward.
171
+ enum ConnectionState {
172
+ CONNECTION_STATE_UNSPECIFIED = 0;
173
+ CONNECTION_STATE_ACCEPTED = 1; // inbound socket accepted, no channel yet
174
+ CONNECTION_STATE_OPENING = 2; // SSH channel being opened
175
+ CONNECTION_STATE_PIPED = 3; // bidirectional flow established
176
+ CONNECTION_STATE_HALF_CLOSED = 4;
177
+ CONNECTION_STATE_CLOSED = 5;
178
+ CONNECTION_STATE_FAILED = 6;
179
+ }
180
+
181
+ enum ForwardDirection {
182
+ FORWARD_DIRECTION_UNSPECIFIED = 0;
183
+ FORWARD_DIRECTION_LOCAL = 1; // ssh -L
184
+ FORWARD_DIRECTION_REMOTE = 2; // ssh -R
185
+ }
186
+
187
+ message ConnectionStatus {
188
+ string connection_id = 1;
189
+ string forward_id = 2;
190
+ ForwardDirection direction = 3;
191
+ ConnectionState state = 4;
192
+ Endpoint source = 5;
193
+ Endpoint destination = 6;
194
+ int64 opened_at_unix_ms = 7;
195
+ int64 closed_at_unix_ms = 8;
196
+ uint64 bytes_to_target = 9;
197
+ uint64 bytes_from_target = 10;
198
+ string error = 11;
199
+ }
200
+
201
+ message ForwardStatus {
202
+ string forward_id = 1;
203
+ ForwardDirection direction = 2;
204
+ ForwardState state = 3;
205
+ Endpoint listen = 4; // for remote forwards, the peer-side bind
206
+ Endpoint target = 5;
207
+ uint32 assigned_port = 6; // resolved port when requested port was 0
208
+ uint32 active_connections = 7;
209
+ uint64 total_connections = 8;
210
+ string error = 9;
211
+ }
212
+
213
+ message TunnelStatus {
214
+ string tunnel_id = 1;
215
+ TunnelState state = 2;
216
+ Endpoint endpoint = 3;
217
+ uint32 reconnect_attempt = 4;
218
+ int64 connected_at_unix_ms = 5;
219
+ repeated ForwardStatus forwards = 6;
220
+ string error = 7;
221
+ }
222
+
223
+ // ---------------------------------------------------------------------------
224
+ // Events
225
+ // ---------------------------------------------------------------------------
226
+
227
+ message TunnelEvent {
228
+ int64 timestamp_unix_ms = 1;
229
+ string tunnel_id = 2;
230
+
231
+ oneof payload {
232
+ StateChanged state_changed = 10;
233
+ ForwardChanged forward_changed = 11;
234
+ ConnectionChanged connection_changed = 12;
235
+ ReconnectScheduled reconnect_scheduled = 13;
236
+ ErrorRaised error_raised = 14;
237
+ }
238
+
239
+ message StateChanged {
240
+ TunnelState previous = 1;
241
+ TunnelState current = 2;
242
+ string reason = 3;
243
+ }
244
+
245
+ message ForwardChanged {
246
+ ForwardStatus status = 1;
247
+ ForwardState previous = 2;
248
+ }
249
+
250
+ message ConnectionChanged {
251
+ ConnectionStatus status = 1;
252
+ ConnectionState previous = 2;
253
+ }
254
+
255
+ message ReconnectScheduled {
256
+ uint32 attempt = 1;
257
+ uint32 delay_ms = 2;
258
+ string cause = 3;
259
+ }
260
+
261
+ message ErrorRaised {
262
+ // Mirrors the runtime error taxonomy so non-TypeScript consumers can
263
+ // branch on a stable code rather than on a message string.
264
+ enum Code {
265
+ CODE_UNSPECIFIED = 0;
266
+ CODE_INVALID_FORWARD_SPEC = 1;
267
+ CODE_PORT_NOT_PERMITTED = 2;
268
+ CODE_HOST_NOT_PERMITTED = 3;
269
+ CODE_TRANSPORT_NOT_READY = 4;
270
+ CODE_AUTH_FAILED = 5;
271
+ CODE_LISTEN_FAILED = 6;
272
+ CODE_REMOTE_BIND_FAILED = 7;
273
+ CODE_CHANNEL_OPEN_FAILED = 8;
274
+ CODE_TARGET_UNREACHABLE = 9;
275
+ CODE_DUPLICATE_FORWARD_ID = 10;
276
+ CODE_UNKNOWN_FORWARD = 11;
277
+ CODE_CONNECTION_LIMIT = 12;
278
+ CODE_RECONNECT_EXHAUSTED = 13;
279
+ CODE_CREDENTIAL_UNRESOLVED = 14;
280
+ }
281
+ Code code = 1;
282
+ string message = 2;
283
+ string forward_id = 3;
284
+ string connection_id = 4;
285
+ }
286
+ }
287
+
288
+ // ---------------------------------------------------------------------------
289
+ // Optional control plane
290
+ // ---------------------------------------------------------------------------
291
+ //
292
+ // Implementing this service is NOT required to use the library in-process. It
293
+ // exists so that a non-Node runtime (for example a Python agent-flow runtime)
294
+ // can drive a Node tunnel manager over a loopback control API and resolve a
295
+ // named forward to a concrete local endpoint.
296
+
297
+ message OpenTunnelRequest { TunnelConfig config = 1; }
298
+ message OpenTunnelResponse { TunnelStatus status = 1; }
299
+
300
+ message CloseTunnelRequest { string tunnel_id = 1; }
301
+ message CloseTunnelResponse { TunnelStatus status = 1; }
302
+
303
+ message GetTunnelStatusRequest { string tunnel_id = 1; }
304
+ message GetTunnelStatusResponse { TunnelStatus status = 1; }
305
+
306
+ message OpenLocalForwardRequest {
307
+ string tunnel_id = 1;
308
+ LocalForward forward = 2;
309
+ }
310
+ message OpenRemoteForwardRequest {
311
+ string tunnel_id = 1;
312
+ RemoteForward forward = 2;
313
+ }
314
+ message OpenForwardResponse { ForwardStatus status = 1; }
315
+
316
+ message CloseForwardRequest {
317
+ string tunnel_id = 1;
318
+ string forward_id = 2;
319
+ }
320
+ message CloseForwardResponse { ForwardStatus status = 1; }
321
+
322
+ // Resolve a semantic forward id to the loopback endpoint a caller should use.
323
+ message ResolveEndpointRequest {
324
+ string tunnel_id = 1;
325
+ string forward_id = 2;
326
+ }
327
+ message ResolveEndpointResponse {
328
+ Endpoint endpoint = 1;
329
+ ForwardState state = 2;
330
+ }
331
+
332
+ message WatchEventsRequest { string tunnel_id = 1; }
333
+
334
+ service TunnelControl {
335
+ rpc OpenTunnel(OpenTunnelRequest) returns (OpenTunnelResponse);
336
+ rpc CloseTunnel(CloseTunnelRequest) returns (CloseTunnelResponse);
337
+ rpc GetTunnelStatus(GetTunnelStatusRequest) returns (GetTunnelStatusResponse);
338
+ rpc OpenLocalForward(OpenLocalForwardRequest) returns (OpenForwardResponse);
339
+ rpc OpenRemoteForward(OpenRemoteForwardRequest) returns (OpenForwardResponse);
340
+ rpc CloseForward(CloseForwardRequest) returns (CloseForwardResponse);
341
+ rpc ResolveEndpoint(ResolveEndpointRequest) returns (ResolveEndpointResponse);
342
+ rpc WatchEvents(WatchEventsRequest) returns (stream TunnelEvent);
343
+ }
@@ -1,13 +0,0 @@
1
- export declare class TestMessage {
2
- generateMessage(to: string, from: string, action: string, contents: string): {
3
- envelope: {
4
- to: string;
5
- from: string;
6
- timestamp: Date;
7
- action: string;
8
- };
9
- contents: string;
10
- };
11
- signMessage(msg: string, key: any): any;
12
- verifyMessage(msg: string, signature: any, key: any): any;
13
- }
@@ -1,32 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.TestMessage = void 0;
4
- var crypto_1 = require("crypto");
5
- var TestMessage = (function () {
6
- function TestMessage() {
7
- }
8
- TestMessage.prototype.generateMessage = function (to, from, action, contents) {
9
- return {
10
- envelope: {
11
- to: to,
12
- from: from,
13
- timestamp: new Date(),
14
- action: action
15
- },
16
- contents: (contents) ? contents : (0, crypto_1.randomUUID)()
17
- };
18
- };
19
- TestMessage.prototype.signMessage = function (msg, key) {
20
- var s = key.createSign('sha1');
21
- s.update(msg);
22
- return s.sign();
23
- };
24
- TestMessage.prototype.verifyMessage = function (msg, signature, key) {
25
- var v = key.createVerify('sha1');
26
- v.update(msg);
27
- return v.verify(signature);
28
- };
29
- return TestMessage;
30
- }());
31
- exports.TestMessage = TestMessage;
32
- //# sourceMappingURL=TestMessage.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"TestMessage.js","sourceRoot":"","sources":["../src/TestMessage.ts"],"names":[],"mappings":";;;AAAA,iCAAoC;AAEpC;IAAA;IAyBA,CAAC;IAvBU,qCAAe,GAAtB,UAAuB,EAAU,EAAE,IAAY,EAAE,MAAc,EAAE,QAAe;QAC5E,OAAO;YACH,QAAQ,EAAE;gBACN,EAAE,EAAE,EAAE;gBACN,IAAI,EAAE,IAAI;gBACV,SAAS,EAAE,IAAI,IAAI,EAAE;gBACrB,MAAM,EAAE,MAAM;aACjB;YACD,QAAQ,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAA,mBAAU,GAAE;SACjD,CAAA;IACL,CAAC;IAEM,iCAAW,GAAlB,UAAmB,GAAU,EAAE,GAAG;QAC9B,IAAM,CAAC,GAAG,GAAG,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC;QACjC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QACd,OAAO,CAAC,CAAC,IAAI,EAAE,CAAC;IACpB,CAAC;IAEM,mCAAa,GAApB,UAAqB,GAAU,EAAE,SAAS,EAAE,GAAG;QAC3C,IAAM,CAAC,GAAG,GAAG,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC;QACnC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QACd,OAAO,CAAC,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;IAC/B,CAAC;IACL,kBAAC;AAAD,CAAC,AAzBD,IAyBC;AAzBY,kCAAW"}
@@ -1,13 +0,0 @@
1
- export declare class TestMessage {
2
- generateMessage(to: string, from: string, action: string, contents: string): {
3
- envelope: {
4
- to: string;
5
- from: string;
6
- timestamp: Date;
7
- action: string;
8
- };
9
- contents: string;
10
- };
11
- signMessage(msg: string, key: any): any;
12
- verifyMessage(msg: string, signature: any, key: any): any;
13
- }