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,60 @@
1
+ import type { Duplex } from 'node:stream';
2
+ import type { Socket } from 'node:net';
3
+ import type { ConnectionState, ConnectionStatus, Endpoint, ForwardDirection } from './model';
4
+ export interface ProxiedConnectionOptions {
5
+ forwardId: string;
6
+ direction: ForwardDirection;
7
+ source: Endpoint;
8
+ destination: Endpoint;
9
+ connectionId?: string;
10
+ onStateChange?: (status: ConnectionStatus, previous: ConnectionState) => void;
11
+ /** Destroy the connection if it stays idle for this long. 0 disables. */
12
+ idleTimeoutMs?: number;
13
+ }
14
+ export declare class ProxiedConnection {
15
+ readonly connectionId: string;
16
+ readonly forwardId: string;
17
+ readonly direction: ForwardDirection;
18
+ readonly source: Endpoint;
19
+ readonly destination: Endpoint;
20
+ readonly openedAtUnixMs: number;
21
+ private state;
22
+ private closedAtUnixMs?;
23
+ private bytesToTarget;
24
+ private bytesFromTarget;
25
+ private lastError?;
26
+ private readonly onStateChange?;
27
+ private readonly idleTimeoutMs;
28
+ private inbound?;
29
+ private outbound?;
30
+ private tornDown;
31
+ constructor(options: ProxiedConnectionOptions);
32
+ getState(): ConnectionState;
33
+ isClosed(): boolean;
34
+ getStatus(): ConnectionStatus;
35
+ markOpening(): void;
36
+ /**
37
+ * Register the inbound stream before the outbound half exists.
38
+ *
39
+ * Channel open is asynchronous and can fail. Adopting the inbound stream first
40
+ * means `fail()` and `close()` always destroy it, instead of leaving the peer
41
+ * holding an open socket that will never carry data.
42
+ */
43
+ adoptInbound(inbound: Socket | Duplex): void;
44
+ /**
45
+ * Attach both halves and start bidirectional flow.
46
+ *
47
+ * `inbound` is the stream the peer connected to; `outbound` is the stream that
48
+ * reaches the destination. For a local forward that is (accepted TCP socket,
49
+ * SSH channel). For a remote forward it is (SSH channel, outgoing TCP socket).
50
+ */
51
+ attach(inbound: Socket | Duplex, outbound: Duplex): void;
52
+ /** Record a failure and tear down. Safe to call repeatedly. */
53
+ fail(error: Error, side?: 'inbound' | 'outbound' | 'channel'): void;
54
+ /** Close cleanly. Safe to call repeatedly and from either side's handlers. */
55
+ close(): void;
56
+ private teardown;
57
+ private destroyStream;
58
+ private transition;
59
+ }
60
+ //# sourceMappingURL=connection.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"connection.d.ts","sourceRoot":"","sources":["../src/connection.ts"],"names":[],"mappings":"AAkBA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAEvC,OAAO,KAAK,EACV,eAAe,EACf,gBAAgB,EAChB,QAAQ,EACR,gBAAgB,EACjB,MAAM,SAAS,CAAC;AAEjB,MAAM,WAAW,wBAAwB;IACvC,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,gBAAgB,CAAC;IAC5B,MAAM,EAAE,QAAQ,CAAC;IACjB,WAAW,EAAE,QAAQ,CAAC;IACtB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,aAAa,CAAC,EAAE,CAAC,MAAM,EAAE,gBAAgB,EAAE,QAAQ,EAAE,eAAe,KAAK,IAAI,CAAC;IAC9E,yEAAyE;IACzE,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAOD,qBAAa,iBAAiB;IAC5B,SAAgB,YAAY,EAAE,MAAM,CAAC;IACrC,SAAgB,SAAS,EAAE,MAAM,CAAC;IAClC,SAAgB,SAAS,EAAE,gBAAgB,CAAC;IAC5C,SAAgB,MAAM,EAAE,QAAQ,CAAC;IACjC,SAAgB,WAAW,EAAE,QAAQ,CAAC;IACtC,SAAgB,cAAc,EAAE,MAAM,CAAC;IAEvC,OAAO,CAAC,KAAK,CAA+B;IAC5C,OAAO,CAAC,cAAc,CAAC,CAAS;IAChC,OAAO,CAAC,aAAa,CAAK;IAC1B,OAAO,CAAC,eAAe,CAAK;IAC5B,OAAO,CAAC,SAAS,CAAC,CAAS;IAE3B,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAC,CAA4C;IAC3E,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAS;IAEvC,OAAO,CAAC,OAAO,CAAC,CAAkB;IAClC,OAAO,CAAC,QAAQ,CAAC,CAAS;IAC1B,OAAO,CAAC,QAAQ,CAAS;gBAEb,OAAO,EAAE,wBAAwB;IAWtC,QAAQ,IAAI,eAAe;IAI3B,QAAQ,IAAI,OAAO;IAInB,SAAS,IAAI,gBAAgB;IAgB7B,WAAW,IAAI,IAAI;IAI1B;;;;;;OAMG;IACI,YAAY,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI;IAQnD;;;;;;OAMG;IACI,MAAM,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI;IAgD/D,+DAA+D;IACxD,IAAI,CAAC,KAAK,EAAE,KAAK,EAAE,IAAI,GAAE,SAAS,GAAG,UAAU,GAAG,SAAqB,GAAG,IAAI;IAOrF,8EAA8E;IACvE,KAAK,IAAI,IAAI;IAMpB,OAAO,CAAC,QAAQ;IAuBhB,OAAO,CAAC,aAAa;IAUrB,OAAO,CAAC,UAAU;CASnB"}
@@ -0,0 +1,198 @@
1
+ "use strict";
2
+ /*
3
+ * connection.ts — one proxied TCP connection, with explicit state.
4
+ *
5
+ * v1 piped a socket and a channel together and hoped for the best. The failure
6
+ * modes that produced were: half-open connections that never closed, `unpipe`
7
+ * called on an already-destroyed stream, errors on one side leaving the other
8
+ * side leaking, and no way to enumerate what was currently open.
9
+ *
10
+ * This class makes each connection a first-class, observable object:
11
+ * - a single explicit state machine (accepted -> opening -> piped -> closed)
12
+ * - byte counters in both directions
13
+ * - idempotent teardown that always destroys both sides exactly once
14
+ * - errors attributed to a connection id rather than logged anonymously
15
+ *
16
+ * License: MIT
17
+ */
18
+ Object.defineProperty(exports, "__esModule", { value: true });
19
+ exports.ProxiedConnection = void 0;
20
+ const node_crypto_1 = require("node:crypto");
21
+ const TERMINAL_STATES = new Set([
22
+ 'closed',
23
+ 'failed',
24
+ ]);
25
+ class ProxiedConnection {
26
+ connectionId;
27
+ forwardId;
28
+ direction;
29
+ source;
30
+ destination;
31
+ openedAtUnixMs;
32
+ state = 'accepted';
33
+ closedAtUnixMs;
34
+ bytesToTarget = 0;
35
+ bytesFromTarget = 0;
36
+ lastError;
37
+ onStateChange;
38
+ idleTimeoutMs;
39
+ inbound;
40
+ outbound;
41
+ tornDown = false;
42
+ constructor(options) {
43
+ this.connectionId = options.connectionId ?? (0, node_crypto_1.randomUUID)();
44
+ this.forwardId = options.forwardId;
45
+ this.direction = options.direction;
46
+ this.source = options.source;
47
+ this.destination = options.destination;
48
+ this.openedAtUnixMs = Date.now();
49
+ this.onStateChange = options.onStateChange;
50
+ this.idleTimeoutMs = options.idleTimeoutMs ?? 0;
51
+ }
52
+ getState() {
53
+ return this.state;
54
+ }
55
+ isClosed() {
56
+ return TERMINAL_STATES.has(this.state);
57
+ }
58
+ getStatus() {
59
+ return {
60
+ connectionId: this.connectionId,
61
+ forwardId: this.forwardId,
62
+ direction: this.direction,
63
+ state: this.state,
64
+ source: { ...this.source },
65
+ destination: { ...this.destination },
66
+ openedAtUnixMs: this.openedAtUnixMs,
67
+ closedAtUnixMs: this.closedAtUnixMs,
68
+ bytesToTarget: this.bytesToTarget,
69
+ bytesFromTarget: this.bytesFromTarget,
70
+ error: this.lastError,
71
+ };
72
+ }
73
+ markOpening() {
74
+ this.transition('opening');
75
+ }
76
+ /**
77
+ * Register the inbound stream before the outbound half exists.
78
+ *
79
+ * Channel open is asynchronous and can fail. Adopting the inbound stream first
80
+ * means `fail()` and `close()` always destroy it, instead of leaving the peer
81
+ * holding an open socket that will never carry data.
82
+ */
83
+ adoptInbound(inbound) {
84
+ if (this.tornDown) {
85
+ this.destroyStream(inbound);
86
+ return;
87
+ }
88
+ this.inbound = inbound;
89
+ }
90
+ /**
91
+ * Attach both halves and start bidirectional flow.
92
+ *
93
+ * `inbound` is the stream the peer connected to; `outbound` is the stream that
94
+ * reaches the destination. For a local forward that is (accepted TCP socket,
95
+ * SSH channel). For a remote forward it is (SSH channel, outgoing TCP socket).
96
+ */
97
+ attach(inbound, outbound) {
98
+ if (this.tornDown) {
99
+ // A close arrived before the channel opened. Do not leak either side.
100
+ this.destroyStream(inbound);
101
+ this.destroyStream(outbound);
102
+ return;
103
+ }
104
+ this.inbound = inbound;
105
+ this.outbound = outbound;
106
+ inbound.on('data', (chunk) => {
107
+ this.bytesToTarget += chunk.length;
108
+ });
109
+ outbound.on('data', (chunk) => {
110
+ this.bytesFromTarget += chunk.length;
111
+ });
112
+ // Client finished writing: half-close the target so a response can still
113
+ // come back. Target gone (crash, RST, FIN): destroy the client socket.
114
+ // A mere inbound.end() leaves allowHalfOpen listeners parked until the
115
+ // client also FINs, which is the "socket stayed open 5000ms" failure.
116
+ inbound.on('end', () => {
117
+ this.transition('half_closed');
118
+ if (this.outbound && !this.outbound.writableEnded)
119
+ this.outbound.end();
120
+ });
121
+ outbound.on('end', () => {
122
+ this.fail(new Error('target closed'), 'outbound');
123
+ });
124
+ inbound.on('error', (err) => this.fail(err, 'inbound'));
125
+ outbound.on('error', (err) => this.fail(err, 'outbound'));
126
+ inbound.on('close', () => this.close());
127
+ outbound.on('close', () => this.close());
128
+ if (this.idleTimeoutMs > 0 && typeof inbound.setTimeout === 'function') {
129
+ inbound.setTimeout(this.idleTimeoutMs, () => {
130
+ this.fail(new Error(`idle for ${this.idleTimeoutMs}ms`), 'inbound');
131
+ });
132
+ }
133
+ inbound.pipe(outbound);
134
+ outbound.pipe(inbound);
135
+ this.transition('piped');
136
+ }
137
+ /** Record a failure and tear down. Safe to call repeatedly. */
138
+ fail(error, side = 'channel') {
139
+ if (this.isClosed())
140
+ return;
141
+ this.lastError = `${side}: ${error.message}`;
142
+ this.teardown();
143
+ this.transition('failed');
144
+ }
145
+ /** Close cleanly. Safe to call repeatedly and from either side's handlers. */
146
+ close() {
147
+ if (this.isClosed())
148
+ return;
149
+ this.teardown();
150
+ this.transition('closed');
151
+ }
152
+ teardown() {
153
+ if (this.tornDown)
154
+ return;
155
+ this.tornDown = true;
156
+ this.closedAtUnixMs = Date.now();
157
+ const { inbound, outbound } = this;
158
+ if (inbound && outbound) {
159
+ try {
160
+ inbound.unpipe(outbound);
161
+ outbound.unpipe(inbound);
162
+ }
163
+ catch {
164
+ // unpipe on an already-destroyed stream is not actionable
165
+ }
166
+ }
167
+ this.destroyStream(inbound);
168
+ this.destroyStream(outbound);
169
+ this.inbound = undefined;
170
+ this.outbound = undefined;
171
+ }
172
+ destroyStream(stream) {
173
+ if (!stream)
174
+ return;
175
+ try {
176
+ stream.removeAllListeners('data');
177
+ if (!stream.destroyed)
178
+ stream.destroy();
179
+ }
180
+ catch {
181
+ // best effort
182
+ }
183
+ }
184
+ transition(next) {
185
+ if (this.state === next)
186
+ return;
187
+ if (TERMINAL_STATES.has(this.state))
188
+ return;
189
+ // half_closed must not overwrite a later terminal state
190
+ if (next === 'half_closed' && this.state === 'accepted')
191
+ return;
192
+ const previous = this.state;
193
+ this.state = next;
194
+ this.onStateChange?.(this.getStatus(), previous);
195
+ }
196
+ }
197
+ exports.ProxiedConnection = ProxiedConnection;
198
+ //# sourceMappingURL=connection.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"connection.js","sourceRoot":"","sources":["../src/connection.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;GAeG;;;AAEH,6CAAyC;AAsBzC,MAAM,eAAe,GAAiC,IAAI,GAAG,CAAkB;IAC7E,QAAQ;IACR,QAAQ;CACT,CAAC,CAAC;AAEH,MAAa,iBAAiB;IACZ,YAAY,CAAS;IACrB,SAAS,CAAS;IAClB,SAAS,CAAmB;IAC5B,MAAM,CAAW;IACjB,WAAW,CAAW;IACtB,cAAc,CAAS;IAE/B,KAAK,GAAoB,UAAU,CAAC;IACpC,cAAc,CAAU;IACxB,aAAa,GAAG,CAAC,CAAC;IAClB,eAAe,GAAG,CAAC,CAAC;IACpB,SAAS,CAAU;IAEV,aAAa,CAA6C;IAC1D,aAAa,CAAS;IAE/B,OAAO,CAAmB;IAC1B,QAAQ,CAAU;IAClB,QAAQ,GAAG,KAAK,CAAC;IAEzB,YAAY,OAAiC;QAC3C,IAAI,CAAC,YAAY,GAAG,OAAO,CAAC,YAAY,IAAI,IAAA,wBAAU,GAAE,CAAC;QACzD,IAAI,CAAC,SAAS,GAAG,OAAO,CAAC,SAAS,CAAC;QACnC,IAAI,CAAC,SAAS,GAAG,OAAO,CAAC,SAAS,CAAC;QACnC,IAAI,CAAC,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;QAC7B,IAAI,CAAC,WAAW,GAAG,OAAO,CAAC,WAAW,CAAC;QACvC,IAAI,CAAC,cAAc,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACjC,IAAI,CAAC,aAAa,GAAG,OAAO,CAAC,aAAa,CAAC;QAC3C,IAAI,CAAC,aAAa,GAAG,OAAO,CAAC,aAAa,IAAI,CAAC,CAAC;IAClD,CAAC;IAEM,QAAQ;QACb,OAAO,IAAI,CAAC,KAAK,CAAC;IACpB,CAAC;IAEM,QAAQ;QACb,OAAO,eAAe,CAAC,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IACzC,CAAC;IAEM,SAAS;QACd,OAAO;YACL,YAAY,EAAE,IAAI,CAAC,YAAY;YAC/B,SAAS,EAAE,IAAI,CAAC,SAAS;YACzB,SAAS,EAAE,IAAI,CAAC,SAAS;YACzB,KAAK,EAAE,IAAI,CAAC,KAAK;YACjB,MAAM,EAAE,EAAE,GAAG,IAAI,CAAC,MAAM,EAAE;YAC1B,WAAW,EAAE,EAAE,GAAG,IAAI,CAAC,WAAW,EAAE;YACpC,cAAc,EAAE,IAAI,CAAC,cAAc;YACnC,cAAc,EAAE,IAAI,CAAC,cAAc;YACnC,aAAa,EAAE,IAAI,CAAC,aAAa;YACjC,eAAe,EAAE,IAAI,CAAC,eAAe;YACrC,KAAK,EAAE,IAAI,CAAC,SAAS;SACtB,CAAC;IACJ,CAAC;IAEM,WAAW;QAChB,IAAI,CAAC,UAAU,CAAC,SAAS,CAAC,CAAC;IAC7B,CAAC;IAED;;;;;;OAMG;IACI,YAAY,CAAC,OAAwB;QAC1C,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;YAClB,IAAI,CAAC,aAAa,CAAC,OAAO,CAAC,CAAC;YAC5B,OAAO;QACT,CAAC;QACD,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;IACzB,CAAC;IAED;;;;;;OAMG;IACI,MAAM,CAAC,OAAwB,EAAE,QAAgB;QACtD,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;YAClB,sEAAsE;YACtE,IAAI,CAAC,aAAa,CAAC,OAAO,CAAC,CAAC;YAC5B,IAAI,CAAC,aAAa,CAAC,QAAQ,CAAC,CAAC;YAC7B,OAAO;QACT,CAAC;QAED,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;QACvB,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;QAEzB,OAAO,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,KAAa,EAAE,EAAE;YACnC,IAAI,CAAC,aAAa,IAAI,KAAK,CAAC,MAAM,CAAC;QACrC,CAAC,CAAC,CAAC;QACH,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,KAAa,EAAE,EAAE;YACpC,IAAI,CAAC,eAAe,IAAI,KAAK,CAAC,MAAM,CAAC;QACvC,CAAC,CAAC,CAAC;QAEH,yEAAyE;QACzE,uEAAuE;QACvE,uEAAuE;QACvE,sEAAsE;QACtE,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,GAAG,EAAE;YACrB,IAAI,CAAC,UAAU,CAAC,aAAa,CAAC,CAAC;YAC/B,IAAI,IAAI,CAAC,QAAQ,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,aAAa;gBAAE,IAAI,CAAC,QAAQ,CAAC,GAAG,EAAE,CAAC;QACzE,CAAC,CAAC,CAAC;QACH,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,GAAG,EAAE;YACtB,IAAI,CAAC,IAAI,CAAC,IAAI,KAAK,CAAC,eAAe,CAAC,EAAE,UAAU,CAAC,CAAC;QACpD,CAAC,CAAC,CAAC;QAEH,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,GAAU,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,SAAS,CAAC,CAAC,CAAC;QAC/D,QAAQ,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,GAAU,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,UAAU,CAAC,CAAC,CAAC;QAEjE,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC,CAAC;QACxC,QAAQ,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC,CAAC;QAEzC,IAAI,IAAI,CAAC,aAAa,GAAG,CAAC,IAAI,OAAQ,OAAkB,CAAC,UAAU,KAAK,UAAU,EAAE,CAAC;YAClF,OAAkB,CAAC,UAAU,CAAC,IAAI,CAAC,aAAa,EAAE,GAAG,EAAE;gBACtD,IAAI,CAAC,IAAI,CAAC,IAAI,KAAK,CAAC,YAAY,IAAI,CAAC,aAAa,IAAI,CAAC,EAAE,SAAS,CAAC,CAAC;YACtE,CAAC,CAAC,CAAC;QACL,CAAC;QAED,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QACvB,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QAEvB,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC;IAC3B,CAAC;IAED,+DAA+D;IACxD,IAAI,CAAC,KAAY,EAAE,OAA2C,SAAS;QAC5E,IAAI,IAAI,CAAC,QAAQ,EAAE;YAAE,OAAO;QAC5B,IAAI,CAAC,SAAS,GAAG,GAAG,IAAI,KAAK,KAAK,CAAC,OAAO,EAAE,CAAC;QAC7C,IAAI,CAAC,QAAQ,EAAE,CAAC;QAChB,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC;IAC5B,CAAC;IAED,8EAA8E;IACvE,KAAK;QACV,IAAI,IAAI,CAAC,QAAQ,EAAE;YAAE,OAAO;QAC5B,IAAI,CAAC,QAAQ,EAAE,CAAC;QAChB,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC;IAC5B,CAAC;IAEO,QAAQ;QACd,IAAI,IAAI,CAAC,QAAQ;YAAE,OAAO;QAC1B,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC;QACrB,IAAI,CAAC,cAAc,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QAEjC,MAAM,EAAE,OAAO,EAAE,QAAQ,EAAE,GAAG,IAAI,CAAC;QAEnC,IAAI,OAAO,IAAI,QAAQ,EAAE,CAAC;YACxB,IAAI,CAAC;gBACH,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;gBACzB,QAAQ,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;YAC3B,CAAC;YAAC,MAAM,CAAC;gBACP,0DAA0D;YAC5D,CAAC;QACH,CAAC;QAED,IAAI,CAAC,aAAa,CAAC,OAAO,CAAC,CAAC;QAC5B,IAAI,CAAC,aAAa,CAAC,QAAQ,CAAC,CAAC;QAE7B,IAAI,CAAC,OAAO,GAAG,SAAS,CAAC;QACzB,IAAI,CAAC,QAAQ,GAAG,SAAS,CAAC;IAC5B,CAAC;IAEO,aAAa,CAAC,MAAmC;QACvD,IAAI,CAAC,MAAM;YAAE,OAAO;QACpB,IAAI,CAAC;YACH,MAAM,CAAC,kBAAkB,CAAC,MAAM,CAAC,CAAC;YAClC,IAAI,CAAC,MAAM,CAAC,SAAS;gBAAE,MAAM,CAAC,OAAO,EAAE,CAAC;QAC1C,CAAC;QAAC,MAAM,CAAC;YACP,cAAc;QAChB,CAAC;IACH,CAAC;IAEO,UAAU,CAAC,IAAqB;QACtC,IAAI,IAAI,CAAC,KAAK,KAAK,IAAI;YAAE,OAAO;QAChC,IAAI,eAAe,CAAC,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC;YAAE,OAAO;QAC5C,wDAAwD;QACxD,IAAI,IAAI,KAAK,aAAa,IAAI,IAAI,CAAC,KAAK,KAAK,UAAU;YAAE,OAAO;QAChE,MAAM,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC;QAC5B,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC;QAClB,IAAI,CAAC,aAAa,EAAE,CAAC,IAAI,CAAC,SAAS,EAAE,EAAE,QAAQ,CAAC,CAAC;IACnD,CAAC;CACF;AA3LD,8CA2LC"}
@@ -0,0 +1,24 @@
1
+ import type { CredentialRef } from './model';
2
+ /** Host-supplied resolver for `{ source: 'callback' }` refs. */
3
+ export type CredentialResolver = (ref: CredentialRef) => string | Buffer | Promise<string | Buffer>;
4
+ export declare function expandHome(path: string): string;
5
+ export interface ResolveOptions {
6
+ resolver?: CredentialResolver;
7
+ label?: string;
8
+ }
9
+ /**
10
+ * Resolve a credential reference. Returns `undefined` when `ref` is undefined so
11
+ * callers can pass optional fields straight through.
12
+ */
13
+ export declare function resolveCredential(ref: CredentialRef | undefined, options?: ResolveOptions): Promise<Buffer | string | undefined>;
14
+ /** Convenience constructors so callers rarely write the object literal. */
15
+ export declare const credential: {
16
+ file: (path: string) => CredentialRef;
17
+ env: (name: string) => CredentialRef;
18
+ inline: (value: string) => CredentialRef;
19
+ agent: (socketPath?: string) => CredentialRef;
20
+ callback: (name?: string) => CredentialRef;
21
+ };
22
+ /** Resolve the agent socket path for an `agent` credential ref. */
23
+ export declare function resolveAgentSocket(ref: CredentialRef | undefined): string | undefined;
24
+ //# sourceMappingURL=credentials.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"credentials.d.ts","sourceRoot":"","sources":["../src/credentials.ts"],"names":[],"mappings":"AAgBA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAE7C,gEAAgE;AAChE,MAAM,MAAM,kBAAkB,GAAG,CAAC,GAAG,EAAE,aAAa,KAAK,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,MAAM,CAAC,CAAC;AAEpG,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAI/C;AAED,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,EAAE,kBAAkB,CAAC;IAC9B,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;;GAGG;AACH,wBAAsB,iBAAiB,CACrC,GAAG,EAAE,aAAa,GAAG,SAAS,EAC9B,OAAO,GAAE,cAAmB,GAC3B,OAAO,CAAC,MAAM,GAAG,MAAM,GAAG,SAAS,CAAC,CAsEtC;AAED,2EAA2E;AAC3E,eAAO,MAAM,UAAU;iBACR,MAAM,KAAG,aAAa;gBACvB,MAAM,KAAG,aAAa;oBAClB,MAAM,KAAG,aAAa;yBACjB,MAAM,KAAG,aAAa;sBACzB,MAAM,KAAG,aAAa;CACzC,CAAC;AAEF,mEAAmE;AACnE,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,aAAa,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CAGrF"}
@@ -0,0 +1,102 @@
1
+ "use strict";
2
+ /*
3
+ * credentials.ts — resolve a CredentialRef to bytes at connect time.
4
+ *
5
+ * This replaces the v1 keychain integration. The package no longer generates,
6
+ * stores, or derives key material: it reads a key the operator already manages
7
+ * with ssh-keygen, an agent, or a secret manager. `sshpk` and `keytar` are gone
8
+ * from the dependency list as a result.
9
+ *
10
+ * License: MIT
11
+ */
12
+ Object.defineProperty(exports, "__esModule", { value: true });
13
+ exports.credential = void 0;
14
+ exports.expandHome = expandHome;
15
+ exports.resolveCredential = resolveCredential;
16
+ exports.resolveAgentSocket = resolveAgentSocket;
17
+ const node_fs_1 = require("node:fs");
18
+ const node_os_1 = require("node:os");
19
+ const node_path_1 = require("node:path");
20
+ const errors_1 = require("./errors");
21
+ function expandHome(path) {
22
+ if (path === '~')
23
+ return (0, node_os_1.homedir)();
24
+ if (path.startsWith('~/'))
25
+ return (0, node_path_1.join)((0, node_os_1.homedir)(), path.slice(2));
26
+ return path;
27
+ }
28
+ /**
29
+ * Resolve a credential reference. Returns `undefined` when `ref` is undefined so
30
+ * callers can pass optional fields straight through.
31
+ */
32
+ async function resolveCredential(ref, options = {}) {
33
+ if (!ref)
34
+ return undefined;
35
+ const label = options.label ?? 'credential';
36
+ switch (ref.source) {
37
+ case 'inline': {
38
+ if (!ref.value) {
39
+ throw new errors_1.TunnelError('CREDENTIAL_UNRESOLVED', `Inline ${label} has no value`);
40
+ }
41
+ return ref.value;
42
+ }
43
+ case 'env': {
44
+ if (!ref.value) {
45
+ throw new errors_1.TunnelError('CREDENTIAL_UNRESOLVED', `Env ${label} has no variable name`);
46
+ }
47
+ const value = process.env[ref.value];
48
+ if (value === undefined || value.length === 0) {
49
+ throw new errors_1.TunnelError('CREDENTIAL_UNRESOLVED', `Environment variable ${ref.value} for ${label} is unset or empty`);
50
+ }
51
+ return value;
52
+ }
53
+ case 'file': {
54
+ if (!ref.value) {
55
+ throw new errors_1.TunnelError('CREDENTIAL_UNRESOLVED', `File ${label} has no path`);
56
+ }
57
+ const path = expandHome(ref.value);
58
+ if (!(0, node_path_1.isAbsolute)(path)) {
59
+ throw new errors_1.TunnelError('CREDENTIAL_UNRESOLVED', `Path for ${label} must be absolute or start with "~": ${ref.value}`);
60
+ }
61
+ try {
62
+ return (0, node_fs_1.readFileSync)(path);
63
+ }
64
+ catch (cause) {
65
+ throw (0, errors_1.asTunnelError)('CREDENTIAL_UNRESOLVED', `Cannot read ${label} from ${path}`, cause);
66
+ }
67
+ }
68
+ case 'agent': {
69
+ // The agent path is handled by the transport, not read as bytes.
70
+ return undefined;
71
+ }
72
+ case 'callback': {
73
+ if (!options.resolver) {
74
+ throw new errors_1.TunnelError('CREDENTIAL_UNRESOLVED', `${label} uses source "callback" but no credentialResolver was supplied`);
75
+ }
76
+ try {
77
+ return await options.resolver(ref);
78
+ }
79
+ catch (cause) {
80
+ throw (0, errors_1.asTunnelError)('CREDENTIAL_UNRESOLVED', `Resolver failed for ${label}`, cause);
81
+ }
82
+ }
83
+ default: {
84
+ throw new errors_1.TunnelError('CREDENTIAL_UNRESOLVED', `Unsupported credential source ${JSON.stringify(ref.source)}`);
85
+ }
86
+ }
87
+ }
88
+ /** Convenience constructors so callers rarely write the object literal. */
89
+ exports.credential = {
90
+ file: (path) => ({ source: 'file', value: path }),
91
+ env: (name) => ({ source: 'env', value: name }),
92
+ inline: (value) => ({ source: 'inline', value }),
93
+ agent: (socketPath) => ({ source: 'agent', value: socketPath }),
94
+ callback: (name) => ({ source: 'callback', value: name }),
95
+ };
96
+ /** Resolve the agent socket path for an `agent` credential ref. */
97
+ function resolveAgentSocket(ref) {
98
+ if (!ref || ref.source !== 'agent')
99
+ return undefined;
100
+ return ref.value ?? process.env.SSH_AUTH_SOCK;
101
+ }
102
+ //# sourceMappingURL=credentials.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"credentials.js","sourceRoot":"","sources":["../src/credentials.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,19 @@
1
+ export declare const TUNNEL_ERROR_CODES: readonly ["INVALID_FORWARD_SPEC", "PORT_NOT_PERMITTED", "HOST_NOT_PERMITTED", "TRANSPORT_NOT_READY", "AUTH_FAILED", "LISTEN_FAILED", "REMOTE_BIND_FAILED", "CHANNEL_OPEN_FAILED", "TARGET_UNREACHABLE", "DUPLICATE_FORWARD_ID", "UNKNOWN_FORWARD", "CONNECTION_LIMIT", "RECONNECT_EXHAUSTED", "CREDENTIAL_UNRESOLVED"];
2
+ export type TunnelErrorCode = (typeof TUNNEL_ERROR_CODES)[number];
3
+ export interface TunnelErrorDetails {
4
+ forwardId?: string;
5
+ connectionId?: string;
6
+ cause?: unknown;
7
+ }
8
+ export declare class TunnelError extends Error {
9
+ readonly code: TunnelErrorCode;
10
+ readonly forwardId?: string;
11
+ readonly connectionId?: string;
12
+ readonly cause?: unknown;
13
+ constructor(code: TunnelErrorCode, message: string, details?: TunnelErrorDetails);
14
+ toJSON(): Record<string, unknown>;
15
+ }
16
+ export declare function isTunnelError(value: unknown): value is TunnelError;
17
+ /** Wrap an unknown thrown value in a TunnelError without losing the original. */
18
+ export declare function asTunnelError(code: TunnelErrorCode, message: string, cause: unknown, details?: Omit<TunnelErrorDetails, 'cause'>): TunnelError;
19
+ //# sourceMappingURL=errors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAUA,eAAO,MAAM,kBAAkB,wTAerB,CAAC;AAEX,MAAM,MAAM,eAAe,GAAG,CAAC,OAAO,kBAAkB,CAAC,CAAC,MAAM,CAAC,CAAC;AAElE,MAAM,WAAW,kBAAkB;IACjC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED,qBAAa,WAAY,SAAQ,KAAK;IACpC,SAAgB,IAAI,EAAE,eAAe,CAAC;IACtC,SAAgB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnC,SAAgB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtC,SAAyB,KAAK,CAAC,EAAE,OAAO,CAAC;gBAE7B,IAAI,EAAE,eAAe,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,GAAE,kBAAuB;IAU7E,MAAM,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;CASzC;AAED,wBAAgB,aAAa,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,WAAW,CAElE;AAED,iFAAiF;AACjF,wBAAgB,aAAa,CAC3B,IAAI,EAAE,eAAe,EACrB,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,OAAO,EACd,OAAO,GAAE,IAAI,CAAC,kBAAkB,EAAE,OAAO,CAAM,GAC9C,WAAW,CAGb"}
@@ -0,0 +1,64 @@
1
+ "use strict";
2
+ /*
3
+ * errors.ts — stable error taxonomy.
4
+ *
5
+ * Every failure surfaced by this package carries a machine-readable `code` that
6
+ * mirrors sshtunnel.v1.TunnelEvent.ErrorRaised.Code. Callers branch on the code,
7
+ * never on the message text.
8
+ *
9
+ * License: MIT
10
+ */
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.TunnelError = exports.TUNNEL_ERROR_CODES = void 0;
13
+ exports.isTunnelError = isTunnelError;
14
+ exports.asTunnelError = asTunnelError;
15
+ exports.TUNNEL_ERROR_CODES = [
16
+ 'INVALID_FORWARD_SPEC',
17
+ 'PORT_NOT_PERMITTED',
18
+ 'HOST_NOT_PERMITTED',
19
+ 'TRANSPORT_NOT_READY',
20
+ 'AUTH_FAILED',
21
+ 'LISTEN_FAILED',
22
+ 'REMOTE_BIND_FAILED',
23
+ 'CHANNEL_OPEN_FAILED',
24
+ 'TARGET_UNREACHABLE',
25
+ 'DUPLICATE_FORWARD_ID',
26
+ 'UNKNOWN_FORWARD',
27
+ 'CONNECTION_LIMIT',
28
+ 'RECONNECT_EXHAUSTED',
29
+ 'CREDENTIAL_UNRESOLVED',
30
+ ];
31
+ class TunnelError extends Error {
32
+ code;
33
+ forwardId;
34
+ connectionId;
35
+ cause;
36
+ constructor(code, message, details = {}) {
37
+ super(message);
38
+ this.name = 'TunnelError';
39
+ this.code = code;
40
+ this.forwardId = details.forwardId;
41
+ this.connectionId = details.connectionId;
42
+ this.cause = details.cause;
43
+ Object.setPrototypeOf(this, TunnelError.prototype);
44
+ }
45
+ toJSON() {
46
+ return {
47
+ name: this.name,
48
+ code: this.code,
49
+ message: this.message,
50
+ forwardId: this.forwardId,
51
+ connectionId: this.connectionId,
52
+ };
53
+ }
54
+ }
55
+ exports.TunnelError = TunnelError;
56
+ function isTunnelError(value) {
57
+ return value instanceof TunnelError;
58
+ }
59
+ /** Wrap an unknown thrown value in a TunnelError without losing the original. */
60
+ function asTunnelError(code, message, cause, details = {}) {
61
+ const suffix = cause instanceof Error ? `: ${cause.message}` : cause ? `: ${String(cause)}` : '';
62
+ return new TunnelError(code, `${message}${suffix}`, { ...details, cause });
63
+ }
64
+ //# sourceMappingURL=errors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":";AAAA;;;;;;;;GAQG;;;AAsDH,sCAEC;AAGD,sCAQC;AAjEY,QAAA,kBAAkB,GAAG;IAChC,sBAAsB;IACtB,oBAAoB;IACpB,oBAAoB;IACpB,qBAAqB;IACrB,aAAa;IACb,eAAe;IACf,oBAAoB;IACpB,qBAAqB;IACrB,oBAAoB;IACpB,sBAAsB;IACtB,iBAAiB;IACjB,kBAAkB;IAClB,qBAAqB;IACrB,uBAAuB;CACf,CAAC;AAUX,MAAa,WAAY,SAAQ,KAAK;IACpB,IAAI,CAAkB;IACtB,SAAS,CAAU;IACnB,YAAY,CAAU;IACb,KAAK,CAAW;IAEzC,YAAY,IAAqB,EAAE,OAAe,EAAE,UAA8B,EAAE;QAClF,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,aAAa,CAAC;QAC1B,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACjB,IAAI,CAAC,SAAS,GAAG,OAAO,CAAC,SAAS,CAAC;QACnC,IAAI,CAAC,YAAY,GAAG,OAAO,CAAC,YAAY,CAAC;QACzC,IAAI,CAAC,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC;QAC3B,MAAM,CAAC,cAAc,CAAC,IAAI,EAAE,WAAW,CAAC,SAAS,CAAC,CAAC;IACrD,CAAC;IAEM,MAAM;QACX,OAAO;YACL,IAAI,EAAE,IAAI,CAAC,IAAI;YACf,IAAI,EAAE,IAAI,CAAC,IAAI;YACf,OAAO,EAAE,IAAI,CAAC,OAAO;YACrB,SAAS,EAAE,IAAI,CAAC,SAAS;YACzB,YAAY,EAAE,IAAI,CAAC,YAAY;SAChC,CAAC;IACJ,CAAC;CACF;AAzBD,kCAyBC;AAED,SAAgB,aAAa,CAAC,KAAc;IAC1C,OAAO,KAAK,YAAY,WAAW,CAAC;AACtC,CAAC;AAED,iFAAiF;AACjF,SAAgB,aAAa,CAC3B,IAAqB,EACrB,OAAe,EACf,KAAc,EACd,UAA6C,EAAE;IAE/C,MAAM,MAAM,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;IACjG,OAAO,IAAI,WAAW,CAAC,IAAI,EAAE,GAAG,OAAO,GAAG,MAAM,EAAE,EAAE,EAAE,GAAG,OAAO,EAAE,KAAK,EAAE,CAAC,CAAC;AAC7E,CAAC"}
@@ -0,0 +1,34 @@
1
+ import type { LocalForward, PortPolicy, RemoteForward } from './model';
2
+ export declare const DEFAULT_LISTEN_ADDRESS = "127.0.0.1";
3
+ export declare const DEFAULT_REMOTE_BIND_ADDRESS = "localhost";
4
+ /**
5
+ * Parse a local-forward string into a structured {@link LocalForward}.
6
+ * Syntax only — call {@link validateLocalForward} to apply a {@link PortPolicy}.
7
+ */
8
+ export declare function parseLocalForwardSpec(spec: string): LocalForward;
9
+ /**
10
+ * Parse a remote-forward string into a structured {@link RemoteForward}.
11
+ *
12
+ * Field mapping, matching v1 behaviour:
13
+ * 3 fields: bindPort : targetHost : targetPort, bind address defaults to
14
+ * "localhost" on the peer.
15
+ * 4 fields: bindAddress : bindPort : targetHost : targetPort.
16
+ *
17
+ * A bind port of 0 asks the SSH peer to assign one; the assigned value is
18
+ * reported in ForwardStatus.assignedPort.
19
+ */
20
+ export declare function parseRemoteForwardSpec(spec: string): RemoteForward;
21
+ /**
22
+ * Apply a {@link PortPolicy} to a structured local forward and return a
23
+ * normalized copy. Throws a {@link TunnelError} on the first violation.
24
+ */
25
+ export declare function validateLocalForward(forward: LocalForward, policy?: PortPolicy): Required<Pick<LocalForward, 'id' | 'listen' | 'target'>> & LocalForward;
26
+ /** Apply a {@link PortPolicy} to a structured remote forward. */
27
+ export declare function validateRemoteForward(forward: RemoteForward, policy?: PortPolicy): Required<Pick<RemoteForward, 'id' | 'bind' | 'target'>> & RemoteForward;
28
+ /** Parse and validate in one step. */
29
+ export declare function parseAndValidateLocalForward(spec: string, policy?: PortPolicy): LocalForward;
30
+ /** Parse and validate in one step. */
31
+ export declare function parseAndValidateRemoteForward(spec: string, policy?: PortPolicy): RemoteForward;
32
+ export declare function describeLocalForward(forward: LocalForward): string;
33
+ export declare function describeRemoteForward(forward: RemoteForward): string;
34
+ //# sourceMappingURL=forward-spec.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"forward-spec.d.ts","sourceRoot":"","sources":["../src/forward-spec.ts"],"names":[],"mappings":"AAuBA,OAAO,KAAK,EAAE,YAAY,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAGvE,eAAO,MAAM,sBAAsB,cAAc,CAAC;AAClD,eAAO,MAAM,2BAA2B,cAAc,CAAC;AAmCvD;;;GAGG;AACH,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,MAAM,GAAG,YAAY,CA+BhE;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,MAAM,GAAG,aAAa,CAiClE;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,YAAY,EACrB,MAAM,GAAE,UAAe,GACtB,QAAQ,CAAC,IAAI,CAAC,YAAY,EAAE,IAAI,GAAG,QAAQ,GAAG,QAAQ,CAAC,CAAC,GAAG,YAAY,CAuBzE;AAED,iEAAiE;AACjE,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,aAAa,EACtB,MAAM,GAAE,UAAe,GACtB,QAAQ,CAAC,IAAI,CAAC,aAAa,EAAE,IAAI,GAAG,MAAM,GAAG,QAAQ,CAAC,CAAC,GAAG,aAAa,CAezE;AAED,sCAAsC;AACtC,wBAAgB,4BAA4B,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,GAAE,UAAe,GAAG,YAAY,CAEhG;AAED,sCAAsC;AACtC,wBAAgB,6BAA6B,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,GAAE,UAAe,GAAG,aAAa,CAElG;AAED,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,YAAY,GAAG,MAAM,CAGlE;AAED,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,aAAa,GAAG,MAAM,CAGpE"}