fwdcast 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.
@@ -0,0 +1,428 @@
1
+ "use strict";
2
+ /**
3
+ * Tunnel Client Module
4
+ *
5
+ * Handles WebSocket connection to the relay server, message handling,
6
+ * and file streaming for the fwdcast CLI.
7
+ *
8
+ * Requirements: 1.5, 1.6, 3.1, 3.2, 3.6, 5.1, 5.3, 5.4, 5.5
9
+ */
10
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
11
+ if (k2 === undefined) k2 = k;
12
+ var desc = Object.getOwnPropertyDescriptor(m, k);
13
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
14
+ desc = { enumerable: true, get: function() { return m[k]; } };
15
+ }
16
+ Object.defineProperty(o, k2, desc);
17
+ }) : (function(o, m, k, k2) {
18
+ if (k2 === undefined) k2 = k;
19
+ o[k2] = m[k];
20
+ }));
21
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
22
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
23
+ }) : function(o, v) {
24
+ o["default"] = v;
25
+ });
26
+ var __importStar = (this && this.__importStar) || (function () {
27
+ var ownKeys = function(o) {
28
+ ownKeys = Object.getOwnPropertyNames || function (o) {
29
+ var ar = [];
30
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
31
+ return ar;
32
+ };
33
+ return ownKeys(o);
34
+ };
35
+ return function (mod) {
36
+ if (mod && mod.__esModule) return mod;
37
+ var result = {};
38
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
39
+ __setModuleDefault(result, mod);
40
+ return result;
41
+ };
42
+ })();
43
+ var __importDefault = (this && this.__importDefault) || function (mod) {
44
+ return (mod && mod.__esModule) ? mod : { "default": mod };
45
+ };
46
+ Object.defineProperty(exports, "__esModule", { value: true });
47
+ exports.TunnelClient = void 0;
48
+ exports.createTunnelClient = createTunnelClient;
49
+ const ws_1 = __importDefault(require("ws"));
50
+ const fs = __importStar(require("fs"));
51
+ const path = __importStar(require("path"));
52
+ const mime_types_1 = require("mime-types");
53
+ const archiver_1 = __importDefault(require("archiver"));
54
+ const protocol_1 = require("./protocol");
55
+ const scanner_1 = require("./scanner");
56
+ const html_generator_1 = require("./html-generator");
57
+ /**
58
+ * Chunk size for file streaming (64KB)
59
+ */
60
+ const CHUNK_SIZE = 64 * 1024;
61
+ /**
62
+ * TunnelClient manages the WebSocket connection to the relay server
63
+ * and handles incoming requests by serving files or directory listings.
64
+ */
65
+ class TunnelClient {
66
+ ws = null;
67
+ config;
68
+ connected = false;
69
+ sessionId = null;
70
+ registrationPromise = null;
71
+ constructor(config) {
72
+ this.config = config;
73
+ }
74
+ /**
75
+ * Connect to the relay server and register the session.
76
+ * Returns the public URL for the shared directory.
77
+ *
78
+ * Requirements: 1.5, 1.6, 5.1
79
+ */
80
+ async connect() {
81
+ return new Promise((resolve, reject) => {
82
+ this.registrationPromise = { resolve, reject };
83
+ try {
84
+ this.ws = new ws_1.default(this.config.relayUrl);
85
+ this.ws.on('open', () => {
86
+ this.connected = true;
87
+ this.sendRegisterMessage();
88
+ });
89
+ this.ws.on('message', (data) => {
90
+ this.handleMessage(data);
91
+ });
92
+ this.ws.on('close', () => {
93
+ this.connected = false;
94
+ // Only call onDisconnect if we were successfully registered
95
+ // (not during initial connection attempts)
96
+ if (!this.registrationPromise && this.config.onDisconnect) {
97
+ this.config.onDisconnect();
98
+ }
99
+ // If still trying to register, reject the promise
100
+ if (this.registrationPromise) {
101
+ this.registrationPromise.reject(new Error('Connection closed during registration'));
102
+ this.registrationPromise = null;
103
+ }
104
+ });
105
+ this.ws.on('error', (error) => {
106
+ if (this.registrationPromise) {
107
+ this.registrationPromise.reject(error);
108
+ this.registrationPromise = null;
109
+ }
110
+ if (this.config.onError) {
111
+ this.config.onError(error);
112
+ }
113
+ });
114
+ }
115
+ catch (error) {
116
+ reject(error);
117
+ }
118
+ });
119
+ }
120
+ /**
121
+ * Disconnect from the relay server
122
+ */
123
+ disconnect() {
124
+ if (this.ws) {
125
+ this.ws.close();
126
+ this.ws = null;
127
+ this.connected = false;
128
+ }
129
+ }
130
+ /**
131
+ * Check if the client is connected
132
+ */
133
+ isConnected() {
134
+ return this.connected && this.ws !== null && this.ws.readyState === ws_1.default.OPEN;
135
+ }
136
+ /**
137
+ * Send the register message to the relay server
138
+ * Requirements: 5.1
139
+ */
140
+ sendRegisterMessage() {
141
+ const message = (0, protocol_1.createRegisterMessage)(this.config.basePath, this.config.expiresAt);
142
+ this.send(message);
143
+ }
144
+ /**
145
+ * Handle incoming WebSocket messages
146
+ */
147
+ handleMessage(data) {
148
+ const messageStr = data.toString();
149
+ const message = (0, protocol_1.deserializeMessage)(messageStr);
150
+ if (!message) {
151
+ console.error('Received invalid message:', messageStr);
152
+ return;
153
+ }
154
+ if ((0, protocol_1.isRegisteredMessage)(message)) {
155
+ this.handleRegistered(message);
156
+ }
157
+ else if ((0, protocol_1.isRequestMessage)(message)) {
158
+ this.handleRequest(message);
159
+ }
160
+ else if ((0, protocol_1.isExpiredMessage)(message)) {
161
+ this.handleExpired();
162
+ }
163
+ }
164
+ /**
165
+ * Handle registration response from relay
166
+ * Requirements: 1.6
167
+ */
168
+ handleRegistered(message) {
169
+ // Store session ID for use in directory listings
170
+ this.sessionId = message.sessionId;
171
+ const result = {
172
+ sessionId: message.sessionId,
173
+ url: message.url,
174
+ };
175
+ if (this.registrationPromise) {
176
+ this.registrationPromise.resolve(result);
177
+ this.registrationPromise = null;
178
+ }
179
+ if (this.config.onUrl) {
180
+ this.config.onUrl(message.url);
181
+ }
182
+ }
183
+ /**
184
+ * Handle incoming request from relay
185
+ * Routes to file server or directory listing
186
+ *
187
+ * Requirements: 3.1, 3.2, 5.3, 5.4, 5.5
188
+ */
189
+ async handleRequest(message) {
190
+ const { id, method, path: requestPath } = message;
191
+ // Normalize the request path
192
+ const normalizedPath = this.normalizePath(requestPath);
193
+ // Check for special ZIP download request
194
+ if (normalizedPath === '__download__.zip' || normalizedPath.endsWith('/__download__.zip')) {
195
+ const dirPath = normalizedPath.replace('/__download__.zip', '').replace('__download__.zip', '');
196
+ await this.serveZipDownload(id, dirPath);
197
+ return;
198
+ }
199
+ const absolutePath = path.join(this.config.basePath, normalizedPath);
200
+ // Security check: ensure the path is within the base directory
201
+ const resolvedBase = path.resolve(this.config.basePath);
202
+ const resolvedPath = path.resolve(absolutePath);
203
+ if (!resolvedPath.startsWith(resolvedBase)) {
204
+ this.sendErrorResponse(id, 403, 'Forbidden');
205
+ return;
206
+ }
207
+ try {
208
+ // Check if path exists
209
+ const stat = await fs.promises.stat(resolvedPath);
210
+ if (stat.isDirectory()) {
211
+ // Serve directory listing
212
+ await this.serveDirectoryListing(id, normalizedPath);
213
+ }
214
+ else {
215
+ // Serve file
216
+ await this.serveFile(id, resolvedPath, method);
217
+ }
218
+ }
219
+ catch (error) {
220
+ if (error.code === 'ENOENT') {
221
+ this.sendErrorResponse(id, 404, 'Not Found');
222
+ }
223
+ else {
224
+ console.error('Error handling request:', error);
225
+ this.sendErrorResponse(id, 500, 'Internal Server Error');
226
+ }
227
+ }
228
+ }
229
+ /**
230
+ * Handle session expired message
231
+ */
232
+ handleExpired() {
233
+ if (this.config.onExpired) {
234
+ this.config.onExpired();
235
+ }
236
+ this.disconnect();
237
+ }
238
+ /**
239
+ * Serve a ZIP download of a directory
240
+ */
241
+ async serveZipDownload(requestId, dirPath) {
242
+ try {
243
+ const absoluteDirPath = dirPath
244
+ ? path.join(this.config.basePath, dirPath)
245
+ : this.config.basePath;
246
+ // Get directory name for the ZIP filename
247
+ const dirName = dirPath ? path.basename(dirPath) : 'files';
248
+ // Send response headers (chunked transfer since we don't know size)
249
+ this.sendResponse(requestId, 200, {
250
+ 'Content-Type': 'application/zip',
251
+ 'Content-Disposition': `attachment; filename="${dirName}.zip"`,
252
+ 'Transfer-Encoding': 'chunked',
253
+ });
254
+ // Create archive
255
+ const archive = (0, archiver_1.default)('zip', { zlib: { level: 5 } });
256
+ // Collect chunks and send them
257
+ archive.on('data', (chunk) => {
258
+ this.sendData(requestId, chunk);
259
+ });
260
+ archive.on('end', () => {
261
+ this.sendEnd(requestId);
262
+ });
263
+ archive.on('error', (err) => {
264
+ console.error('Archive error:', err);
265
+ this.sendEnd(requestId);
266
+ });
267
+ // Add directory contents to archive
268
+ archive.directory(absoluteDirPath, false);
269
+ // Finalize the archive
270
+ await archive.finalize();
271
+ }
272
+ catch (error) {
273
+ console.error('Error creating ZIP:', error);
274
+ this.sendErrorResponse(requestId, 500, 'Failed to create ZIP');
275
+ }
276
+ }
277
+ /**
278
+ * Serve a directory listing as HTML
279
+ * Requirements: 3.5
280
+ *
281
+ * Dynamically scans the directory on each request to reflect
282
+ * any files added/removed since the session started.
283
+ */
284
+ async serveDirectoryListing(requestId, dirPath) {
285
+ try {
286
+ // Dynamically scan the directory for current contents
287
+ const absoluteDirPath = path.join(this.config.basePath, dirPath);
288
+ const children = await (0, scanner_1.scanDirectoryShallow)(absoluteDirPath, this.config.basePath);
289
+ // Generate HTML with session ID for correct links
290
+ const html = (0, html_generator_1.generateDirectoryHtml)(children, dirPath, this.sessionId || undefined);
291
+ const htmlBuffer = Buffer.from(html, 'utf-8');
292
+ // Send response headers
293
+ this.sendResponse(requestId, 200, {
294
+ 'Content-Type': 'text/html; charset=utf-8',
295
+ 'Content-Length': htmlBuffer.length.toString(),
296
+ });
297
+ // Send data
298
+ this.sendData(requestId, htmlBuffer);
299
+ // Send end
300
+ this.sendEnd(requestId);
301
+ }
302
+ catch (error) {
303
+ console.error('Error serving directory listing:', error);
304
+ this.sendErrorResponse(requestId, 500, 'Internal Server Error');
305
+ }
306
+ }
307
+ /**
308
+ * Stream a file to the relay
309
+ * Requirements: 3.6
310
+ */
311
+ async serveFile(requestId, filePath, method) {
312
+ const stat = await fs.promises.stat(filePath);
313
+ const contentType = this.getContentType(filePath);
314
+ // Send response headers
315
+ this.sendResponse(requestId, 200, {
316
+ 'Content-Type': contentType,
317
+ 'Content-Length': stat.size.toString(),
318
+ });
319
+ // For HEAD requests, don't send body
320
+ if (method === 'HEAD') {
321
+ this.sendEnd(requestId);
322
+ return;
323
+ }
324
+ // Stream file contents
325
+ await this.streamFile(requestId, filePath);
326
+ }
327
+ /**
328
+ * Stream file contents in chunks
329
+ * Requirements: 3.6, 5.4, 5.5
330
+ */
331
+ streamFile(requestId, filePath) {
332
+ return new Promise((resolve, reject) => {
333
+ const readStream = fs.createReadStream(filePath, {
334
+ highWaterMark: CHUNK_SIZE,
335
+ });
336
+ readStream.on('data', (chunk) => {
337
+ const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
338
+ this.sendData(requestId, buffer);
339
+ });
340
+ readStream.on('end', () => {
341
+ this.sendEnd(requestId);
342
+ resolve();
343
+ });
344
+ readStream.on('error', (error) => {
345
+ console.error('Error streaming file:', error);
346
+ this.sendEnd(requestId);
347
+ reject(error);
348
+ });
349
+ });
350
+ }
351
+ /**
352
+ * Send an error response
353
+ */
354
+ sendErrorResponse(requestId, status, message) {
355
+ const html = `<!DOCTYPE html>
356
+ <html>
357
+ <head><title>${status} ${message}</title></head>
358
+ <body><h1>${status} ${message}</h1></body>
359
+ </html>`;
360
+ const htmlBuffer = Buffer.from(html, 'utf-8');
361
+ this.sendResponse(requestId, status, {
362
+ 'Content-Type': 'text/html; charset=utf-8',
363
+ 'Content-Length': htmlBuffer.length.toString(),
364
+ });
365
+ this.sendData(requestId, htmlBuffer);
366
+ this.sendEnd(requestId);
367
+ }
368
+ /**
369
+ * Send a response message
370
+ * Requirements: 5.3
371
+ */
372
+ sendResponse(id, status, headers) {
373
+ const message = (0, protocol_1.createResponseMessage)(id, status, headers);
374
+ this.send(message);
375
+ }
376
+ /**
377
+ * Send a data message with base64 encoded chunk
378
+ * Requirements: 5.4
379
+ */
380
+ sendData(id, chunk) {
381
+ const message = (0, protocol_1.createDataMessage)(id, chunk.toString('base64'));
382
+ this.send(message);
383
+ }
384
+ /**
385
+ * Send an end message
386
+ * Requirements: 5.5
387
+ */
388
+ sendEnd(id) {
389
+ const message = (0, protocol_1.createEndMessage)(id);
390
+ this.send(message);
391
+ }
392
+ /**
393
+ * Send a message through the WebSocket
394
+ */
395
+ send(message) {
396
+ if (this.ws && this.ws.readyState === ws_1.default.OPEN) {
397
+ this.ws.send((0, protocol_1.serializeMessage)(message));
398
+ }
399
+ }
400
+ /**
401
+ * Normalize a request path
402
+ */
403
+ normalizePath(requestPath) {
404
+ // Remove leading slash and decode URI components
405
+ let normalized = decodeURIComponent(requestPath).replace(/^\/+/, '');
406
+ // Remove trailing slash
407
+ normalized = normalized.replace(/\/+$/, '');
408
+ return normalized;
409
+ }
410
+ /**
411
+ * Get the content type for a file based on its extension
412
+ */
413
+ getContentType(filePath) {
414
+ const mimeType = (0, mime_types_1.lookup)(filePath);
415
+ return mimeType || 'application/octet-stream';
416
+ }
417
+ }
418
+ exports.TunnelClient = TunnelClient;
419
+ /**
420
+ * Create and connect a tunnel client
421
+ * Convenience function for simple usage
422
+ */
423
+ async function createTunnelClient(config) {
424
+ const client = new TunnelClient(config);
425
+ const result = await client.connect();
426
+ return { client, result };
427
+ }
428
+ //# sourceMappingURL=tunnel-client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tunnel-client.js","sourceRoot":"","sources":["../src/tunnel-client.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,53 @@
1
+ import { DirectoryEntry, ScanResult } from './scanner';
2
+ /**
3
+ * Default size limits for validation
4
+ */
5
+ export declare const DEFAULT_MAX_TOTAL_SIZE: number;
6
+ export declare const DEFAULT_MAX_FILE_SIZE: number;
7
+ /**
8
+ * Validation error types
9
+ */
10
+ export type ValidationErrorType = 'total_size_exceeded' | 'file_size_exceeded';
11
+ /**
12
+ * Details about a validation error
13
+ */
14
+ export interface ValidationError {
15
+ type: ValidationErrorType;
16
+ message: string;
17
+ /** The file that caused the error (for file_size_exceeded) */
18
+ file?: DirectoryEntry;
19
+ /** The actual size that exceeded the limit */
20
+ actualSize: number;
21
+ /** The limit that was exceeded */
22
+ limit: number;
23
+ }
24
+ /**
25
+ * Result of validating a scan result
26
+ */
27
+ export interface ValidationResult {
28
+ valid: boolean;
29
+ errors: ValidationError[];
30
+ }
31
+ /**
32
+ * Options for validation
33
+ */
34
+ export interface ValidationOptions {
35
+ maxTotalSize?: number;
36
+ maxFileSize?: number;
37
+ }
38
+ /**
39
+ * Validates a scan result against size limits.
40
+ *
41
+ * @param scanResult - The scan result to validate
42
+ * @param options - Optional size limits (defaults to 100MB total, 50MB per file)
43
+ * @returns ValidationResult indicating if the scan is valid and any errors
44
+ */
45
+ export declare function validateScanResult(scanResult: ScanResult, options?: ValidationOptions): ValidationResult;
46
+ /**
47
+ * Formats a size in bytes to a human-readable string.
48
+ *
49
+ * @param bytes - Size in bytes
50
+ * @returns Human-readable size string (e.g., "1.5 MB")
51
+ */
52
+ export declare function formatSize(bytes: number): string;
53
+ //# sourceMappingURL=validator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validator.d.ts","sourceRoot":"","sources":["../src/validator.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AAEvD;;GAEG;AACH,eAAO,MAAM,sBAAsB,QAAoB,CAAC;AACxD,eAAO,MAAM,qBAAqB,QAAmB,CAAC;AAEtD;;GAEG;AACH,MAAM,MAAM,mBAAmB,GAAG,qBAAqB,GAAG,oBAAoB,CAAC;AAE/E;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,mBAAmB,CAAC;IAC1B,OAAO,EAAE,MAAM,CAAC;IAChB,8DAA8D;IAC9D,IAAI,CAAC,EAAE,cAAc,CAAC;IACtB,8CAA8C;IAC9C,UAAU,EAAE,MAAM,CAAC;IACnB,kCAAkC;IAClC,KAAK,EAAE,MAAM,CAAC;CACf;AAED;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,OAAO,CAAC;IACf,MAAM,EAAE,eAAe,EAAE,CAAC;CAC3B;AAED;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAChC,UAAU,EAAE,UAAU,EACtB,OAAO,GAAE,iBAAsB,GAC9B,gBAAgB,CAgClB;AAED;;;;;GAKG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAQhD"}
@@ -0,0 +1,65 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DEFAULT_MAX_FILE_SIZE = exports.DEFAULT_MAX_TOTAL_SIZE = void 0;
4
+ exports.validateScanResult = validateScanResult;
5
+ exports.formatSize = formatSize;
6
+ /**
7
+ * Default size limits for validation
8
+ */
9
+ exports.DEFAULT_MAX_TOTAL_SIZE = 100 * 1024 * 1024; // 100 MB
10
+ exports.DEFAULT_MAX_FILE_SIZE = 50 * 1024 * 1024; // 50 MB
11
+ /**
12
+ * Validates a scan result against size limits.
13
+ *
14
+ * @param scanResult - The scan result to validate
15
+ * @param options - Optional size limits (defaults to 100MB total, 50MB per file)
16
+ * @returns ValidationResult indicating if the scan is valid and any errors
17
+ */
18
+ function validateScanResult(scanResult, options = {}) {
19
+ const maxTotalSize = options.maxTotalSize ?? exports.DEFAULT_MAX_TOTAL_SIZE;
20
+ const maxFileSize = options.maxFileSize ?? exports.DEFAULT_MAX_FILE_SIZE;
21
+ const errors = [];
22
+ // Check each file against the individual file size limit
23
+ for (const entry of scanResult.entries) {
24
+ if (!entry.isDirectory && entry.size > maxFileSize) {
25
+ errors.push({
26
+ type: 'file_size_exceeded',
27
+ message: `File "${entry.relativePath}" exceeds the ${formatSize(maxFileSize)} limit (${formatSize(entry.size)})`,
28
+ file: entry,
29
+ actualSize: entry.size,
30
+ limit: maxFileSize,
31
+ });
32
+ }
33
+ }
34
+ // Check total size against the total size limit
35
+ if (scanResult.totalSize > maxTotalSize) {
36
+ errors.push({
37
+ type: 'total_size_exceeded',
38
+ message: `Total directory size ${formatSize(scanResult.totalSize)} exceeds the ${formatSize(maxTotalSize)} limit`,
39
+ actualSize: scanResult.totalSize,
40
+ limit: maxTotalSize,
41
+ });
42
+ }
43
+ return {
44
+ valid: errors.length === 0,
45
+ errors,
46
+ };
47
+ }
48
+ /**
49
+ * Formats a size in bytes to a human-readable string.
50
+ *
51
+ * @param bytes - Size in bytes
52
+ * @returns Human-readable size string (e.g., "1.5 MB")
53
+ */
54
+ function formatSize(bytes) {
55
+ if (bytes < 1024) {
56
+ return `${bytes} B`;
57
+ }
58
+ else if (bytes < 1024 * 1024) {
59
+ return `${(bytes / 1024).toFixed(1)} KB`;
60
+ }
61
+ else {
62
+ return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
63
+ }
64
+ }
65
+ //# sourceMappingURL=validator.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validator.js","sourceRoot":"","sources":["../src/validator.ts"],"names":[],"mappings":";;;AAkDA,gDAmCC;AAQD,gCAQC;AAnGD;;GAEG;AACU,QAAA,sBAAsB,GAAG,GAAG,GAAG,IAAI,GAAG,IAAI,CAAC,CAAC,SAAS;AACrD,QAAA,qBAAqB,GAAG,EAAE,GAAG,IAAI,GAAG,IAAI,CAAC,CAAG,QAAQ;AAqCjE;;;;;;GAMG;AACH,SAAgB,kBAAkB,CAChC,UAAsB,EACtB,UAA6B,EAAE;IAE/B,MAAM,YAAY,GAAG,OAAO,CAAC,YAAY,IAAI,8BAAsB,CAAC;IACpE,MAAM,WAAW,GAAG,OAAO,CAAC,WAAW,IAAI,6BAAqB,CAAC;IACjE,MAAM,MAAM,GAAsB,EAAE,CAAC;IAErC,yDAAyD;IACzD,KAAK,MAAM,KAAK,IAAI,UAAU,CAAC,OAAO,EAAE,CAAC;QACvC,IAAI,CAAC,KAAK,CAAC,WAAW,IAAI,KAAK,CAAC,IAAI,GAAG,WAAW,EAAE,CAAC;YACnD,MAAM,CAAC,IAAI,CAAC;gBACV,IAAI,EAAE,oBAAoB;gBAC1B,OAAO,EAAE,SAAS,KAAK,CAAC,YAAY,iBAAiB,UAAU,CAAC,WAAW,CAAC,WAAW,UAAU,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG;gBAChH,IAAI,EAAE,KAAK;gBACX,UAAU,EAAE,KAAK,CAAC,IAAI;gBACtB,KAAK,EAAE,WAAW;aACnB,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IAED,gDAAgD;IAChD,IAAI,UAAU,CAAC,SAAS,GAAG,YAAY,EAAE,CAAC;QACxC,MAAM,CAAC,IAAI,CAAC;YACV,IAAI,EAAE,qBAAqB;YAC3B,OAAO,EAAE,wBAAwB,UAAU,CAAC,UAAU,CAAC,SAAS,CAAC,gBAAgB,UAAU,CAAC,YAAY,CAAC,QAAQ;YACjH,UAAU,EAAE,UAAU,CAAC,SAAS;YAChC,KAAK,EAAE,YAAY;SACpB,CAAC,CAAC;IACL,CAAC;IAED,OAAO;QACL,KAAK,EAAE,MAAM,CAAC,MAAM,KAAK,CAAC;QAC1B,MAAM;KACP,CAAC;AACJ,CAAC;AAED;;;;;GAKG;AACH,SAAgB,UAAU,CAAC,KAAa;IACtC,IAAI,KAAK,GAAG,IAAI,EAAE,CAAC;QACjB,OAAO,GAAG,KAAK,IAAI,CAAC;IACtB,CAAC;SAAM,IAAI,KAAK,GAAG,IAAI,GAAG,IAAI,EAAE,CAAC;QAC/B,OAAO,GAAG,CAAC,KAAK,GAAG,IAAI,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC;IAC3C,CAAC;SAAM,CAAC;QACN,OAAO,GAAG,CAAC,KAAK,GAAG,CAAC,IAAI,GAAG,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC;IACpD,CAAC;AACH,CAAC"}
package/package.json ADDED
@@ -0,0 +1,64 @@
1
+ {
2
+ "name": "fwdcast",
3
+ "version": "1.0.0",
4
+ "description": "Temporary file sharing - stream local files as a public website without uploading",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "bin": {
8
+ "fwdcast": "dist/index.js"
9
+ },
10
+ "files": [
11
+ "dist/**/*",
12
+ "scripts/**/*"
13
+ ],
14
+ "scripts": {
15
+ "build": "tsc",
16
+ "prebuild": "rm -rf dist",
17
+ "prepublishOnly": "npm run build",
18
+ "postinstall": "node scripts/postinstall.js || true",
19
+ "start": "node dist/index.js",
20
+ "dev": "ts-node src/index.ts",
21
+ "test": "vitest --run",
22
+ "test:watch": "vitest"
23
+ },
24
+ "keywords": [
25
+ "file-sharing",
26
+ "tunnel",
27
+ "temporary",
28
+ "streaming",
29
+ "share",
30
+ "local",
31
+ "websocket",
32
+ "cli",
33
+ "npx"
34
+ ],
35
+ "author": "vamsiy",
36
+ "repository": {
37
+ "type": "git",
38
+ "url": "git+https://github.com/vamsiy78/fwdcast.git"
39
+ },
40
+ "homepage": "https://github.com/vamsiy78/fwdcast#readme",
41
+ "bugs": {
42
+ "url": "https://github.com/vamsiy78/fwdcast/issues"
43
+ },
44
+ "engines": {
45
+ "node": ">=18.0.0"
46
+ },
47
+ "license": "MIT",
48
+ "dependencies": {
49
+ "archiver": "^7.0.1",
50
+ "commander": "^12.1.0",
51
+ "mime-types": "^3.0.2",
52
+ "ws": "^8.18.0"
53
+ },
54
+ "devDependencies": {
55
+ "@types/archiver": "^7.0.0",
56
+ "@types/mime-types": "^3.0.1",
57
+ "@types/node": "^22.10.0",
58
+ "@types/ws": "^8.5.13",
59
+ "fast-check": "^3.23.0",
60
+ "ts-node": "^10.9.2",
61
+ "typescript": "^5.7.0",
62
+ "vitest": "^2.1.0"
63
+ }
64
+ }
@@ -0,0 +1,23 @@
1
+ #!/usr/bin/env node
2
+
3
+ // Only show message for global installs
4
+ if (!process.env.npm_config_global) {
5
+ process.exit(0);
6
+ }
7
+
8
+ console.log(`
9
+ ┌─────────────────────────────────────────────────┐
10
+ │ │
11
+ │ 📡 fwdcast installed successfully! │
12
+ │ │
13
+ │ Quick start: │
14
+ │ fwdcast Share current folder │
15
+ │ fwdcast ~/Downloads Share specific folder │
16
+ │ │
17
+ │ Help: │
18
+ │ fwdcast --help │
19
+ │ │
20
+ │ GitHub: github.com/vamsiy78/fwdcast │
21
+ │ │
22
+ └─────────────────────────────────────────────────┘
23
+ `);