@kadi.build/file-sharing 1.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/README.md ADDED
@@ -0,0 +1,477 @@
1
+ # @kadi.build/file-sharing
2
+
3
+ File sharing service with tunneling, authentication, and a local S3-compatible interface.
4
+ Integrates [`@kadi.build/file-manager`](https://www.npmjs.com/package/@kadi.build/file-manager) and [`@kadi.build/tunnel-services`](https://www.npmjs.com/package/@kadi.build/tunnel-services) into one turnkey solution.
5
+
6
+ ## Features
7
+
8
+ - **HTTP File Server** — Static file serving with directory listing, range requests, CORS, and multi-scheme authentication
9
+ - **Local S3-Compatible API** — Emulates AWS S3 endpoints locally so you can use `@aws-sdk/client-s3` against your local filesystem
10
+ - **Tunnel Integration** — Expose your local server publicly via KĀDI (default), ngrok, serveo, localtunnel, or pinggy
11
+ - **Authentication** — Basic Auth, Bearer Token, and API Key (header / query param) for HTTP; AWS SigV4 / SigV2 / Bearer for S3
12
+ - **Secrets Management** — Automatic `.env` loading (walks up parent directories for monorepo support) + environment variables + explicit config
13
+ - **Download Monitoring** — Track active downloads, progress, speed, and completion statistics
14
+ - **Graceful Shutdown** — Priority-ordered shutdown with timeout and force-kill
15
+ - **Webhook Notifications** — Event-driven notifications to external endpoints
16
+ - **Monitoring Dashboard** — Console-based real-time dashboard
17
+
18
+ ## Installation
19
+
20
+ ```bash
21
+ npm install @kadi.build/file-sharing
22
+ ```
23
+
24
+ ## Quick Start
25
+
26
+ ### Basic File Sharing
27
+
28
+ ```javascript
29
+ import { FileSharingServer } from '@kadi.build/file-sharing';
30
+
31
+ const server = new FileSharingServer({
32
+ staticDir: '/path/to/files',
33
+ port: 3000
34
+ });
35
+
36
+ await server.start();
37
+ console.log(`Serving files at ${server.getInfo().localUrl}`);
38
+ await server.stop();
39
+ ```
40
+
41
+ ### With Public Tunnel (KĀDI)
42
+
43
+ ```javascript
44
+ const server = new FileSharingServer({
45
+ staticDir: '/path/to/files',
46
+ port: 3000,
47
+ tunnel: {
48
+ enabled: true
49
+ // KĀDI is the default — reads token from KADI_TUNNEL_TOKEN env var or .env
50
+ }
51
+ });
52
+
53
+ await server.start();
54
+ console.log(`Public URL: ${server.tunnelUrl}`);
55
+ ```
56
+
57
+ ### With Authentication
58
+
59
+ ```javascript
60
+ const server = new FileSharingServer({
61
+ staticDir: '/path/to/files',
62
+ port: 3000,
63
+ auth: { apiKey: 'my-secret-key' }, // or { username: 'admin', password: 'secret' }
64
+ tunnel: { enabled: true }
65
+ });
66
+
67
+ await server.start();
68
+ // Clients must send X-API-Key header, Bearer token, or ?apiKey= query param
69
+ ```
70
+
71
+ ### With Local S3 API
72
+
73
+ ```javascript
74
+ import { FileSharingServer } from '@kadi.build/file-sharing';
75
+ import { S3Client, ListObjectsV2Command, PutObjectCommand } from '@aws-sdk/client-s3';
76
+
77
+ const server = new FileSharingServer({
78
+ staticDir: '/path/to/files',
79
+ port: 3000,
80
+ enableS3: true,
81
+ s3Port: 9000
82
+ });
83
+
84
+ await server.start();
85
+
86
+ // Use the standard AWS SDK against your LOCAL filesystem
87
+ const s3 = new S3Client({
88
+ endpoint: 'http://localhost:9000',
89
+ region: 'us-east-1',
90
+ credentials: { accessKeyId: 'minioadmin', secretAccessKey: 'minioadmin' },
91
+ forcePathStyle: true
92
+ });
93
+
94
+ const response = await s3.send(new ListObjectsV2Command({ Bucket: 'local' }));
95
+ console.log(response.Contents);
96
+
97
+ await s3.send(new PutObjectCommand({
98
+ Bucket: 'local',
99
+ Key: 'uploaded.txt',
100
+ Body: 'Hello from S3!'
101
+ }));
102
+ ```
103
+
104
+ ### Quick Share (One-Liner)
105
+
106
+ ```javascript
107
+ import { createQuickShare } from '@kadi.build/file-sharing';
108
+
109
+ const { server, localUrl, publicUrl } = await createQuickShare('./my-files', {
110
+ tunnel: true,
111
+ auth: { apiKey: 'share-key-123' }
112
+ });
113
+
114
+ console.log(`Share this link: ${publicUrl}`);
115
+ ```
116
+
117
+ ---
118
+
119
+ ## Secrets & Authentication
120
+
121
+ ### How Secrets Are Loaded
122
+
123
+ Secrets are resolved in **priority order** (first match wins):
124
+
125
+ 1. **Explicit constructor config** — values you pass directly
126
+ 2. **Environment variables** — `process.env.*`
127
+ 3. **`.env` file** — automatically found by walking up parent directories from `process.cwd()`
128
+
129
+ The `.env` loader supports monorepo layouts: if your `.env` is at the workspace root and the package runs from `packages/file-sharing/`, it will find it automatically.
130
+
131
+ ### Environment Variables
132
+
133
+ | Variable | Description | Default |
134
+ |----------|-------------|---------|
135
+ | **Tunnel — KĀDI** | | |
136
+ | `KADI_TUNNEL_TOKEN` | KĀDI authentication token | *(required for KĀDI)* |
137
+ | `KADI_TUNNEL_SERVER` | KĀDI broker address | `broker.kadi.build` |
138
+ | `KADI_TUNNEL_DOMAIN` | Tunnel domain | `tunnel.kadi.build` |
139
+ | `KADI_TUNNEL_PORT` | KĀDI frps server port | `7000` |
140
+ | `KADI_TUNNEL_SSH_PORT` | KĀDI SSH gateway port | `2200` |
141
+ | `KADI_TUNNEL_MODE` | Connection mode: `ssh`, `frpc`, or `auto` | `auto` |
142
+ | `KADI_AGENT_ID` | Agent identifier for proxy naming | `kadi` |
143
+ | **Tunnel — Ngrok** | | |
144
+ | `NGROK_AUTHTOKEN` | Ngrok auth token (also accepts `NGROK_AUTH_TOKEN`) | — |
145
+ | **HTTP Authentication** | | |
146
+ | `KADI_AUTH_API_KEY` | API key for Bearer / X-API-Key auth | — |
147
+ | `KADI_AUTH_USERNAME` | HTTP Basic auth username | — |
148
+ | `KADI_AUTH_PASSWORD` | HTTP Basic auth password | — |
149
+ | **S3 Authentication** | | |
150
+ | `KADI_S3_ACCESS_KEY` | S3 access key ID | `minioadmin` |
151
+ | `KADI_S3_SECRET_KEY` | S3 secret access key | `minioadmin` |
152
+
153
+ ### `.env` File Example
154
+
155
+ ```env
156
+ # Tunnel credentials
157
+ KADI_TUNNEL_TOKEN=your-kadi-token-here
158
+ KADI_TUNNEL_SERVER=broker.kadi.build
159
+ KADI_TUNNEL_DOMAIN=tunnel.kadi.build
160
+ KADI_TUNNEL_PORT=7000
161
+ KADI_TUNNEL_SSH_PORT=2200
162
+ KADI_TUNNEL_MODE=ssh
163
+ KADI_AGENT_ID=my-agent
164
+
165
+ # Optional: Ngrok (fallback)
166
+ NGROK_AUTHTOKEN=your-ngrok-token
167
+
168
+ # HTTP auth (optional — protects file downloads)
169
+ KADI_AUTH_API_KEY=my-secret-api-key
170
+
171
+ # S3 credentials (optional — default minioadmin/minioadmin)
172
+ KADI_S3_ACCESS_KEY=my-access-key
173
+ KADI_S3_SECRET_KEY=my-secret-key
174
+ ```
175
+
176
+ ### HTTP Authentication Schemes
177
+
178
+ When `auth` is configured (via config, env vars, or `.env`), the HTTP server supports three authentication schemes:
179
+
180
+ | Scheme | How to Authenticate |
181
+ |--------|---------------------|
182
+ | **Basic Auth** | `Authorization: Basic <base64(user:pass)>` |
183
+ | **Bearer Token** | `Authorization: Bearer <apiKey>` |
184
+ | **API Key** | `X-API-Key: <apiKey>` header or `?apiKey=<key>` query param |
185
+
186
+ If `auth.apiKey` is set, all three token-based methods are accepted.
187
+ If `auth.username` + `auth.password` are set, only Basic Auth is accepted.
188
+
189
+ ### S3 Authentication
190
+
191
+ When custom S3 credentials are set (anything other than the default `minioadmin`/`minioadmin`), the S3 server validates requests:
192
+
193
+ | Method | How It Works |
194
+ |--------|-------------|
195
+ | **AWS SigV4** | Standard `Authorization: AWS4-HMAC-SHA256 Credential=<accessKeyId>/...` |
196
+ | **AWS SigV2** | Legacy `Authorization: AWS <accessKeyId>:signature` |
197
+ | **Bearer** | Convenience `Authorization: Bearer <accessKeyId>` |
198
+ | **Pre-signed URL** | `?X-Amz-Credential=<accessKeyId>/...` query param |
199
+
200
+ > With default credentials (`minioadmin`/`minioadmin`), auth is skipped for backward compatibility unless you set `enforceAuth: true` in the S3 config.
201
+
202
+ ---
203
+
204
+ ## API Reference
205
+
206
+ ### FileSharingServer
207
+
208
+ Main orchestrating class.
209
+
210
+ ```javascript
211
+ const server = new FileSharingServer({
212
+ // File serving
213
+ staticDir: process.cwd(), // Directory to serve
214
+ port: 3000, // HTTP port
215
+ host: '0.0.0.0', // Bind address
216
+ enableDirectoryListing: true, // Show directory listing
217
+ cors: true, // Enable CORS
218
+
219
+ // Authentication (or use env vars / .env)
220
+ auth: null, // { apiKey: 'key' } or { username: 'u', password: 'p' }
221
+
222
+ // S3 API
223
+ enableS3: false, // Enable S3-compatible API
224
+ s3Port: 9000, // S3 API port
225
+ s3Config: { // Passed to S3Server
226
+ accessKeyId: 'minioadmin',
227
+ secretAccessKey: 'minioadmin',
228
+ bucketName: 'local',
229
+ region: 'us-east-1',
230
+ enforceAuth: false // Force auth even with default creds
231
+ },
232
+
233
+ // Tunnel
234
+ tunnel: {
235
+ enabled: false, // Start tunnel on server.start()
236
+ service: 'kadi', // 'kadi' | 'ngrok' | 'serveo' | 'localtunnel' | 'pinggy'
237
+ autoFallback: true, // Fall back to other services on failure
238
+ autoReconnect: true, // Auto-reconnect on tunnel drop
239
+ reconnectDelay: 5000, // ms between reconnect attempts
240
+ // KĀDI-specific (or use env vars / .env)
241
+ kadiToken: undefined,
242
+ kadiServer: undefined,
243
+ kadiDomain: undefined,
244
+ kadiPort: undefined, // frps server port (NOT the local port)
245
+ kadiSshPort: undefined,
246
+ kadiMode: undefined, // 'ssh' | 'frpc' | 'auto'
247
+ kadiAgentId: undefined,
248
+ // Ngrok-specific
249
+ ngrokAuthToken: undefined,
250
+ // Advanced
251
+ managerOptions: {} // Extra options passed to TunnelManager
252
+ },
253
+
254
+ // Shutdown
255
+ shutdown: {
256
+ gracefulTimeout: 30000,
257
+ finishActiveDownloads: true,
258
+ forceKillTimeout: 60000
259
+ },
260
+
261
+ // Monitoring
262
+ monitoring: {
263
+ enabled: true,
264
+ dashboard: false, // Enable console dashboard
265
+ webhooks: [] // ['https://hooks.example.com/events']
266
+ }
267
+ });
268
+ ```
269
+
270
+ #### Methods
271
+
272
+ | Method | Returns | Description |
273
+ |--------|---------|-------------|
274
+ | `start()` | `Promise<ServerInfo>` | Start HTTP server (+ S3 and tunnel if enabled) |
275
+ | `stop()` | `Promise<void>` | Gracefully stop all servers and tunnels |
276
+ | `enableTunnel(options?)` | `Promise<TunnelInfo>` | Enable public tunnel after start |
277
+ | `disableTunnel()` | `Promise<void>` | Close active tunnel |
278
+ | `enableS3(options?)` | `Promise<{endpoint, port}>` | Enable S3 API dynamically |
279
+ | `disableS3()` | `Promise<void>` | Disable S3 API |
280
+ | `getInfo()` | `ServerInfo` | Get server info, URLs, and tunnel status |
281
+ | `getStats()` | `DownloadStats` | Get download statistics |
282
+ | `listFiles(subPath?)` | `Promise<FileEntry[]>` | List files in served directory |
283
+ | `addWebhook(url, events?)` | `void` | Register a webhook endpoint |
284
+ | `removeWebhook(url)` | `void` | Remove a webhook |
285
+
286
+ #### Properties
287
+
288
+ | Property | Type | Description |
289
+ |----------|------|-------------|
290
+ | `isRunning` | `boolean` | Whether the server is running |
291
+ | `port` | `number` | Current HTTP port |
292
+ | `tunnelUrl` | `string \| null` | Current public tunnel URL |
293
+ | `staticDir` | `string` | Directory being served |
294
+ | `tunnel` | `object \| null` | Active tunnel info |
295
+ | `tunnelManager` | `TunnelManager` | Underlying tunnel manager |
296
+
297
+ #### Events
298
+
299
+ | Event | Payload | When |
300
+ |-------|---------|------|
301
+ | `started` | `ServerInfo` | Server fully started |
302
+ | `stopping` | — | Shutdown initiated |
303
+ | `stopped` | — | Server fully stopped |
304
+ | `download:start` | `{id, file, ...}` | File download begins |
305
+ | `download:complete` | `{id, file, duration, ...}` | Download finished |
306
+ | `download:error` | `{id, error}` | Download failed |
307
+ | `upload:complete` | `{file, size}` | S3 upload finished |
308
+ | `s3:started` | `{port, endpoint}` | S3 server started |
309
+ | `s3:get` | `{bucket, key}` | S3 GetObject |
310
+ | `s3:put` | `{bucket, key}` | S3 PutObject |
311
+ | `s3:delete` | `{bucket, key}` | S3 DeleteObject |
312
+ | `tunnel:created` | `TunnelInfo` | Tunnel established |
313
+ | `tunnel:closed` | — | Tunnel shut down |
314
+ | `tunnel:error` | `Error` | Tunnel failed (non-fatal) |
315
+ | `http:started` | `{port, host}` | HTTP server listening |
316
+ | `http:error` | `Error` | HTTP error |
317
+
318
+ ### HttpServerProvider
319
+
320
+ Low-level HTTP file server with authentication.
321
+
322
+ ```javascript
323
+ import { HttpServerProvider } from '@kadi.build/file-sharing/http';
324
+
325
+ const httpServer = new HttpServerProvider({
326
+ port: 3000,
327
+ staticDir: './files',
328
+ enableDirectoryListing: true,
329
+ cors: true,
330
+ auth: { apiKey: 'my-key' } // or { username: 'u', password: 'p' }
331
+ });
332
+
333
+ await httpServer.start();
334
+ ```
335
+
336
+ ### S3Server
337
+
338
+ Local S3-compatible API server. Files are stored **on disk**, not on AWS.
339
+
340
+ ```javascript
341
+ import { S3Server } from '@kadi.build/file-sharing/s3';
342
+
343
+ const s3 = new S3Server({
344
+ port: 9000,
345
+ rootDir: './data',
346
+ bucketName: 'local',
347
+ region: 'us-east-1',
348
+ accessKeyId: 'my-key', // default: 'minioadmin'
349
+ secretAccessKey: 'my-secret', // default: 'minioadmin'
350
+ enforceAuth: true // validate even with default creds
351
+ });
352
+
353
+ await s3.start();
354
+ ```
355
+
356
+ **Supported S3 Operations:**
357
+
358
+ - `ListBuckets`, `CreateBucket`, `DeleteBucket`, `HeadBucket`
359
+ - `ListObjects`, `ListObjectsV2`
360
+ - `GetObject` (with range requests)
361
+ - `PutObject`
362
+ - `DeleteObject`
363
+ - `HeadObject`
364
+ - `CreateMultipartUpload`, `UploadPart`, `CompleteMultipartUpload`
365
+
366
+ ### createQuickShare
367
+
368
+ One-liner to share a directory with optional tunnel and auth.
369
+
370
+ ```javascript
371
+ import { createQuickShare } from '@kadi.build/file-sharing';
372
+
373
+ const { server, localUrl, publicUrl } = await createQuickShare('./my-files', {
374
+ port: 3000, // HTTP port (default: 3000)
375
+ tunnel: true, // Enable tunnel (default: false)
376
+ tunnelService: 'kadi', // Tunnel service (default: 'kadi')
377
+ kadiToken: 'token', // Or set KADI_TUNNEL_TOKEN env
378
+ ngrokAuthToken: 'token', // Or set NGROK_AUTHTOKEN env
379
+ auth: { apiKey: 'share-key' }, // Protect downloads
380
+ tunnelOptions: {} // Extra TunnelManager options
381
+ });
382
+
383
+ console.log(`Local: ${localUrl}`);
384
+ console.log(`Public: ${publicUrl}`);
385
+ ```
386
+
387
+ ### DownloadMonitor
388
+
389
+ Track download progress and statistics.
390
+
391
+ ```javascript
392
+ import { DownloadMonitor } from '@kadi.build/file-sharing';
393
+
394
+ const monitor = new DownloadMonitor();
395
+
396
+ monitor.on('download:start', (dl) => console.log(`Started: ${dl.file}`));
397
+ monitor.on('download:progress', (dl) => console.log(`${dl.progress.toFixed(1)}%`));
398
+ monitor.on('download:complete', (dl) => console.log(`Done in ${dl.duration}ms`));
399
+
400
+ monitor.startDownload('id', { file: 'test.txt', totalSize: 1000 });
401
+ monitor.updateProgress('id', 500);
402
+ monitor.completeDownload('id');
403
+
404
+ console.log(monitor.getStats());
405
+ // { activeCount, completedCount, totalBytes, peakConcurrent, ... }
406
+ ```
407
+
408
+ ### ShutdownManager
409
+
410
+ Graceful shutdown with priority-ordered callbacks.
411
+
412
+ ```javascript
413
+ import { ShutdownManager } from '@kadi.build/file-sharing';
414
+
415
+ const sm = new ShutdownManager({ gracefulTimeout: 10000 });
416
+
417
+ sm.register(async () => console.log('Close DB'), 1); // priority 1 = first
418
+ sm.register(async () => console.log('Close HTTP'), 10); // priority 10 = later
419
+
420
+ await sm.shutdown();
421
+ ```
422
+
423
+ ---
424
+
425
+ ## Sub-path Exports
426
+
427
+ ```javascript
428
+ // Main export — all classes and utilities
429
+ import { FileSharingServer, createQuickShare } from '@kadi.build/file-sharing';
430
+
431
+ // S3 server only
432
+ import { S3Server } from '@kadi.build/file-sharing/s3';
433
+
434
+ // HTTP server only
435
+ import { HttpServerProvider } from '@kadi.build/file-sharing/http';
436
+
437
+ // Re-exports from dependencies (convenience)
438
+ import { FileManager, createFileManager, TunnelManager } from '@kadi.build/file-sharing';
439
+ ```
440
+
441
+ ---
442
+
443
+ ## Tunnel Services
444
+
445
+ KĀDI is the default and recommended tunnel service. It supports two connection modes:
446
+
447
+ | Mode | How It Works | Requirements |
448
+ |------|-------------|--------------|
449
+ | **SSH** (default) | `ssh -R` through KĀDI's SSH gateway — zero-dependency | SSH client in `$PATH` |
450
+ | **frpc** | Uses the frp client binary for higher reliability | `frpc` binary in `$PATH` |
451
+ | **auto** | Prefers `frpc` if available, falls back to SSH | — |
452
+
453
+ Set `KADI_TUNNEL_MODE=ssh` (or `frpc`) in your `.env` to force a specific mode.
454
+
455
+ If KĀDI credentials are not provided, the tunnel will automatically fall back to free services (serveo → localtunnel → pinggy → localhost.run) unless `autoFallback: false` is set.
456
+
457
+ ---
458
+
459
+ ## Dependencies
460
+
461
+ | Package | Purpose |
462
+ |---------|---------|
463
+ | `@kadi.build/file-manager` | File management utilities |
464
+ | `@kadi.build/tunnel-services` | Multi-provider tunnel management |
465
+ | `express` | HTTP framework |
466
+ | `cors` | CORS middleware |
467
+ | `mime-types` | MIME type detection |
468
+ | `xml2js` | XML generation for S3 responses |
469
+ | `chalk` | Console styling |
470
+
471
+ ## Requirements
472
+
473
+ - Node.js ≥ 18.0.0
474
+
475
+ ## License
476
+
477
+ MIT
package/package.json ADDED
@@ -0,0 +1,60 @@
1
+ {
2
+ "name": "@kadi.build/file-sharing",
3
+ "version": "1.0.0",
4
+ "description": "File sharing service with tunneling and local S3-compatible interface",
5
+ "type": "module",
6
+ "main": "src/index.js",
7
+ "exports": {
8
+ ".": {
9
+ "import": "./src/index.js",
10
+ "types": "./types/index.d.ts"
11
+ },
12
+ "./s3": {
13
+ "import": "./src/S3Server.js"
14
+ },
15
+ "./http": {
16
+ "import": "./src/HttpServerProvider.js"
17
+ }
18
+ },
19
+ "files": [
20
+ "src/",
21
+ "types/",
22
+ "README.md"
23
+ ],
24
+ "scripts": {
25
+ "test": "node tests/run-all-tests.js",
26
+ "test:smoke": "node tests/test-dependency-smoke.js",
27
+ "test:http": "node tests/test-http-server.js",
28
+ "test:s3": "node tests/test-s3-server.js",
29
+ "test:integration": "node tests/test-file-sharing.js",
30
+ "test:monitor": "node tests/test-download-monitor.js",
31
+ "test:shutdown": "node tests/test-shutdown-manager.js",
32
+ "test:streaming": "node tests/test-streaming.js"
33
+ },
34
+ "keywords": [
35
+ "file-sharing",
36
+ "http-server",
37
+ "s3-compatible",
38
+ "tunnel",
39
+ "kadi",
40
+ "file-server",
41
+ "download-monitor"
42
+ ],
43
+ "dependencies": {
44
+ "@kadi.build/file-manager": "^1.0.0",
45
+ "@kadi.build/tunnel-services": "^1.0.2",
46
+ "chalk": "^5.3.0",
47
+ "cors": "^2.8.5",
48
+ "express": "^4.18.0",
49
+ "mime-types": "^2.1.35",
50
+ "xml2js": "^0.5.0"
51
+ },
52
+ "devDependencies": {
53
+ "@aws-sdk/client-s3": "^3.0.0",
54
+ "supertest": "^6.0.0"
55
+ },
56
+ "engines": {
57
+ "node": ">=18.0.0"
58
+ },
59
+ "license": "MIT"
60
+ }