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/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