api-ape 3.0.1 → 4.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 +58 -570
- package/client/README.md +73 -14
- package/client/auth/crypto/aead.js +214 -0
- package/client/auth/crypto/constants.js +32 -0
- package/client/auth/crypto/encoding.js +104 -0
- package/client/auth/crypto/files.md +27 -0
- package/client/auth/crypto/kdf.js +217 -0
- package/client/auth/crypto-utils.js +118 -0
- package/client/auth/files.md +52 -0
- package/client/auth/key-recovery.js +288 -0
- package/client/auth/recovery/constants.js +37 -0
- package/client/auth/recovery/files.md +23 -0
- package/client/auth/recovery/key-derivation.js +61 -0
- package/client/auth/recovery/sss-browser.js +189 -0
- package/client/auth/share-storage.js +205 -0
- package/client/auth/storage/constants.js +18 -0
- package/client/auth/storage/db.js +132 -0
- package/client/auth/storage/files.md +27 -0
- package/client/auth/storage/keys.js +173 -0
- package/client/auth/storage/shares.js +200 -0
- package/client/browser.js +190 -23
- package/client/connectSocket.js +418 -988
- package/client/connection/README.md +23 -0
- package/client/connection/fileDownload.js +256 -0
- package/client/connection/fileHandling.js +450 -0
- package/client/connection/fileUtils.js +346 -0
- package/client/connection/files.md +71 -0
- package/client/connection/messageHandler.js +105 -0
- package/client/connection/network.js +350 -0
- package/client/connection/proxy.js +233 -0
- package/client/connection/sender.js +333 -0
- package/client/connection/state.js +321 -0
- package/client/connection/subscriptions.js +151 -0
- package/client/files.md +53 -0
- package/client/index.js +298 -142
- package/client/transports/README.md +50 -0
- package/client/transports/files.md +41 -0
- package/client/transports/streamParser.js +195 -0
- package/client/transports/streaming.js +555 -202
- package/dist/ape.js +6 -1
- package/dist/ape.js.map +4 -4
- package/index.d.ts +38 -16
- package/package.json +32 -7
- package/server/README.md +287 -53
- package/server/adapters/README.md +28 -19
- package/server/adapters/files.md +68 -0
- package/server/adapters/firebase.js +543 -160
- package/server/adapters/index.js +362 -112
- package/server/adapters/mongo.js +530 -140
- package/server/adapters/postgres.js +534 -155
- package/server/adapters/redis.js +508 -143
- package/server/adapters/supabase.js +555 -186
- package/server/client/README.md +43 -0
- package/server/client/connection.js +586 -0
- package/server/client/files.md +40 -0
- package/server/client/index.js +342 -0
- package/server/files.md +54 -0
- package/server/index.js +332 -27
- package/server/lib/README.md +26 -0
- package/server/lib/broadcast/clients.js +219 -0
- package/server/lib/broadcast/files.md +58 -0
- package/server/lib/broadcast/index.js +57 -0
- package/server/lib/broadcast/publishProxy.js +110 -0
- package/server/lib/broadcast/pubsub.js +137 -0
- package/server/lib/broadcast/sendProxy.js +103 -0
- package/server/lib/bun.js +315 -99
- package/server/lib/fileTransfer/README.md +63 -0
- package/server/lib/fileTransfer/files.md +30 -0
- package/server/lib/fileTransfer/streaming.js +435 -0
- package/server/lib/fileTransfer.js +710 -326
- package/server/lib/files.md +111 -0
- package/server/lib/httpUtils.js +283 -0
- package/server/lib/loader.js +208 -7
- package/server/lib/longPolling/README.md +63 -0
- package/server/lib/longPolling/files.md +44 -0
- package/server/lib/longPolling/getHandler.js +365 -0
- package/server/lib/longPolling/postHandler.js +327 -0
- package/server/lib/longPolling.js +174 -221
- package/server/lib/main.js +369 -532
- package/server/lib/runtimes/README.md +42 -0
- package/server/lib/runtimes/bun.js +586 -0
- package/server/lib/runtimes/files.md +56 -0
- package/server/lib/runtimes/node.js +511 -0
- package/server/lib/wiring.js +539 -98
- package/server/lib/ws/README.md +35 -0
- package/server/lib/ws/adapters/README.md +54 -0
- package/server/lib/ws/adapters/bun.js +538 -170
- package/server/lib/ws/adapters/deno.js +623 -149
- package/server/lib/ws/adapters/files.md +42 -0
- package/server/lib/ws/files.md +74 -0
- package/server/lib/ws/frames.js +532 -154
- package/server/lib/ws/index.js +207 -10
- package/server/lib/ws/server.js +385 -92
- package/server/lib/ws/socket.js +549 -181
- package/server/lib/wsProvider.js +363 -89
- package/server/plugins/binary.js +282 -0
- package/server/security/README.md +92 -0
- package/server/security/auth/README.md +319 -0
- package/server/security/auth/adapters/files.md +95 -0
- package/server/security/auth/adapters/ldap/constants.js +37 -0
- package/server/security/auth/adapters/ldap/files.md +19 -0
- package/server/security/auth/adapters/ldap/helpers.js +111 -0
- package/server/security/auth/adapters/ldap.js +353 -0
- package/server/security/auth/adapters/oauth2/constants.js +41 -0
- package/server/security/auth/adapters/oauth2/files.md +19 -0
- package/server/security/auth/adapters/oauth2/helpers.js +123 -0
- package/server/security/auth/adapters/oauth2.js +273 -0
- package/server/security/auth/adapters/opaque-handlers.js +314 -0
- package/server/security/auth/adapters/opaque.js +205 -0
- package/server/security/auth/adapters/saml/constants.js +52 -0
- package/server/security/auth/adapters/saml/files.md +19 -0
- package/server/security/auth/adapters/saml/helpers.js +74 -0
- package/server/security/auth/adapters/saml.js +173 -0
- package/server/security/auth/adapters/totp.js +703 -0
- package/server/security/auth/adapters/webauthn.js +625 -0
- package/server/security/auth/files.md +61 -0
- package/server/security/auth/framework/constants.js +27 -0
- package/server/security/auth/framework/files.md +23 -0
- package/server/security/auth/framework/handlers.js +272 -0
- package/server/security/auth/framework/socket-auth.js +177 -0
- package/server/security/auth/handlers/auth-messages.js +143 -0
- package/server/security/auth/handlers/files.md +28 -0
- package/server/security/auth/index.js +290 -0
- package/server/security/auth/mfa/crypto/aead.js +148 -0
- package/server/security/auth/mfa/crypto/constants.js +35 -0
- package/server/security/auth/mfa/crypto/files.md +27 -0
- package/server/security/auth/mfa/crypto/kdf.js +120 -0
- package/server/security/auth/mfa/crypto/utils.js +68 -0
- package/server/security/auth/mfa/crypto-utils.js +80 -0
- package/server/security/auth/mfa/files.md +77 -0
- package/server/security/auth/mfa/ledger/constants.js +75 -0
- package/server/security/auth/mfa/ledger/errors.js +73 -0
- package/server/security/auth/mfa/ledger/files.md +23 -0
- package/server/security/auth/mfa/ledger/share-record.js +32 -0
- package/server/security/auth/mfa/ledger.js +255 -0
- package/server/security/auth/mfa/recovery/constants.js +67 -0
- package/server/security/auth/mfa/recovery/files.md +19 -0
- package/server/security/auth/mfa/recovery/handlers.js +216 -0
- package/server/security/auth/mfa/recovery.js +191 -0
- package/server/security/auth/mfa/sss/constants.js +21 -0
- package/server/security/auth/mfa/sss/files.md +23 -0
- package/server/security/auth/mfa/sss/gf256.js +103 -0
- package/server/security/auth/mfa/sss/serialization.js +82 -0
- package/server/security/auth/mfa/sss.js +161 -0
- package/server/security/auth/mfa/two-of-three/constants.js +58 -0
- package/server/security/auth/mfa/two-of-three/files.md +23 -0
- package/server/security/auth/mfa/two-of-three/handlers.js +241 -0
- package/server/security/auth/mfa/two-of-three/helpers.js +71 -0
- package/server/security/auth/mfa/two-of-three.js +136 -0
- package/server/security/auth/nonce-manager.js +89 -0
- package/server/security/auth/state-machine-mfa.js +269 -0
- package/server/security/auth/state-machine.js +257 -0
- package/server/security/extractRootDomain.js +144 -16
- package/server/security/files.md +51 -0
- package/server/security/origin.js +197 -15
- package/server/security/reply.js +274 -16
- package/server/socket/README.md +119 -0
- package/server/socket/authMiddleware.js +299 -0
- package/server/socket/files.md +86 -0
- package/server/socket/open.js +154 -8
- package/server/socket/pluginHooks.js +334 -0
- package/server/socket/receive.js +184 -225
- package/server/socket/receiveContext.js +117 -0
- package/server/socket/send.js +416 -78
- package/server/socket/tagUtils.js +402 -0
- package/server/utils/README.md +19 -0
- package/server/utils/deepRequire.js +255 -30
- package/server/utils/files.md +57 -0
- package/server/utils/genId.js +182 -20
- package/server/utils/parseUserAgent.js +313 -251
- package/server/utils/userAgent/README.md +65 -0
- package/server/utils/userAgent/files.md +46 -0
- package/server/utils/userAgent/patterns.js +545 -0
- package/utils/README.md +21 -0
- package/utils/files.md +66 -0
- package/utils/jss/README.md +21 -0
- package/utils/jss/decode.js +471 -0
- package/utils/jss/encode.js +312 -0
- package/utils/jss/files.md +68 -0
- package/utils/jss/plugins.js +210 -0
- package/utils/jss.js +219 -273
- package/utils/messageHash.js +238 -35
- package/dist/api-ape.min.js +0 -2
- package/dist/api-ape.min.js.map +0 -7
- package/server/client.js +0 -308
- package/server/lib/broadcast.js +0 -146
|
@@ -0,0 +1,435 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Streaming File Manager - Client-to-Client File Transfers
|
|
3
|
+
*
|
|
4
|
+
* This module provides a specialized manager for handling streaming file transfers
|
|
5
|
+
* between clients. Unlike traditional uploads where the server receives the complete
|
|
6
|
+
* file, streaming transfers allow data to flow through the server in chunks,
|
|
7
|
+
* enabling real-time file sharing between clients.
|
|
8
|
+
*
|
|
9
|
+
* Use Cases:
|
|
10
|
+
* - Live file sharing in collaborative applications
|
|
11
|
+
* - Large file transfers without server-side storage
|
|
12
|
+
* - Real-time media streaming between peers
|
|
13
|
+
* - Progressive file downloads while upload is in progress
|
|
14
|
+
*
|
|
15
|
+
* How Streaming Transfers Work:
|
|
16
|
+
* 1. Uploader registers a streaming file with a unique ID
|
|
17
|
+
* 2. Uploader sends chunks via `appendChunk()` as data becomes available
|
|
18
|
+
* 3. Downloader(s) can read partial data via `get()` with offset
|
|
19
|
+
* 4. Uploader calls `complete()` when transfer is finished
|
|
20
|
+
* 5. File is automatically cleaned up after timeout
|
|
21
|
+
*
|
|
22
|
+
* Memory Management:
|
|
23
|
+
* - Chunks are stored in memory as Buffer arrays
|
|
24
|
+
* - Automatic cleanup via configurable timeouts
|
|
25
|
+
* - `destroy()` method for immediate cleanup
|
|
26
|
+
*
|
|
27
|
+
* @module server/lib/fileTransfer/streaming
|
|
28
|
+
* @see {@link module:server/lib/fileTransfer} - Main file transfer module
|
|
29
|
+
*
|
|
30
|
+
* @example
|
|
31
|
+
* // Create a streaming file manager
|
|
32
|
+
* const { StreamingFileManager } = require('./streaming')
|
|
33
|
+
*
|
|
34
|
+
* const manager = new StreamingFileManager(
|
|
35
|
+
* 30000, // 30 second start timeout
|
|
36
|
+
* 60000 // 60 second complete timeout
|
|
37
|
+
* )
|
|
38
|
+
*
|
|
39
|
+
* // Register a new streaming file
|
|
40
|
+
* const fileId = manager.register('file-abc', 'uploader-client-id')
|
|
41
|
+
*
|
|
42
|
+
* // Append chunks as they arrive
|
|
43
|
+
* manager.appendChunk('file-abc', Buffer.from('chunk1'))
|
|
44
|
+
* manager.appendChunk('file-abc', Buffer.from('chunk2'))
|
|
45
|
+
*
|
|
46
|
+
* // Downloader reads partial data
|
|
47
|
+
* const result = manager.get('file-abc', 0)
|
|
48
|
+
* console.log(result.data) // Buffer with 'chunk1chunk2'
|
|
49
|
+
* console.log(result.isComplete) // false
|
|
50
|
+
* console.log(result.totalReceived) // 12
|
|
51
|
+
*
|
|
52
|
+
* // Mark as complete when done
|
|
53
|
+
* manager.complete('file-abc')
|
|
54
|
+
*
|
|
55
|
+
* // Clean up on shutdown
|
|
56
|
+
* manager.destroy()
|
|
57
|
+
*/
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* @typedef {Object} StreamingFileEntry
|
|
61
|
+
* Internal entry tracking a streaming file transfer.
|
|
62
|
+
*
|
|
63
|
+
* @property {string} uploaderId - Client ID of the uploader
|
|
64
|
+
* @property {Buffer[]} chunks - Array of received data chunks
|
|
65
|
+
* @property {number} totalReceived - Total bytes received so far
|
|
66
|
+
* @property {boolean} isComplete - Whether the upload is complete
|
|
67
|
+
* @property {number} createdAt - Timestamp when the file was registered
|
|
68
|
+
* @property {NodeJS.Timeout} timer - Cleanup timeout timer
|
|
69
|
+
*/
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* @typedef {Object} StreamingFileResult
|
|
73
|
+
* Result from reading a streaming file.
|
|
74
|
+
*
|
|
75
|
+
* @property {Buffer} data - The file data (or partial data from offset)
|
|
76
|
+
* @property {boolean} isComplete - Whether the upload is complete
|
|
77
|
+
* @property {number} totalReceived - Total bytes received so far
|
|
78
|
+
*/
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Manages streaming file transfers between clients.
|
|
82
|
+
*
|
|
83
|
+
* This class provides a buffer for streaming data that flows through the server
|
|
84
|
+
* from one client to another. It supports:
|
|
85
|
+
*
|
|
86
|
+
* - **Chunked uploads**: Data arrives in pieces and is accumulated
|
|
87
|
+
* - **Partial reads**: Downloaders can read data as it arrives
|
|
88
|
+
* - **Completion tracking**: Know when the full file has been received
|
|
89
|
+
* - **Automatic cleanup**: Files are removed after configurable timeouts
|
|
90
|
+
*
|
|
91
|
+
* The manager uses a two-phase timeout system:
|
|
92
|
+
* 1. **Start timeout**: Maximum time to wait for first chunk after registration
|
|
93
|
+
* 2. **Complete timeout**: Maximum time to keep file after completion
|
|
94
|
+
*
|
|
95
|
+
* @class StreamingFileManager
|
|
96
|
+
*
|
|
97
|
+
* @example
|
|
98
|
+
* // Basic usage
|
|
99
|
+
* const manager = new StreamingFileManager(30000, 60000)
|
|
100
|
+
*
|
|
101
|
+
* // Register and stream
|
|
102
|
+
* manager.register('file-1', 'client-a')
|
|
103
|
+
* manager.appendChunk('file-1', chunk1)
|
|
104
|
+
* manager.appendChunk('file-1', chunk2)
|
|
105
|
+
* manager.complete('file-1')
|
|
106
|
+
*
|
|
107
|
+
* @example
|
|
108
|
+
* // Reading with offset (for resumable downloads)
|
|
109
|
+
* const result1 = manager.get('file-1', 0) // Get all data
|
|
110
|
+
* const result2 = manager.get('file-1', 100) // Get data from byte 100
|
|
111
|
+
*
|
|
112
|
+
* @example
|
|
113
|
+
* // Progressive download while upload continues
|
|
114
|
+
* let offset = 0
|
|
115
|
+
* const interval = setInterval(() => {
|
|
116
|
+
* const result = manager.get('file-1', offset)
|
|
117
|
+
* if (result) {
|
|
118
|
+
* sendToDownloader(result.data)
|
|
119
|
+
* offset += result.data.length
|
|
120
|
+
*
|
|
121
|
+
* if (result.isComplete && offset >= result.totalReceived) {
|
|
122
|
+
* clearInterval(interval)
|
|
123
|
+
* }
|
|
124
|
+
* }
|
|
125
|
+
* }, 100)
|
|
126
|
+
*/
|
|
127
|
+
class StreamingFileManager {
|
|
128
|
+
/**
|
|
129
|
+
* Creates a new StreamingFileManager instance.
|
|
130
|
+
*
|
|
131
|
+
* @constructor
|
|
132
|
+
* @param {number} startTimeout - Milliseconds before an idle (no data) file is cleaned up.
|
|
133
|
+
* This timeout starts when the file is registered and resets when data arrives.
|
|
134
|
+
* @param {number} completeTimeout - Milliseconds to keep a completed file before cleanup.
|
|
135
|
+
* After `complete()` is called, this timeout determines how long the file stays available.
|
|
136
|
+
*
|
|
137
|
+
* @example
|
|
138
|
+
* // Short timeouts for real-time streaming
|
|
139
|
+
* const rtManager = new StreamingFileManager(5000, 10000)
|
|
140
|
+
*
|
|
141
|
+
* @example
|
|
142
|
+
* // Longer timeouts for large file transfers
|
|
143
|
+
* const fileManager = new StreamingFileManager(60000, 300000)
|
|
144
|
+
*/
|
|
145
|
+
constructor(startTimeout, completeTimeout) {
|
|
146
|
+
/**
|
|
147
|
+
* Timeout for idle files (no data received).
|
|
148
|
+
* @type {number}
|
|
149
|
+
* @private
|
|
150
|
+
*/
|
|
151
|
+
this.startTimeout = startTimeout;
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Timeout for completed files.
|
|
155
|
+
* @type {number}
|
|
156
|
+
* @private
|
|
157
|
+
*/
|
|
158
|
+
this.completeTimeout = completeTimeout;
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Map of active streaming files keyed by file ID.
|
|
162
|
+
* @type {Map<string, StreamingFileEntry>}
|
|
163
|
+
* @private
|
|
164
|
+
*/
|
|
165
|
+
this.streamingFiles = new Map();
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Registers a new streaming file for transfer.
|
|
170
|
+
*
|
|
171
|
+
* Creates an entry for the file and starts the cleanup timeout.
|
|
172
|
+
* If a file with the same ID already exists, it is replaced
|
|
173
|
+
* (the old timeout is cleared).
|
|
174
|
+
*
|
|
175
|
+
* @param {string} fileId - Unique identifier for the file
|
|
176
|
+
* @param {string} uploaderId - Client ID of the uploader (for authorization)
|
|
177
|
+
* @returns {string} The file ID (same as input, for chaining)
|
|
178
|
+
*
|
|
179
|
+
* @example
|
|
180
|
+
* // Register a new streaming file
|
|
181
|
+
* const fileId = manager.register('file-abc-123', 'client-uploader-id')
|
|
182
|
+
*
|
|
183
|
+
* @example
|
|
184
|
+
* // File ID can be any unique string
|
|
185
|
+
* manager.register(crypto.randomUUID(), clientId)
|
|
186
|
+
* manager.register(`${clientId}-${Date.now()}`, clientId)
|
|
187
|
+
*/
|
|
188
|
+
register(fileId, uploaderId) {
|
|
189
|
+
// Clear existing entry if present
|
|
190
|
+
if (this.streamingFiles.has(fileId)) {
|
|
191
|
+
const existing = this.streamingFiles.get(fileId);
|
|
192
|
+
if (existing.timer) clearTimeout(existing.timer);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Create the streaming file entry.
|
|
197
|
+
* @type {StreamingFileEntry}
|
|
198
|
+
*/
|
|
199
|
+
const entry = {
|
|
200
|
+
uploaderId,
|
|
201
|
+
chunks: [],
|
|
202
|
+
totalReceived: 0,
|
|
203
|
+
isComplete: false,
|
|
204
|
+
createdAt: Date.now(),
|
|
205
|
+
// Set cleanup timer for start + complete timeout combined
|
|
206
|
+
timer: setTimeout(() => {
|
|
207
|
+
this.streamingFiles.delete(fileId);
|
|
208
|
+
}, this.startTimeout + this.completeTimeout),
|
|
209
|
+
};
|
|
210
|
+
|
|
211
|
+
this.streamingFiles.set(fileId, entry);
|
|
212
|
+
return fileId;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Appends a data chunk to a streaming file.
|
|
217
|
+
*
|
|
218
|
+
* Chunks are accumulated in order and can be read by downloaders
|
|
219
|
+
* as they arrive. The total received byte count is updated.
|
|
220
|
+
*
|
|
221
|
+
* @param {string} fileId - The file's unique identifier
|
|
222
|
+
* @param {Buffer} chunk - Data chunk to append
|
|
223
|
+
* @returns {boolean} True if the chunk was appended, false if file not found
|
|
224
|
+
*
|
|
225
|
+
* @example
|
|
226
|
+
* // Append chunks as they arrive from the uploader
|
|
227
|
+
* socket.on('fileChunk', (fileId, data) => {
|
|
228
|
+
* const success = manager.appendChunk(fileId, Buffer.from(data))
|
|
229
|
+
* if (!success) {
|
|
230
|
+
* socket.emit('error', 'File not found')
|
|
231
|
+
* }
|
|
232
|
+
* })
|
|
233
|
+
*
|
|
234
|
+
* @example
|
|
235
|
+
* // Handle streaming upload from HTTP
|
|
236
|
+
* req.on('data', (chunk) => {
|
|
237
|
+
* manager.appendChunk(fileId, chunk)
|
|
238
|
+
* })
|
|
239
|
+
*/
|
|
240
|
+
appendChunk(fileId, chunk) {
|
|
241
|
+
const entry = this.streamingFiles.get(fileId);
|
|
242
|
+
if (!entry) return false;
|
|
243
|
+
|
|
244
|
+
entry.chunks.push(chunk);
|
|
245
|
+
entry.totalReceived += chunk.length;
|
|
246
|
+
return true;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Marks a streaming file as complete.
|
|
251
|
+
*
|
|
252
|
+
* Once complete, no more chunks should be appended. The file remains
|
|
253
|
+
* available for download until the complete timeout expires.
|
|
254
|
+
*
|
|
255
|
+
* Optionally, the complete data can be provided to replace all chunks.
|
|
256
|
+
* This is useful when the final assembled file differs from the chunks
|
|
257
|
+
* (e.g., after decompression or decryption).
|
|
258
|
+
*
|
|
259
|
+
* @param {string} fileId - The file's unique identifier
|
|
260
|
+
* @param {Buffer} [data] - Optional complete data to replace chunks
|
|
261
|
+
* @returns {boolean} True if marked complete, false if file not found
|
|
262
|
+
*
|
|
263
|
+
* @example
|
|
264
|
+
* // Mark complete without replacing data
|
|
265
|
+
* manager.complete('file-abc')
|
|
266
|
+
*
|
|
267
|
+
* @example
|
|
268
|
+
* // Mark complete with final assembled data
|
|
269
|
+
* const finalData = assembleChunks(manager.get('file-abc').data)
|
|
270
|
+
* manager.complete('file-abc', finalData)
|
|
271
|
+
*
|
|
272
|
+
* @example
|
|
273
|
+
* // In upload completion handler
|
|
274
|
+
* socket.on('fileComplete', (fileId) => {
|
|
275
|
+
* if (manager.complete(fileId)) {
|
|
276
|
+
* notifyDownloaders(fileId, 'complete')
|
|
277
|
+
* }
|
|
278
|
+
* })
|
|
279
|
+
*/
|
|
280
|
+
complete(fileId, data) {
|
|
281
|
+
const entry = this.streamingFiles.get(fileId);
|
|
282
|
+
if (!entry) return false;
|
|
283
|
+
|
|
284
|
+
// Replace chunks with complete data if provided
|
|
285
|
+
if (data) {
|
|
286
|
+
entry.chunks = [data];
|
|
287
|
+
entry.totalReceived = data.length;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
entry.isComplete = true;
|
|
291
|
+
|
|
292
|
+
// Reset timer for complete timeout only
|
|
293
|
+
clearTimeout(entry.timer);
|
|
294
|
+
entry.timer = setTimeout(() => {
|
|
295
|
+
this.streamingFiles.delete(fileId);
|
|
296
|
+
}, this.completeTimeout);
|
|
297
|
+
|
|
298
|
+
return true;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* Retrieves data from a streaming file.
|
|
303
|
+
*
|
|
304
|
+
* Returns the accumulated data, completion status, and total bytes received.
|
|
305
|
+
* An optional offset allows reading only new data since last read
|
|
306
|
+
* (useful for progressive downloads).
|
|
307
|
+
*
|
|
308
|
+
* @param {string} fileId - The file's unique identifier
|
|
309
|
+
* @param {number} [offset=0] - Byte offset to start reading from
|
|
310
|
+
* @returns {StreamingFileResult|null} File data and status, or null if not found
|
|
311
|
+
*
|
|
312
|
+
* @example
|
|
313
|
+
* // Get all data
|
|
314
|
+
* const result = manager.get('file-abc')
|
|
315
|
+
* if (result) {
|
|
316
|
+
* console.log('Data:', result.data)
|
|
317
|
+
* console.log('Complete:', result.isComplete)
|
|
318
|
+
* console.log('Total:', result.totalReceived)
|
|
319
|
+
* }
|
|
320
|
+
*
|
|
321
|
+
* @example
|
|
322
|
+
* // Progressive download with offset
|
|
323
|
+
* let downloaded = 0
|
|
324
|
+
* function downloadMore() {
|
|
325
|
+
* const result = manager.get('file-abc', downloaded)
|
|
326
|
+
* if (result && result.data.length > 0) {
|
|
327
|
+
* writeToFile(result.data)
|
|
328
|
+
* downloaded += result.data.length
|
|
329
|
+
* }
|
|
330
|
+
* if (!result?.isComplete) {
|
|
331
|
+
* setTimeout(downloadMore, 100)
|
|
332
|
+
* }
|
|
333
|
+
* }
|
|
334
|
+
*
|
|
335
|
+
* @example
|
|
336
|
+
* // HTTP streaming response
|
|
337
|
+
* app.get('/stream/:fileId', (req, res) => {
|
|
338
|
+
* const result = manager.get(req.params.fileId)
|
|
339
|
+
* if (!result) {
|
|
340
|
+
* return res.status(404).send('Not found')
|
|
341
|
+
* }
|
|
342
|
+
* res.setHeader('X-Complete', result.isComplete ? '1' : '0')
|
|
343
|
+
* res.setHeader('X-Total', result.totalReceived)
|
|
344
|
+
* res.send(result.data)
|
|
345
|
+
* })
|
|
346
|
+
*/
|
|
347
|
+
get(fileId, offset = 0) {
|
|
348
|
+
const entry = this.streamingFiles.get(fileId);
|
|
349
|
+
if (!entry) return null;
|
|
350
|
+
|
|
351
|
+
// Concatenate all chunks into a single buffer
|
|
352
|
+
const data = Buffer.concat(entry.chunks);
|
|
353
|
+
|
|
354
|
+
return {
|
|
355
|
+
// Return data from offset (for partial reads)
|
|
356
|
+
data: offset > 0 ? data.slice(offset) : data,
|
|
357
|
+
isComplete: entry.isComplete,
|
|
358
|
+
totalReceived: entry.totalReceived,
|
|
359
|
+
};
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
/**
|
|
363
|
+
* Checks if a streaming file exists.
|
|
364
|
+
*
|
|
365
|
+
* @param {string} fileId - The file's unique identifier
|
|
366
|
+
* @returns {boolean} True if the file exists, false otherwise
|
|
367
|
+
*
|
|
368
|
+
* @example
|
|
369
|
+
* if (manager.has('file-abc')) {
|
|
370
|
+
* // File exists, safe to read or append
|
|
371
|
+
* }
|
|
372
|
+
*/
|
|
373
|
+
has(fileId) {
|
|
374
|
+
return this.streamingFiles.has(fileId);
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
/**
|
|
378
|
+
* Cleans up files older than the specified maximum age.
|
|
379
|
+
*
|
|
380
|
+
* This is useful for periodic cleanup in addition to the automatic
|
|
381
|
+
* timeout-based cleanup. Can be called on an interval to ensure
|
|
382
|
+
* stale files don't accumulate.
|
|
383
|
+
*
|
|
384
|
+
* @param {number} maxAge - Maximum age in milliseconds
|
|
385
|
+
*
|
|
386
|
+
* @example
|
|
387
|
+
* // Clean up files older than 5 minutes every minute
|
|
388
|
+
* setInterval(() => {
|
|
389
|
+
* manager.cleanup(5 * 60 * 1000)
|
|
390
|
+
* }, 60 * 1000)
|
|
391
|
+
*
|
|
392
|
+
* @example
|
|
393
|
+
* // One-time cleanup of very old files
|
|
394
|
+
* manager.cleanup(24 * 60 * 60 * 1000) // 24 hours
|
|
395
|
+
*/
|
|
396
|
+
cleanup(maxAge) {
|
|
397
|
+
const now = Date.now();
|
|
398
|
+
for (const [fileId, entry] of this.streamingFiles) {
|
|
399
|
+
/* istanbul ignore next 4 - periodic cleanup, covered by automatic timers in practice */
|
|
400
|
+
if (now - entry.createdAt > maxAge) {
|
|
401
|
+
clearTimeout(entry.timer);
|
|
402
|
+
this.streamingFiles.delete(fileId);
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
/**
|
|
408
|
+
* Destroys the manager and clears all streaming files.
|
|
409
|
+
*
|
|
410
|
+
* Cancels all pending cleanup timers and removes all files from memory.
|
|
411
|
+
* Call this when shutting down the server or when the manager is no
|
|
412
|
+
* longer needed.
|
|
413
|
+
*
|
|
414
|
+
* @example
|
|
415
|
+
* // Clean shutdown
|
|
416
|
+
* process.on('SIGTERM', () => {
|
|
417
|
+
* manager.destroy()
|
|
418
|
+
* process.exit(0)
|
|
419
|
+
* })
|
|
420
|
+
*
|
|
421
|
+
* @example
|
|
422
|
+
* // In test cleanup
|
|
423
|
+
* afterEach(() => {
|
|
424
|
+
* manager.destroy()
|
|
425
|
+
* })
|
|
426
|
+
*/
|
|
427
|
+
destroy() {
|
|
428
|
+
for (const entry of this.streamingFiles.values()) {
|
|
429
|
+
clearTimeout(entry.timer);
|
|
430
|
+
}
|
|
431
|
+
this.streamingFiles.clear();
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
module.exports = { StreamingFileManager };
|