ssh_tunnel_proxy 2.1.1 → 3.0.0
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 +19 -3
- package/MIGRATION.md +227 -0
- package/README.md +360 -212
- package/build/bridge.d.ts +14 -0
- package/build/bridge.d.ts.map +1 -0
- package/build/bridge.js +12 -0
- package/build/bridge.js.map +1 -0
- package/build/connection.d.ts +60 -0
- package/build/connection.d.ts.map +1 -0
- package/build/connection.js +198 -0
- package/build/connection.js.map +1 -0
- package/build/credentials.d.ts +24 -0
- package/build/credentials.d.ts.map +1 -0
- package/build/credentials.js +102 -0
- package/build/credentials.js.map +1 -0
- package/build/errors.d.ts +19 -0
- package/build/errors.d.ts.map +1 -0
- package/build/errors.js +64 -0
- package/build/errors.js.map +1 -0
- package/build/forward-spec.d.ts +34 -0
- package/build/forward-spec.d.ts.map +1 -0
- package/build/forward-spec.js +175 -0
- package/build/forward-spec.js.map +1 -0
- package/build/index.d.ts +19 -0
- package/build/index.d.ts.map +1 -0
- package/build/index.js +71 -0
- package/build/index.js.map +1 -0
- package/build/legacy.d.ts +70 -0
- package/build/legacy.d.ts.map +1 -0
- package/build/legacy.js +188 -0
- package/build/legacy.js.map +1 -0
- package/build/local-forward.d.ts +31 -0
- package/build/local-forward.d.ts.map +1 -0
- package/build/local-forward.js +231 -0
- package/build/local-forward.js.map +1 -0
- package/build/model.d.ts +126 -0
- package/build/model.d.ts.map +1 -0
- package/build/model.js +55 -0
- package/build/model.js.map +1 -0
- package/build/port-policy.d.ts +43 -0
- package/build/port-policy.d.ts.map +1 -0
- package/build/port-policy.js +138 -0
- package/build/port-policy.js.map +1 -0
- package/build/remote-forward.d.ts +40 -0
- package/build/remote-forward.d.ts.map +1 -0
- package/build/remote-forward.js +226 -0
- package/build/remote-forward.js.map +1 -0
- package/build/tunnel.d.ts +121 -0
- package/build/tunnel.d.ts.map +1 -0
- package/build/tunnel.js +599 -0
- package/build/tunnel.js.map +1 -0
- package/examples/config.example.json +86 -0
- package/examples/setup_ssh.sh +12 -0
- package/examples/tunnel-cli.js +461 -0
- package/examples/tunnel-worker.js +98 -0
- package/examples/zrok-termux.ts +147 -0
- package/package.json +43 -53
- package/proto/sshtunnel/v1/tunnel.proto +343 -0
- package/dist/TestMessage.d.ts +0 -13
- package/dist/TestMessage.js +0 -32
- package/dist/TestMessage.js.map +0 -1
- package/dist/cjs/TestMessage.d.ts +0 -13
- package/dist/cjs/TestMessage.js +0 -32
- package/dist/cjs/TestMessage.js.map +0 -1
- package/dist/cjs/index.d.ts +0 -73
- package/dist/cjs/index.js +0 -621
- package/dist/cjs/index.js.map +0 -1
- package/dist/cjs/keypair_storage.d.ts +0 -12
- package/dist/cjs/keypair_storage.js +0 -143
- package/dist/cjs/keypair_storage.js.map +0 -1
- package/dist/cjs/ngrok_service.d.ts +0 -11
- package/dist/cjs/ngrok_service.js +0 -40
- package/dist/cjs/ngrok_service.js.map +0 -1
- package/dist/cjs/ssh2-node.d.ts +0 -1
- package/dist/cjs/ssh2-node.js +0 -211
- package/dist/cjs/ssh2-node.js.map +0 -1
- package/dist/esm/TestMessage.d.ts +0 -13
- package/dist/esm/TestMessage.js +0 -29
- package/dist/esm/TestMessage.js.map +0 -1
- package/dist/esm/index.d.ts +0 -73
- package/dist/esm/index.js +0 -616
- package/dist/esm/index.js.map +0 -1
- package/dist/esm/keypair_storage.d.ts +0 -12
- package/dist/esm/keypair_storage.js +0 -117
- package/dist/esm/keypair_storage.js.map +0 -1
- package/dist/esm/ngrok_service.d.ts +0 -11
- package/dist/esm/ngrok_service.js +0 -37
- package/dist/esm/ngrok_service.js.map +0 -1
- package/dist/esm/ssh2-node.d.ts +0 -1
- package/dist/esm/ssh2-node.js +0 -184
- package/dist/esm/ssh2-node.js.map +0 -1
- package/dist/index.d.ts +0 -73
- package/dist/index.js +0 -620
- package/dist/index.js.map +0 -1
- package/dist/keypair_storage.d.ts +0 -12
- package/dist/keypair_storage.js +0 -120
- package/dist/keypair_storage.js.map +0 -1
- package/dist/ngrok_service.d.ts +0 -11
- package/dist/ngrok_service.js +0 -40
- package/dist/ngrok_service.js.map +0 -1
- package/dist/ssh2-node.d.ts +0 -1
- package/dist/ssh2-node.js +0 -188
- package/dist/ssh2-node.js.map +0 -1
- package/dist/types/TestMessage.d.ts +0 -13
- package/dist/types/index.d.ts +0 -73
- package/dist/types/keypair_storage.d.ts +0 -12
- package/dist/types/ngrok_service.d.ts +0 -11
- package/dist/types/ssh2-node.d.ts +0 -1
- 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":"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"}
|
|
@@ -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"}
|
package/build/errors.js
ADDED
|
@@ -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"}
|