sshclient-wasm 0.1.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.md +28 -0
- package/README.md +1139 -0
- package/dist/aws-iot-tunnel.d.ts +96 -0
- package/dist/aws-iot-tunnel.d.ts.map +1 -0
- package/dist/index.cjs.js +2 -0
- package/dist/index.cjs.js.map +1 -0
- package/dist/index.d.ts +92 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.esm.js +1934 -0
- package/dist/index.esm.js.map +1 -0
- package/dist/next.cjs.js +2 -0
- package/dist/next.cjs.js.map +1 -0
- package/dist/next.d.ts +75 -0
- package/dist/next.d.ts.map +1 -0
- package/dist/next.esm.js +101 -0
- package/dist/next.esm.js.map +1 -0
- package/dist/react.cjs.js +2 -0
- package/dist/react.cjs.js.map +1 -0
- package/dist/react.d.ts +52 -0
- package/dist/react.d.ts.map +1 -0
- package/dist/react.esm.js +121 -0
- package/dist/react.esm.js.map +1 -0
- package/dist/transport.d.ts +64 -0
- package/dist/transport.d.ts.map +1 -0
- package/dist/vite.cjs.js +2 -0
- package/dist/vite.cjs.js.map +1 -0
- package/dist/vite.d.ts +100 -0
- package/dist/vite.d.ts.map +1 -0
- package/dist/vite.esm.js +146 -0
- package/dist/vite.esm.js.map +1 -0
- package/dist/wasm_exec.js +575 -0
- package/package.json +81 -0
package/README.md
ADDED
|
@@ -0,0 +1,1139 @@
|
|
|
1
|
+
# sshclient-wasm
|
|
2
|
+
|
|
3
|
+
WebAssembly-based SSH client for the browser with transport-agnostic architecture. Built with Go's `golang.org/x/crypto/ssh` package and compiled to WASM for browser usage.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- 🔐 SSH client running entirely in the browser via WebAssembly
|
|
8
|
+
- 🔌 Transport-agnostic architecture supporting multiple protocols
|
|
9
|
+
- 🌐 Built-in support for WebSocket and AWS IoT Secure Tunneling
|
|
10
|
+
- 📦 Packet-level send/receive hooks for monitoring and transformation
|
|
11
|
+
- 🔄 Support for custom packet transformations (e.g., Protobuf encoding)
|
|
12
|
+
- 🔑 Password and private key authentication
|
|
13
|
+
- 📘 TypeScript support with full type definitions
|
|
14
|
+
- 🚀 ES Module compatible for modern frontend frameworks
|
|
15
|
+
|
|
16
|
+
## Architecture
|
|
17
|
+
|
|
18
|
+
```mermaid
|
|
19
|
+
graph TB
|
|
20
|
+
subgraph "Browser Environment"
|
|
21
|
+
A["ES Module<br/>(TypeScript)"]
|
|
22
|
+
B["Transport Translation Layer"]
|
|
23
|
+
C["WASM Layer<br/>(Go SSH Client)"]
|
|
24
|
+
|
|
25
|
+
A --> B
|
|
26
|
+
B <--> C
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
subgraph "Transport Implementations"
|
|
30
|
+
D["WebSocket Transport"]
|
|
31
|
+
E["AWS IoT Secure Tunnel<br/>Transport"]
|
|
32
|
+
F["Custom Transport<br/>(User-defined)"]
|
|
33
|
+
|
|
34
|
+
B --> D
|
|
35
|
+
B --> E
|
|
36
|
+
B --> F
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
subgraph "Network Layer"
|
|
40
|
+
G["Direct WebSocket<br/>Connection"]
|
|
41
|
+
H["AWS IoT<br/>Secure Tunneling"]
|
|
42
|
+
I["Custom Protocol<br/>Endpoint"]
|
|
43
|
+
|
|
44
|
+
D <--> G
|
|
45
|
+
E <--> H
|
|
46
|
+
F <--> I
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
subgraph "Destination"
|
|
50
|
+
J["SSH Server"]
|
|
51
|
+
|
|
52
|
+
G --> J
|
|
53
|
+
H --> J
|
|
54
|
+
I --> J
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
style A fill:#0277bd,color:#fff
|
|
58
|
+
style B fill:#7b1fa2,color:#fff
|
|
59
|
+
style C fill:#ef6c00,color:#fff
|
|
60
|
+
style D fill:#388e3c,color:#fff
|
|
61
|
+
style E fill:#388e3c,color:#fff
|
|
62
|
+
style F fill:#388e3c,color:#fff
|
|
63
|
+
style G fill:#d32f2f,color:#fff
|
|
64
|
+
style H fill:#d32f2f,color:#fff
|
|
65
|
+
style I fill:#d32f2f,color:#fff
|
|
66
|
+
style J fill:#424242,color:#fff
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## Installation
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
npm install sshclient-wasm
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## Quick Start
|
|
76
|
+
|
|
77
|
+
### Installation & Setup
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
npm install sshclient-wasm
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
**Copy WASM files to your public directory:**
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
# Copy these files to your public/ directory:
|
|
87
|
+
# - sshclient.wasm
|
|
88
|
+
# - wasm_exec.js
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
### Framework-Specific Imports (Recommended)
|
|
92
|
+
|
|
93
|
+
```javascript
|
|
94
|
+
// Next.js optimized
|
|
95
|
+
import { initializeSSHClient, SSHClient, NextJSConfig } from "sshclient-wasm/next";
|
|
96
|
+
|
|
97
|
+
// Vite optimized
|
|
98
|
+
import { initializeSSHClient, SSHClient, ViteConfig } from "sshclient-wasm/vite";
|
|
99
|
+
|
|
100
|
+
// React hooks and utilities
|
|
101
|
+
import { useSSHClient, SSHClient } from "sshclient-wasm/react";
|
|
102
|
+
|
|
103
|
+
// Generic/universal import
|
|
104
|
+
import { SSHClient } from "sshclient-wasm";
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### Quick Initialization
|
|
108
|
+
|
|
109
|
+
```javascript
|
|
110
|
+
// Framework-specific (auto-optimized)
|
|
111
|
+
import { initializeSSHClient } from "sshclient-wasm/next";
|
|
112
|
+
await initializeSSHClient();
|
|
113
|
+
|
|
114
|
+
// Or generic with auto-detection
|
|
115
|
+
import { SSHClient } from "sshclient-wasm";
|
|
116
|
+
await SSHClient.initialize();
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
## Usage Examples
|
|
120
|
+
|
|
121
|
+
### Basic WebSocket Connection
|
|
122
|
+
|
|
123
|
+
```javascript
|
|
124
|
+
import { SSHClient, WebSocketTransport } from "sshclient-wasm";
|
|
125
|
+
|
|
126
|
+
// Simple initialization - auto-detects assets in public directory
|
|
127
|
+
await SSHClient.initialize();
|
|
128
|
+
|
|
129
|
+
// Create a WebSocket transport
|
|
130
|
+
const transport = new WebSocketTransport(
|
|
131
|
+
"transport-1",
|
|
132
|
+
"wss://ssh-gateway.example.com",
|
|
133
|
+
["ssh"]
|
|
134
|
+
);
|
|
135
|
+
|
|
136
|
+
// Connect to SSH server
|
|
137
|
+
const session = await SSHClient.connect(
|
|
138
|
+
{
|
|
139
|
+
host: "example.com",
|
|
140
|
+
port: 22,
|
|
141
|
+
user: "username",
|
|
142
|
+
password: "password",
|
|
143
|
+
},
|
|
144
|
+
transport
|
|
145
|
+
);
|
|
146
|
+
|
|
147
|
+
// Send data
|
|
148
|
+
await session.send(new Uint8Array([0x01, 0x02, 0x03]));
|
|
149
|
+
|
|
150
|
+
// Disconnect
|
|
151
|
+
await session.disconnect();
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
### Next.js Example
|
|
155
|
+
|
|
156
|
+
```javascript
|
|
157
|
+
import { initializeSSHClient, NextJSConfig } from "sshclient-wasm/next";
|
|
158
|
+
import { useEffect, useState } from "react";
|
|
159
|
+
|
|
160
|
+
function SSHComponent() {
|
|
161
|
+
const [isReady, setIsReady] = useState(false);
|
|
162
|
+
|
|
163
|
+
useEffect(() => {
|
|
164
|
+
// Initialize with Next.js optimizations
|
|
165
|
+
initializeSSHClient()
|
|
166
|
+
.then(() => setIsReady(true))
|
|
167
|
+
.catch((error) => {
|
|
168
|
+
console.error("Failed to initialize SSH client:", error);
|
|
169
|
+
// Error messages will guide you to fix asset placement
|
|
170
|
+
});
|
|
171
|
+
}, []);
|
|
172
|
+
|
|
173
|
+
if (!isReady) return <div>Loading SSH client...</div>;
|
|
174
|
+
|
|
175
|
+
// Your SSH logic here
|
|
176
|
+
return <div>SSH client ready!</div>;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// Alternative: Use the React hook
|
|
180
|
+
import { useSSHClient } from "sshclient-wasm/react";
|
|
181
|
+
|
|
182
|
+
function SSHComponentWithHook() {
|
|
183
|
+
const { isInitialized, initError, isLoading } = useSSHClient();
|
|
184
|
+
|
|
185
|
+
if (isLoading) return <div>Loading SSH client...</div>;
|
|
186
|
+
if (initError) return <div>Error: {initError.message}</div>;
|
|
187
|
+
if (!isInitialized) return <div>SSH client not ready</div>;
|
|
188
|
+
|
|
189
|
+
return <div>SSH client ready!</div>;
|
|
190
|
+
}
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
### Vite/React Example
|
|
194
|
+
|
|
195
|
+
```javascript
|
|
196
|
+
import { initializeSSHClient, ViteConfig } from "sshclient-wasm/vite";
|
|
197
|
+
import { useSSHConnection } from "sshclient-wasm/react";
|
|
198
|
+
|
|
199
|
+
function ViteSSHComponent() {
|
|
200
|
+
const { connect, disconnect, session, connectionState } = useSSHConnection();
|
|
201
|
+
|
|
202
|
+
useEffect(() => {
|
|
203
|
+
initializeSSHClient();
|
|
204
|
+
}, []);
|
|
205
|
+
|
|
206
|
+
const handleConnect = async () => {
|
|
207
|
+
const transport = new WebSocketTransport("transport-1", "wss://example.com");
|
|
208
|
+
await connect({
|
|
209
|
+
host: "example.com",
|
|
210
|
+
port: 22,
|
|
211
|
+
user: "username",
|
|
212
|
+
password: "password"
|
|
213
|
+
}, transport);
|
|
214
|
+
};
|
|
215
|
+
|
|
216
|
+
return (
|
|
217
|
+
<div>
|
|
218
|
+
<p>Status: {connectionState}</p>
|
|
219
|
+
<button onClick={handleConnect}>Connect SSH</button>
|
|
220
|
+
<button onClick={disconnect}>Disconnect</button>
|
|
221
|
+
</div>
|
|
222
|
+
);
|
|
223
|
+
}
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
### AWS IoT Secure Tunnel Connection
|
|
227
|
+
|
|
228
|
+
```javascript
|
|
229
|
+
import { SSHClient, SecureTunnelTransport } from "sshclient-wasm";
|
|
230
|
+
|
|
231
|
+
// Auto-initialize
|
|
232
|
+
await SSHClient.initialize();
|
|
233
|
+
|
|
234
|
+
// Create AWS IoT Secure Tunnel transport
|
|
235
|
+
const transport = new SecureTunnelTransport("tunnel-1", {
|
|
236
|
+
region: "us-east-1",
|
|
237
|
+
accessToken: "your-tunnel-access-token",
|
|
238
|
+
clientMode: "source",
|
|
239
|
+
serviceId: "SSH",
|
|
240
|
+
protocol: "V3",
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
// Connect through the secure tunnel
|
|
244
|
+
const session = await SSHClient.connect(
|
|
245
|
+
{
|
|
246
|
+
host: "internal-server",
|
|
247
|
+
port: 22,
|
|
248
|
+
user: "username",
|
|
249
|
+
privateKey: "ssh-private-key",
|
|
250
|
+
},
|
|
251
|
+
transport
|
|
252
|
+
);
|
|
253
|
+
|
|
254
|
+
// Use the SSH session
|
|
255
|
+
await session.send(encoder.encode("ls -la\n"));
|
|
256
|
+
|
|
257
|
+
// Disconnect
|
|
258
|
+
await session.disconnect();
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
### With Packet Hooks
|
|
262
|
+
|
|
263
|
+
```javascript
|
|
264
|
+
const transport = new WebSocketTransport("transport-1", "wss://example.com");
|
|
265
|
+
|
|
266
|
+
const session = await SSHClient.connect(
|
|
267
|
+
{
|
|
268
|
+
host: "example.com",
|
|
269
|
+
port: 22,
|
|
270
|
+
user: "username",
|
|
271
|
+
password: "password",
|
|
272
|
+
},
|
|
273
|
+
transport,
|
|
274
|
+
{
|
|
275
|
+
onPacketSend: (data, metadata) => {
|
|
276
|
+
console.log("Sending packet:", data, metadata);
|
|
277
|
+
},
|
|
278
|
+
onPacketReceive: (data, metadata) => {
|
|
279
|
+
console.log("Received packet:", data, metadata);
|
|
280
|
+
},
|
|
281
|
+
onStateChange: (state) => {
|
|
282
|
+
console.log("Connection state:", state);
|
|
283
|
+
},
|
|
284
|
+
}
|
|
285
|
+
);
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
### Packet Transformation
|
|
289
|
+
|
|
290
|
+
```javascript
|
|
291
|
+
import { PacketTransformer } from "sshclient-wasm";
|
|
292
|
+
|
|
293
|
+
// Transform to/from Base64
|
|
294
|
+
const base64 = PacketTransformer.toBase64(data);
|
|
295
|
+
const binary = PacketTransformer.fromBase64(base64);
|
|
296
|
+
|
|
297
|
+
// Custom Protobuf transformation (implement your own logic)
|
|
298
|
+
const protobuf = PacketTransformer.toProtobuf(data, schema);
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
## Framework-Specific Setup
|
|
302
|
+
|
|
303
|
+
### Next.js
|
|
304
|
+
|
|
305
|
+
**1. Copy WASM files to `public/`:**
|
|
306
|
+
|
|
307
|
+
```bash
|
|
308
|
+
cp node_modules/sshclient-wasm/dist/sshclient.wasm public/
|
|
309
|
+
cp node_modules/sshclient-wasm/dist/wasm_exec.js public/
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
**2. Use the Next.js configuration helper:**
|
|
313
|
+
|
|
314
|
+
```javascript
|
|
315
|
+
// next.config.js
|
|
316
|
+
import { NextJSConfig } from "sshclient-wasm/next";
|
|
317
|
+
|
|
318
|
+
/** @type {import('next').NextConfig} */
|
|
319
|
+
const nextConfig = NextJSConfig.getNextConfig({
|
|
320
|
+
// Your custom Next.js config here
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
module.exports = nextConfig;
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
**Or configure manually:**
|
|
327
|
+
|
|
328
|
+
```javascript
|
|
329
|
+
/** @type {import('next').NextConfig} */
|
|
330
|
+
const nextConfig = {
|
|
331
|
+
webpack: (config) => {
|
|
332
|
+
config.experiments = { ...config.experiments, asyncWebAssembly: true };
|
|
333
|
+
return config;
|
|
334
|
+
},
|
|
335
|
+
async headers() {
|
|
336
|
+
return [
|
|
337
|
+
{
|
|
338
|
+
source: "/(.*)",
|
|
339
|
+
headers: [
|
|
340
|
+
{ key: "Cross-Origin-Embedder-Policy", value: "require-corp" },
|
|
341
|
+
{ key: "Cross-Origin-Opener-Policy", value: "same-origin" },
|
|
342
|
+
],
|
|
343
|
+
},
|
|
344
|
+
];
|
|
345
|
+
},
|
|
346
|
+
};
|
|
347
|
+
|
|
348
|
+
module.exports = nextConfig;
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
**3. Use in your components:**
|
|
352
|
+
|
|
353
|
+
```javascript
|
|
354
|
+
import { initializeSSHClient, useSSHClient } from "sshclient-wasm/next";
|
|
355
|
+
|
|
356
|
+
// Method 1: Direct initialization
|
|
357
|
+
useEffect(() => {
|
|
358
|
+
initializeSSHClient()
|
|
359
|
+
.then(() => console.log("SSH client ready"))
|
|
360
|
+
.catch(console.error);
|
|
361
|
+
}, []);
|
|
362
|
+
|
|
363
|
+
// Method 2: Using the hook (if using sshclient-wasm/react)
|
|
364
|
+
const { isInitialized, initError, isLoading } = useSSHClient();
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
### Vite/React
|
|
368
|
+
|
|
369
|
+
**1. Copy WASM files to `public/`:**
|
|
370
|
+
|
|
371
|
+
```bash
|
|
372
|
+
cp node_modules/sshclient-wasm/dist/sshclient.wasm public/
|
|
373
|
+
cp node_modules/sshclient-wasm/dist/wasm_exec.js public/
|
|
374
|
+
```
|
|
375
|
+
|
|
376
|
+
**2. Use the Vite configuration helper:**
|
|
377
|
+
|
|
378
|
+
```typescript
|
|
379
|
+
// vite.config.ts
|
|
380
|
+
import { defineConfig } from "vite";
|
|
381
|
+
import react from "@vitejs/plugin-react";
|
|
382
|
+
import { ViteConfig } from "sshclient-wasm/vite";
|
|
383
|
+
|
|
384
|
+
export default defineConfig(ViteConfig.getViteConfig({
|
|
385
|
+
plugins: [react()],
|
|
386
|
+
// Your custom Vite config here
|
|
387
|
+
}));
|
|
388
|
+
```
|
|
389
|
+
|
|
390
|
+
**Or configure manually:**
|
|
391
|
+
|
|
392
|
+
```typescript
|
|
393
|
+
import { defineConfig } from "vite";
|
|
394
|
+
import react from "@vitejs/plugin-react";
|
|
395
|
+
|
|
396
|
+
export default defineConfig({
|
|
397
|
+
plugins: [react()],
|
|
398
|
+
server: {
|
|
399
|
+
headers: {
|
|
400
|
+
"Cross-Origin-Embedder-Policy": "require-corp",
|
|
401
|
+
"Cross-Origin-Opener-Policy": "same-origin",
|
|
402
|
+
},
|
|
403
|
+
},
|
|
404
|
+
});
|
|
405
|
+
```
|
|
406
|
+
|
|
407
|
+
**3. Use in your components:**
|
|
408
|
+
|
|
409
|
+
```javascript
|
|
410
|
+
import { initializeSSHClient } from "sshclient-wasm/vite";
|
|
411
|
+
import { useSSHClient } from "sshclient-wasm/react";
|
|
412
|
+
|
|
413
|
+
// Method 1: Direct initialization
|
|
414
|
+
useEffect(() => {
|
|
415
|
+
initializeSSHClient()
|
|
416
|
+
.then(() => console.log("SSH client ready"))
|
|
417
|
+
.catch(console.error);
|
|
418
|
+
}, []);
|
|
419
|
+
|
|
420
|
+
// Method 2: Using React hooks
|
|
421
|
+
const { isInitialized, initError, isLoading } = useSSHClient();
|
|
422
|
+
```
|
|
423
|
+
|
|
424
|
+
### React Hooks & Utilities
|
|
425
|
+
|
|
426
|
+
The `sshclient-wasm/react` module provides React-specific hooks and utilities:
|
|
427
|
+
|
|
428
|
+
```javascript
|
|
429
|
+
import {
|
|
430
|
+
useSSHClient,
|
|
431
|
+
useSSHConnection,
|
|
432
|
+
SSHClientProvider,
|
|
433
|
+
withSSHClient
|
|
434
|
+
} from "sshclient-wasm/react";
|
|
435
|
+
|
|
436
|
+
// Hook for initialization
|
|
437
|
+
const { isInitialized, initError, isLoading } = useSSHClient();
|
|
438
|
+
|
|
439
|
+
// Hook for connection management
|
|
440
|
+
const { connect, disconnect, send, session, connectionState } = useSSHConnection();
|
|
441
|
+
|
|
442
|
+
// Provider for context
|
|
443
|
+
<SSHClientProvider options={{ cacheBusting: false }}>
|
|
444
|
+
<YourApp />
|
|
445
|
+
</SSHClientProvider>
|
|
446
|
+
|
|
447
|
+
// HOC wrapper
|
|
448
|
+
const WrappedComponent = withSSHClient(YourComponent);
|
|
449
|
+
```
|
|
450
|
+
|
|
451
|
+
### Webpack/Generic
|
|
452
|
+
|
|
453
|
+
**1. Copy WASM files to your public directory**
|
|
454
|
+
|
|
455
|
+
**2. Initialize with custom options:**
|
|
456
|
+
|
|
457
|
+
```javascript
|
|
458
|
+
import { SSHClient } from "sshclient-wasm";
|
|
459
|
+
|
|
460
|
+
await SSHClient.initialize({
|
|
461
|
+
publicDir: "/assets/", // Your public directory path
|
|
462
|
+
autoDetect: true,
|
|
463
|
+
cacheBusting: process.env.NODE_ENV === "development",
|
|
464
|
+
});
|
|
465
|
+
```
|
|
466
|
+
|
|
467
|
+
## Advanced Initialization Options
|
|
468
|
+
|
|
469
|
+
### Custom Paths
|
|
470
|
+
|
|
471
|
+
```javascript
|
|
472
|
+
import { SSHClient } from "sshclient-wasm";
|
|
473
|
+
|
|
474
|
+
await SSHClient.initialize({
|
|
475
|
+
wasmPath: "/custom/path/sshclient.wasm",
|
|
476
|
+
wasmExecPath: "/custom/path/wasm_exec.js",
|
|
477
|
+
autoDetect: false,
|
|
478
|
+
cacheBusting: false,
|
|
479
|
+
timeout: 15000,
|
|
480
|
+
});
|
|
481
|
+
```
|
|
482
|
+
|
|
483
|
+
### Asset Availability Testing
|
|
484
|
+
|
|
485
|
+
```javascript
|
|
486
|
+
import { SSHClientHelpers } from "sshclient-wasm";
|
|
487
|
+
|
|
488
|
+
// Test if assets are properly placed
|
|
489
|
+
const { wasmAvailable, wasmExecAvailable } =
|
|
490
|
+
await SSHClientHelpers.testAssetAvailability(
|
|
491
|
+
"/sshclient.wasm",
|
|
492
|
+
"/wasm_exec.js"
|
|
493
|
+
);
|
|
494
|
+
|
|
495
|
+
if (!wasmAvailable) {
|
|
496
|
+
console.error("❌ Please copy sshclient.wasm to your public directory");
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
if (!wasmExecAvailable) {
|
|
500
|
+
console.error("❌ Please copy wasm_exec.js to your public directory");
|
|
501
|
+
}
|
|
502
|
+
```
|
|
503
|
+
|
|
504
|
+
### Error Handling & Troubleshooting
|
|
505
|
+
|
|
506
|
+
```javascript
|
|
507
|
+
try {
|
|
508
|
+
await SSHClient.initialize();
|
|
509
|
+
} catch (error) {
|
|
510
|
+
if (error.message.includes("WASM file not found")) {
|
|
511
|
+
console.error("Copy sshclient.wasm to public/ directory");
|
|
512
|
+
} else if (error.message.includes("wasm_exec.js not found")) {
|
|
513
|
+
console.error("Copy wasm_exec.js to public/ directory");
|
|
514
|
+
} else if (error.message.includes("Failed to fetch WASM")) {
|
|
515
|
+
console.error("Check network connection and CORS headers");
|
|
516
|
+
} else {
|
|
517
|
+
console.error("Initialization failed:", error.message);
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
```
|
|
521
|
+
|
|
522
|
+
## Development
|
|
523
|
+
|
|
524
|
+
### Prerequisites
|
|
525
|
+
|
|
526
|
+
- Go 1.21+
|
|
527
|
+
- Node.js 16+
|
|
528
|
+
- Make
|
|
529
|
+
|
|
530
|
+
### Building from Source
|
|
531
|
+
|
|
532
|
+
```bash
|
|
533
|
+
# Clone the repository
|
|
534
|
+
git clone https://github.com/andrew/sshclient-wasm.git
|
|
535
|
+
cd sshclient-wasm
|
|
536
|
+
|
|
537
|
+
# Install dependencies
|
|
538
|
+
go mod download
|
|
539
|
+
npm install
|
|
540
|
+
|
|
541
|
+
# Build WASM and TypeScript
|
|
542
|
+
make build
|
|
543
|
+
|
|
544
|
+
# Run example
|
|
545
|
+
make dev
|
|
546
|
+
```
|
|
547
|
+
|
|
548
|
+
### Project Structure
|
|
549
|
+
|
|
550
|
+
```
|
|
551
|
+
sshclient-wasm/
|
|
552
|
+
├── main.go # WASM entry point
|
|
553
|
+
├── pkg/sshclient/ # Go SSH client implementation
|
|
554
|
+
│ ├── client.go # Main client logic
|
|
555
|
+
│ └── interceptor.go # Packet interception
|
|
556
|
+
├── lib/ # TypeScript/JavaScript bindings
|
|
557
|
+
│ └── index.ts # Main TypeScript API
|
|
558
|
+
├── example/ # Example application
|
|
559
|
+
└── dist/ # Build output
|
|
560
|
+
```
|
|
561
|
+
|
|
562
|
+
## TypeScript API Reference
|
|
563
|
+
|
|
564
|
+
### Main Classes
|
|
565
|
+
|
|
566
|
+
#### SSHClient
|
|
567
|
+
|
|
568
|
+
The main entry point for establishing SSH connections through WebAssembly.
|
|
569
|
+
|
|
570
|
+
```typescript
|
|
571
|
+
class SSHClient {
|
|
572
|
+
/**
|
|
573
|
+
* Initialize the WASM module. Must be called before any other methods.
|
|
574
|
+
* @param options - Initialization options or legacy string path
|
|
575
|
+
*/
|
|
576
|
+
static async initialize(
|
|
577
|
+
options?: InitializationOptions | string
|
|
578
|
+
): Promise<void>;
|
|
579
|
+
|
|
580
|
+
/**
|
|
581
|
+
* Connect to an SSH server through a transport
|
|
582
|
+
* @param options - SSH connection configuration
|
|
583
|
+
* @param transport - Transport implementation for network communication
|
|
584
|
+
* @param callbacks - Optional callbacks for monitoring packet flow
|
|
585
|
+
* @returns SSH session handle for sending data and disconnecting
|
|
586
|
+
*/
|
|
587
|
+
static async connect(
|
|
588
|
+
options: ConnectionOptions,
|
|
589
|
+
transport: Transport,
|
|
590
|
+
callbacks?: SSHClientCallbacks
|
|
591
|
+
): Promise<SSHSession>;
|
|
592
|
+
|
|
593
|
+
/**
|
|
594
|
+
* Disconnect a specific SSH session
|
|
595
|
+
* @param sessionId - The ID of the session to disconnect
|
|
596
|
+
*/
|
|
597
|
+
static async disconnect(sessionId: string): Promise<void>;
|
|
598
|
+
|
|
599
|
+
/**
|
|
600
|
+
* Get the library version
|
|
601
|
+
* @returns Version string
|
|
602
|
+
*/
|
|
603
|
+
static getVersion(): string;
|
|
604
|
+
}
|
|
605
|
+
```
|
|
606
|
+
|
|
607
|
+
#### PacketTransformer
|
|
608
|
+
|
|
609
|
+
Utilities for transforming SSH packet data.
|
|
610
|
+
|
|
611
|
+
```typescript
|
|
612
|
+
class PacketTransformer {
|
|
613
|
+
/**
|
|
614
|
+
* Convert binary data to Base64 encoding
|
|
615
|
+
* @param data - Binary data to encode
|
|
616
|
+
* @returns Base64 encoded string
|
|
617
|
+
*/
|
|
618
|
+
static toBase64(data: Uint8Array): string;
|
|
619
|
+
|
|
620
|
+
/**
|
|
621
|
+
* Convert Base64 string to binary data
|
|
622
|
+
* @param base64 - Base64 encoded string
|
|
623
|
+
* @returns Decoded binary data
|
|
624
|
+
*/
|
|
625
|
+
static fromBase64(base64: string): Uint8Array;
|
|
626
|
+
|
|
627
|
+
/**
|
|
628
|
+
* Transform binary data to Protobuf format (user-implemented)
|
|
629
|
+
* @param data - Binary data to transform
|
|
630
|
+
* @param schema - Protobuf schema definition
|
|
631
|
+
* @returns Protobuf encoded data
|
|
632
|
+
*/
|
|
633
|
+
static toProtobuf(data: Uint8Array, schema?: any): Uint8Array;
|
|
634
|
+
|
|
635
|
+
/**
|
|
636
|
+
* Transform Protobuf data to binary format (user-implemented)
|
|
637
|
+
* @param data - Protobuf encoded data
|
|
638
|
+
* @param schema - Protobuf schema definition
|
|
639
|
+
* @returns Decoded binary data
|
|
640
|
+
*/
|
|
641
|
+
static fromProtobuf(data: Uint8Array, schema?: any): Uint8Array;
|
|
642
|
+
}
|
|
643
|
+
```
|
|
644
|
+
|
|
645
|
+
### Transport Implementations
|
|
646
|
+
|
|
647
|
+
#### WebSocketTransport
|
|
648
|
+
|
|
649
|
+
Direct WebSocket connection to SSH servers.
|
|
650
|
+
|
|
651
|
+
```typescript
|
|
652
|
+
class WebSocketTransport implements Transport {
|
|
653
|
+
/**
|
|
654
|
+
* Create a WebSocket transport
|
|
655
|
+
* @param id - Unique identifier for this transport
|
|
656
|
+
* @param url - WebSocket URL (e.g., 'wss://ssh-gateway.example.com')
|
|
657
|
+
* @param protocols - Optional WebSocket subprotocols
|
|
658
|
+
*/
|
|
659
|
+
constructor(id: string, url: string, protocols?: string[]);
|
|
660
|
+
|
|
661
|
+
async connect(): Promise<void>;
|
|
662
|
+
async disconnect(): Promise<void>;
|
|
663
|
+
async send(data: Uint8Array): Promise<void>;
|
|
664
|
+
}
|
|
665
|
+
```
|
|
666
|
+
|
|
667
|
+
#### SecureTunnelTransport
|
|
668
|
+
|
|
669
|
+
AWS IoT Secure Tunneling transport for end-to-end encrypted connections.
|
|
670
|
+
|
|
671
|
+
```typescript
|
|
672
|
+
class SecureTunnelTransport implements Transport {
|
|
673
|
+
/**
|
|
674
|
+
* Create an AWS IoT Secure Tunnel transport
|
|
675
|
+
* @param id - Unique identifier for this transport
|
|
676
|
+
* @param config - Tunnel configuration
|
|
677
|
+
*/
|
|
678
|
+
constructor(id: string, config: SecureTunnelConfig);
|
|
679
|
+
|
|
680
|
+
async connect(): Promise<void>;
|
|
681
|
+
async disconnect(): Promise<void>;
|
|
682
|
+
async send(data: Uint8Array): Promise<void>;
|
|
683
|
+
}
|
|
684
|
+
```
|
|
685
|
+
|
|
686
|
+
#### CustomTransport
|
|
687
|
+
|
|
688
|
+
User-defined transport for custom protocols.
|
|
689
|
+
|
|
690
|
+
```typescript
|
|
691
|
+
class CustomTransport implements Transport {
|
|
692
|
+
/**
|
|
693
|
+
* Create a custom transport with user-provided implementations
|
|
694
|
+
* @param id - Unique identifier for this transport
|
|
695
|
+
* @param connectImpl - Custom connection implementation
|
|
696
|
+
* @param disconnectImpl - Custom disconnection implementation
|
|
697
|
+
* @param sendImpl - Custom send implementation
|
|
698
|
+
*/
|
|
699
|
+
constructor(
|
|
700
|
+
id: string,
|
|
701
|
+
connectImpl?: () => Promise<void>,
|
|
702
|
+
disconnectImpl?: () => Promise<void>,
|
|
703
|
+
sendImpl?: (data: Uint8Array) => Promise<void>
|
|
704
|
+
);
|
|
705
|
+
|
|
706
|
+
/**
|
|
707
|
+
* Inject received data into the transport
|
|
708
|
+
* @param data - Data received from the custom protocol
|
|
709
|
+
*/
|
|
710
|
+
injectData(data: Uint8Array): void;
|
|
711
|
+
}
|
|
712
|
+
```
|
|
713
|
+
|
|
714
|
+
#### SSHClientHelpers
|
|
715
|
+
|
|
716
|
+
Framework-specific helpers and utilities.
|
|
717
|
+
|
|
718
|
+
```typescript
|
|
719
|
+
class SSHClientHelpers {
|
|
720
|
+
/**
|
|
721
|
+
* Get recommended asset paths for the detected framework
|
|
722
|
+
*/
|
|
723
|
+
static getAssetPaths: (publicDir?: string) => {
|
|
724
|
+
wasmPath: string;
|
|
725
|
+
wasmExecPath: string;
|
|
726
|
+
};
|
|
727
|
+
|
|
728
|
+
/**
|
|
729
|
+
* Detect the current framework
|
|
730
|
+
*/
|
|
731
|
+
static detectFramework: () => "nextjs" | "vite" | "webpack" | "generic";
|
|
732
|
+
|
|
733
|
+
/**
|
|
734
|
+
* Test if WASM assets are available at the given paths
|
|
735
|
+
*/
|
|
736
|
+
static testAssetAvailability: (
|
|
737
|
+
wasmPath: string,
|
|
738
|
+
wasmExecPath: string
|
|
739
|
+
) => Promise<{
|
|
740
|
+
wasmAvailable: boolean;
|
|
741
|
+
wasmExecAvailable: boolean;
|
|
742
|
+
}>;
|
|
743
|
+
|
|
744
|
+
/**
|
|
745
|
+
* Next.js specific initialization helper
|
|
746
|
+
*/
|
|
747
|
+
static initializeForNextJS: (
|
|
748
|
+
options?: Partial<InitializationOptions>
|
|
749
|
+
) => Promise<void>;
|
|
750
|
+
|
|
751
|
+
/**
|
|
752
|
+
* Vite specific initialization helper
|
|
753
|
+
*/
|
|
754
|
+
static initializeForVite: (
|
|
755
|
+
options?: Partial<InitializationOptions>
|
|
756
|
+
) => Promise<void>;
|
|
757
|
+
|
|
758
|
+
/**
|
|
759
|
+
* Generic initialization with sensible defaults
|
|
760
|
+
*/
|
|
761
|
+
static initializeWithDefaults: (
|
|
762
|
+
customOptions?: Partial<InitializationOptions>
|
|
763
|
+
) => Promise<void>;
|
|
764
|
+
}
|
|
765
|
+
```
|
|
766
|
+
|
|
767
|
+
### Interfaces
|
|
768
|
+
|
|
769
|
+
#### InitializationOptions
|
|
770
|
+
|
|
771
|
+
Configuration options for initializing the SSH client.
|
|
772
|
+
|
|
773
|
+
```typescript
|
|
774
|
+
interface InitializationOptions {
|
|
775
|
+
/** Path to the WASM file (default: auto-detected) */
|
|
776
|
+
wasmPath?: string;
|
|
777
|
+
|
|
778
|
+
/** Path to the wasm_exec.js file (default: auto-detected) */
|
|
779
|
+
wasmExecPath?: string;
|
|
780
|
+
|
|
781
|
+
/** Enable automatic path detection (default: true) */
|
|
782
|
+
autoDetect?: boolean;
|
|
783
|
+
|
|
784
|
+
/** Public directory path for auto-detection (default: '/') */
|
|
785
|
+
publicDir?: string;
|
|
786
|
+
|
|
787
|
+
/** Enable cache busting for development (default: true) */
|
|
788
|
+
cacheBusting?: boolean;
|
|
789
|
+
|
|
790
|
+
/** Timeout for loading assets in milliseconds (default: 10000) */
|
|
791
|
+
timeout?: number;
|
|
792
|
+
}
|
|
793
|
+
```
|
|
794
|
+
|
|
795
|
+
#### Transport
|
|
796
|
+
|
|
797
|
+
Base interface for all transport implementations.
|
|
798
|
+
|
|
799
|
+
```typescript
|
|
800
|
+
interface Transport {
|
|
801
|
+
id: string;
|
|
802
|
+
connect(): Promise<void>;
|
|
803
|
+
disconnect(): Promise<void>;
|
|
804
|
+
send(data: Uint8Array): Promise<void>;
|
|
805
|
+
|
|
806
|
+
// Event handlers (set by the library)
|
|
807
|
+
onData?: (data: Uint8Array) => void;
|
|
808
|
+
onError?: (error: Error) => void;
|
|
809
|
+
onClose?: () => void;
|
|
810
|
+
}
|
|
811
|
+
```
|
|
812
|
+
|
|
813
|
+
#### ConnectionOptions
|
|
814
|
+
|
|
815
|
+
SSH connection configuration.
|
|
816
|
+
|
|
817
|
+
```typescript
|
|
818
|
+
interface ConnectionOptions {
|
|
819
|
+
/** Target SSH server hostname */
|
|
820
|
+
host: string;
|
|
821
|
+
|
|
822
|
+
/** SSH server port (default: 22) */
|
|
823
|
+
port: number;
|
|
824
|
+
|
|
825
|
+
/** SSH username */
|
|
826
|
+
user: string;
|
|
827
|
+
|
|
828
|
+
/** Password for authentication (optional) */
|
|
829
|
+
password?: string;
|
|
830
|
+
|
|
831
|
+
/** Private key for authentication (optional) */
|
|
832
|
+
privateKey?: string;
|
|
833
|
+
|
|
834
|
+
/** Connection timeout in milliseconds (optional) */
|
|
835
|
+
timeout?: number;
|
|
836
|
+
}
|
|
837
|
+
```
|
|
838
|
+
|
|
839
|
+
#### SecureTunnelConfig
|
|
840
|
+
|
|
841
|
+
AWS IoT Secure Tunnel configuration.
|
|
842
|
+
|
|
843
|
+
```typescript
|
|
844
|
+
interface SecureTunnelConfig {
|
|
845
|
+
/** AWS region where the tunnel is created */
|
|
846
|
+
region: string;
|
|
847
|
+
|
|
848
|
+
/** Tunnel access token */
|
|
849
|
+
accessToken: string;
|
|
850
|
+
|
|
851
|
+
/** Client mode: 'source' or 'destination' */
|
|
852
|
+
clientMode: "source" | "destination";
|
|
853
|
+
|
|
854
|
+
/** Service identifier for multiplexed tunnels (optional) */
|
|
855
|
+
serviceId?: string;
|
|
856
|
+
|
|
857
|
+
/** Protocol version: 'V2' or 'V3' (default: 'V2') */
|
|
858
|
+
protocol?: "V2" | "V3";
|
|
859
|
+
}
|
|
860
|
+
```
|
|
861
|
+
|
|
862
|
+
#### SSHSession
|
|
863
|
+
|
|
864
|
+
SSH session handle returned from connect().
|
|
865
|
+
|
|
866
|
+
```typescript
|
|
867
|
+
interface SSHSession {
|
|
868
|
+
/** Unique session identifier */
|
|
869
|
+
sessionId: string;
|
|
870
|
+
|
|
871
|
+
/**
|
|
872
|
+
* Send data through the SSH connection
|
|
873
|
+
* @param data - Binary data to send
|
|
874
|
+
*/
|
|
875
|
+
send(data: Uint8Array): Promise<void>;
|
|
876
|
+
|
|
877
|
+
/**
|
|
878
|
+
* Close the SSH connection
|
|
879
|
+
*/
|
|
880
|
+
disconnect(): Promise<void>;
|
|
881
|
+
}
|
|
882
|
+
```
|
|
883
|
+
|
|
884
|
+
#### SSHClientCallbacks
|
|
885
|
+
|
|
886
|
+
Optional callbacks for monitoring SSH packet flow.
|
|
887
|
+
|
|
888
|
+
```typescript
|
|
889
|
+
interface SSHClientCallbacks {
|
|
890
|
+
/**
|
|
891
|
+
* Called when sending an SSH packet
|
|
892
|
+
* @param data - Raw packet data being sent
|
|
893
|
+
* @param metadata - Packet metadata (type, size, etc.)
|
|
894
|
+
*/
|
|
895
|
+
onPacketSend?: (data: Uint8Array, metadata: PacketMetadata) => void;
|
|
896
|
+
|
|
897
|
+
/**
|
|
898
|
+
* Called when receiving an SSH packet
|
|
899
|
+
* @param data - Raw packet data received
|
|
900
|
+
* @param metadata - Packet metadata (type, size, etc.)
|
|
901
|
+
*/
|
|
902
|
+
onPacketReceive?: (data: Uint8Array, metadata: PacketMetadata) => void;
|
|
903
|
+
|
|
904
|
+
/**
|
|
905
|
+
* Called when SSH connection state changes
|
|
906
|
+
* @param state - New connection state
|
|
907
|
+
*/
|
|
908
|
+
onStateChange?: (state: SSHConnectionState) => void;
|
|
909
|
+
}
|
|
910
|
+
```
|
|
911
|
+
|
|
912
|
+
#### PacketMetadata
|
|
913
|
+
|
|
914
|
+
Metadata about SSH packets.
|
|
915
|
+
|
|
916
|
+
```typescript
|
|
917
|
+
interface PacketMetadata {
|
|
918
|
+
/** Packet type identifier */
|
|
919
|
+
packetType: string;
|
|
920
|
+
|
|
921
|
+
/** Packet type code */
|
|
922
|
+
packetTypeCode: number;
|
|
923
|
+
|
|
924
|
+
/** Packet size in bytes */
|
|
925
|
+
size: number;
|
|
926
|
+
|
|
927
|
+
/** Timestamp of packet */
|
|
928
|
+
timestamp?: number;
|
|
929
|
+
}
|
|
930
|
+
```
|
|
931
|
+
|
|
932
|
+
#### SSHConnectionState
|
|
933
|
+
|
|
934
|
+
SSH connection states.
|
|
935
|
+
|
|
936
|
+
```typescript
|
|
937
|
+
type SSHConnectionState =
|
|
938
|
+
| "connecting"
|
|
939
|
+
| "connected"
|
|
940
|
+
| "authenticating"
|
|
941
|
+
| "authenticated"
|
|
942
|
+
| "ready"
|
|
943
|
+
| "disconnecting"
|
|
944
|
+
| "disconnected"
|
|
945
|
+
| "error";
|
|
946
|
+
```
|
|
947
|
+
|
|
948
|
+
### Enums
|
|
949
|
+
|
|
950
|
+
#### TunnelMessageType
|
|
951
|
+
|
|
952
|
+
AWS IoT Secure Tunnel message types.
|
|
953
|
+
|
|
954
|
+
```typescript
|
|
955
|
+
enum TunnelMessageType {
|
|
956
|
+
UNKNOWN = 0,
|
|
957
|
+
DATA = 1,
|
|
958
|
+
STREAM_START = 2,
|
|
959
|
+
STREAM_RESET = 3,
|
|
960
|
+
SESSION_RESET = 4,
|
|
961
|
+
SERVICE_IDS = 5,
|
|
962
|
+
CONNECTION_START = 6,
|
|
963
|
+
CONNECTION_RESET = 7,
|
|
964
|
+
}
|
|
965
|
+
```
|
|
966
|
+
|
|
967
|
+
## Troubleshooting
|
|
968
|
+
|
|
969
|
+
### Common Issues
|
|
970
|
+
|
|
971
|
+
#### ❌ "WASM file not found"
|
|
972
|
+
|
|
973
|
+
```bash
|
|
974
|
+
# Solution: Copy WASM files to public directory
|
|
975
|
+
cp node_modules/sshclient-wasm/dist/sshclient.wasm public/
|
|
976
|
+
cp node_modules/sshclient-wasm/dist/wasm_exec.js public/
|
|
977
|
+
```
|
|
978
|
+
|
|
979
|
+
#### ❌ "Failed to fetch WASM file: 404"
|
|
980
|
+
|
|
981
|
+
- Verify files are in your public directory
|
|
982
|
+
- Check that your web server serves static files from public/
|
|
983
|
+
- Ensure file paths are correct (case-sensitive on some servers)
|
|
984
|
+
|
|
985
|
+
#### ❌ "Go runtime not loaded"
|
|
986
|
+
|
|
987
|
+
- Ensure `wasm_exec.js` is accessible and loads successfully
|
|
988
|
+
- Check browser console for script loading errors
|
|
989
|
+
- Verify CORS headers allow script loading
|
|
990
|
+
|
|
991
|
+
#### ❌ "Cross-origin requests blocked"
|
|
992
|
+
|
|
993
|
+
**Next.js users:** Add CORS headers to `next.config.js`:
|
|
994
|
+
|
|
995
|
+
```javascript
|
|
996
|
+
async headers() {
|
|
997
|
+
return [{
|
|
998
|
+
source: '/(.*)',
|
|
999
|
+
headers: [
|
|
1000
|
+
{ key: 'Cross-Origin-Embedder-Policy', value: 'require-corp' },
|
|
1001
|
+
{ key: 'Cross-Origin-Opener-Policy', value: 'same-origin' },
|
|
1002
|
+
],
|
|
1003
|
+
}];
|
|
1004
|
+
}
|
|
1005
|
+
```
|
|
1006
|
+
|
|
1007
|
+
**Vite users:** Add to `vite.config.ts`:
|
|
1008
|
+
|
|
1009
|
+
```typescript
|
|
1010
|
+
server: {
|
|
1011
|
+
headers: {
|
|
1012
|
+
'Cross-Origin-Embedder-Policy': 'require-corp',
|
|
1013
|
+
'Cross-Origin-Opener-Policy': 'same-origin',
|
|
1014
|
+
},
|
|
1015
|
+
}
|
|
1016
|
+
```
|
|
1017
|
+
|
|
1018
|
+
#### ❌ "WebAssembly.instantiate failed"
|
|
1019
|
+
|
|
1020
|
+
- Your browser may not support WebAssembly
|
|
1021
|
+
- WASM file may be corrupted or incompatible
|
|
1022
|
+
- Try clearing browser cache
|
|
1023
|
+
|
|
1024
|
+
### Debug Helpers
|
|
1025
|
+
|
|
1026
|
+
#### Test Asset Availability
|
|
1027
|
+
|
|
1028
|
+
```typescript
|
|
1029
|
+
import { SSHClientHelpers } from "sshclient-wasm";
|
|
1030
|
+
|
|
1031
|
+
const result = await SSHClientHelpers.testAssetAvailability(
|
|
1032
|
+
"/sshclient.wasm",
|
|
1033
|
+
"/wasm_exec.js"
|
|
1034
|
+
);
|
|
1035
|
+
|
|
1036
|
+
console.log("WASM available:", result.wasmAvailable);
|
|
1037
|
+
console.log("Exec available:", result.wasmExecAvailable);
|
|
1038
|
+
```
|
|
1039
|
+
|
|
1040
|
+
#### Check Framework Detection
|
|
1041
|
+
|
|
1042
|
+
```typescript
|
|
1043
|
+
import { SSHClientHelpers } from "sshclient-wasm";
|
|
1044
|
+
|
|
1045
|
+
console.log("Detected framework:", SSHClientHelpers.detectFramework());
|
|
1046
|
+
console.log("Recommended paths:", SSHClientHelpers.getAssetPaths());
|
|
1047
|
+
```
|
|
1048
|
+
|
|
1049
|
+
### Error Handling
|
|
1050
|
+
|
|
1051
|
+
All async methods throw errors that can be caught:
|
|
1052
|
+
|
|
1053
|
+
```typescript
|
|
1054
|
+
try {
|
|
1055
|
+
await SSHClient.initialize();
|
|
1056
|
+
await SSHClient.connect(options, transport);
|
|
1057
|
+
} catch (error) {
|
|
1058
|
+
if (error.message.includes("WASM file not found")) {
|
|
1059
|
+
console.error("📁 Copy sshclient.wasm to public/ directory");
|
|
1060
|
+
} else if (error.message.includes("wasm_exec.js not found")) {
|
|
1061
|
+
console.error("📁 Copy wasm_exec.js to public/ directory");
|
|
1062
|
+
} else if (error.message.includes("Failed to fetch WASM")) {
|
|
1063
|
+
console.error("🌐 Check network connection and CORS headers");
|
|
1064
|
+
} else if (error.message.includes("authentication")) {
|
|
1065
|
+
console.error("🔐 Check SSH credentials");
|
|
1066
|
+
} else if (error.message.includes("timeout")) {
|
|
1067
|
+
console.error("⏰ Connection or initialization timeout");
|
|
1068
|
+
} else {
|
|
1069
|
+
console.error("❌ Unknown error:", error.message);
|
|
1070
|
+
}
|
|
1071
|
+
}
|
|
1072
|
+
```
|
|
1073
|
+
|
|
1074
|
+
### Performance Tips
|
|
1075
|
+
|
|
1076
|
+
- **Cache Busting**: Disable in production for better performance
|
|
1077
|
+
- **Asset Preloading**: Consider preloading WASM files for faster initialization
|
|
1078
|
+
- **Memory Usage**: Monitor memory usage for long-running sessions
|
|
1079
|
+
- **Connection Pooling**: Reuse transports when possible
|
|
1080
|
+
|
|
1081
|
+
### Examples
|
|
1082
|
+
|
|
1083
|
+
#### Custom Packet Transformation
|
|
1084
|
+
|
|
1085
|
+
```typescript
|
|
1086
|
+
const session = await SSHClient.connect(connectionOptions, transport, {
|
|
1087
|
+
onPacketSend: (data, metadata) => {
|
|
1088
|
+
// Transform outgoing packets
|
|
1089
|
+
const transformed = PacketTransformer.toProtobuf(data, mySchema);
|
|
1090
|
+
console.log(
|
|
1091
|
+
`Sending ${metadata.packetType} packet (${metadata.size} bytes)`
|
|
1092
|
+
);
|
|
1093
|
+
return transformed;
|
|
1094
|
+
},
|
|
1095
|
+
onPacketReceive: (data, metadata) => {
|
|
1096
|
+
// Process incoming packets
|
|
1097
|
+
const decoded = PacketTransformer.fromProtobuf(data, mySchema);
|
|
1098
|
+
console.log(`Received ${metadata.packetType} packet`);
|
|
1099
|
+
return decoded;
|
|
1100
|
+
},
|
|
1101
|
+
});
|
|
1102
|
+
```
|
|
1103
|
+
|
|
1104
|
+
#### Custom Transport Implementation
|
|
1105
|
+
|
|
1106
|
+
```typescript
|
|
1107
|
+
class MyCustomTransport extends CustomTransport {
|
|
1108
|
+
constructor(id: string, config: MyConfig) {
|
|
1109
|
+
super(
|
|
1110
|
+
id,
|
|
1111
|
+
async () => {
|
|
1112
|
+
// Custom connection logic
|
|
1113
|
+
await this.establishConnection(config);
|
|
1114
|
+
},
|
|
1115
|
+
async () => {
|
|
1116
|
+
// Custom disconnection logic
|
|
1117
|
+
await this.closeConnection();
|
|
1118
|
+
},
|
|
1119
|
+
async (data: Uint8Array) => {
|
|
1120
|
+
// Custom send logic
|
|
1121
|
+
await this.sendData(data);
|
|
1122
|
+
}
|
|
1123
|
+
);
|
|
1124
|
+
}
|
|
1125
|
+
|
|
1126
|
+
private async establishConnection(config: MyConfig) {
|
|
1127
|
+
// Implementation specific to your protocol
|
|
1128
|
+
}
|
|
1129
|
+
|
|
1130
|
+
// Call this.injectData() when receiving data from your protocol
|
|
1131
|
+
handleIncomingData(data: Uint8Array) {
|
|
1132
|
+
this.injectData(data);
|
|
1133
|
+
}
|
|
1134
|
+
}
|
|
1135
|
+
```
|
|
1136
|
+
|
|
1137
|
+
## License
|
|
1138
|
+
|
|
1139
|
+
MIT
|