@leogps/file-uploader 2.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.
Files changed (46) hide show
  1. package/.eslintrc.js +178 -0
  2. package/LICENSE +21 -0
  3. package/README.md +115 -0
  4. package/dist/client/1551f4f60c37af51121f.woff2 +0 -0
  5. package/dist/client/2285773e6b4b172f07d9.woff +0 -0
  6. package/dist/client/23f19bb08961f37aaf69.eot +0 -0
  7. package/dist/client/2f517e09eb2ca6650ff5.svg +3717 -0
  8. package/dist/client/4689f52cc96215721344.svg +801 -0
  9. package/dist/client/491974d108fe4002b2aa.ttf +0 -0
  10. package/dist/client/527940b104eb2ea366c8.ttf +0 -0
  11. package/dist/client/77206a6bb316fa0aded5.eot +0 -0
  12. package/dist/client/7a3337626410ca2f4071.woff2 +0 -0
  13. package/dist/client/7a8b4f130182d19a2d7c.svg +5034 -0
  14. package/dist/client/9bbb245e67a133f6e486.eot +0 -0
  15. package/dist/client/bb58e57c48a3e911f15f.woff +0 -0
  16. package/dist/client/be9ee23c0c6390141475.ttf +0 -0
  17. package/dist/client/d878b0a6a1144760244f.woff2 +0 -0
  18. package/dist/client/eeccf4f66002c6f2ba24.woff +0 -0
  19. package/dist/client/favicon.ico +0 -0
  20. package/dist/client/index.html +1 -0
  21. package/dist/client/main.66a16cbe5e2ce036e9a7.bundle.js +39507 -0
  22. package/dist/client/main.6db272040eaab1c51019.css +14 -0
  23. package/dist/index.js +3 -0
  24. package/dist/index.js.LICENSE.txt +273 -0
  25. package/package-gzip.js +30 -0
  26. package/package.json +107 -0
  27. package/src/globals.ts +23 -0
  28. package/src/index.ts +87 -0
  29. package/src/model/progress.ts +175 -0
  30. package/src/model/progress_utils.ts +17 -0
  31. package/src/routes/uploadChunk.ts +125 -0
  32. package/src/routes/uploadComplete.ts +53 -0
  33. package/src/routes/uploadInit.ts +83 -0
  34. package/src/routes/uploadStatus.ts +137 -0
  35. package/src/service/progress_writer.ts +52 -0
  36. package/src-client/entrypoint.ts +273 -0
  37. package/src-client/progress-handler.ts +233 -0
  38. package/src-client/public/favicon.ico +0 -0
  39. package/src-client/public/index.html +67 -0
  40. package/src-client/sha1.ts +19 -0
  41. package/src-client/style.scss +87 -0
  42. package/tsconfig.json +107 -0
  43. package/webpack-client.common.js +29 -0
  44. package/webpack-client.dev.js +51 -0
  45. package/webpack-client.prod.js +65 -0
  46. package/webpack.config.js +41 -0
@@ -0,0 +1,273 @@
1
+ /*
2
+ object-assign
3
+ (c) Sindre Sorhus
4
+ @license MIT
5
+ */
6
+
7
+ /*!
8
+ * accepts
9
+ * Copyright(c) 2014 Jonathan Ong
10
+ * Copyright(c) 2015 Douglas Christopher Wilson
11
+ * MIT Licensed
12
+ */
13
+
14
+ /*!
15
+ * base64id v0.1.0
16
+ */
17
+
18
+ /*!
19
+ * body-parser
20
+ * Copyright(c) 2014 Jonathan Ong
21
+ * Copyright(c) 2014-2015 Douglas Christopher Wilson
22
+ * MIT Licensed
23
+ */
24
+
25
+ /*!
26
+ * body-parser
27
+ * Copyright(c) 2014-2015 Douglas Christopher Wilson
28
+ * MIT Licensed
29
+ */
30
+
31
+ /*!
32
+ * bytes
33
+ * Copyright(c) 2012-2014 TJ Holowaychuk
34
+ * Copyright(c) 2015 Jed Watson
35
+ * MIT Licensed
36
+ */
37
+
38
+ /*!
39
+ * content-disposition
40
+ * Copyright(c) 2014-2017 Douglas Christopher Wilson
41
+ * MIT Licensed
42
+ */
43
+
44
+ /*!
45
+ * content-type
46
+ * Copyright(c) 2015 Douglas Christopher Wilson
47
+ * MIT Licensed
48
+ */
49
+
50
+ /*!
51
+ * cookie
52
+ * Copyright(c) 2012-2014 Roman Shtylman
53
+ * Copyright(c) 2015 Douglas Christopher Wilson
54
+ * MIT Licensed
55
+ */
56
+
57
+ /*!
58
+ * depd
59
+ * Copyright(c) 2014-2018 Douglas Christopher Wilson
60
+ * MIT Licensed
61
+ */
62
+
63
+ /*!
64
+ * destroy
65
+ * Copyright(c) 2014 Jonathan Ong
66
+ * Copyright(c) 2015-2022 Douglas Christopher Wilson
67
+ * MIT Licensed
68
+ */
69
+
70
+ /*!
71
+ * ee-first
72
+ * Copyright(c) 2014 Jonathan Ong
73
+ * MIT Licensed
74
+ */
75
+
76
+ /*!
77
+ * encodeurl
78
+ * Copyright(c) 2016 Douglas Christopher Wilson
79
+ * MIT Licensed
80
+ */
81
+
82
+ /*!
83
+ * escape-html
84
+ * Copyright(c) 2012-2013 TJ Holowaychuk
85
+ * Copyright(c) 2015 Andreas Lubbe
86
+ * Copyright(c) 2015 Tiancheng "Timothy" Gu
87
+ * MIT Licensed
88
+ */
89
+
90
+ /*!
91
+ * etag
92
+ * Copyright(c) 2014-2016 Douglas Christopher Wilson
93
+ * MIT Licensed
94
+ */
95
+
96
+ /*!
97
+ * express
98
+ * Copyright(c) 2009-2013 TJ Holowaychuk
99
+ * Copyright(c) 2013 Roman Shtylman
100
+ * Copyright(c) 2014-2015 Douglas Christopher Wilson
101
+ * MIT Licensed
102
+ */
103
+
104
+ /*!
105
+ * express
106
+ * Copyright(c) 2009-2013 TJ Holowaychuk
107
+ * Copyright(c) 2014-2015 Douglas Christopher Wilson
108
+ * MIT Licensed
109
+ */
110
+
111
+ /*!
112
+ * finalhandler
113
+ * Copyright(c) 2014-2022 Douglas Christopher Wilson
114
+ * MIT Licensed
115
+ */
116
+
117
+ /*!
118
+ * forwarded
119
+ * Copyright(c) 2014-2017 Douglas Christopher Wilson
120
+ * MIT Licensed
121
+ */
122
+
123
+ /*!
124
+ * fresh
125
+ * Copyright(c) 2012 TJ Holowaychuk
126
+ * Copyright(c) 2016-2017 Douglas Christopher Wilson
127
+ * MIT Licensed
128
+ */
129
+
130
+ /*!
131
+ * http-errors
132
+ * Copyright(c) 2014 Jonathan Ong
133
+ * Copyright(c) 2016 Douglas Christopher Wilson
134
+ * MIT Licensed
135
+ */
136
+
137
+ /*!
138
+ * media-typer
139
+ * Copyright(c) 2014 Douglas Christopher Wilson
140
+ * MIT Licensed
141
+ */
142
+
143
+ /*!
144
+ * merge-descriptors
145
+ * Copyright(c) 2014 Jonathan Ong
146
+ * Copyright(c) 2015 Douglas Christopher Wilson
147
+ * MIT Licensed
148
+ */
149
+
150
+ /*!
151
+ * methods
152
+ * Copyright(c) 2013-2014 TJ Holowaychuk
153
+ * Copyright(c) 2015-2016 Douglas Christopher Wilson
154
+ * MIT Licensed
155
+ */
156
+
157
+ /*!
158
+ * mime-db
159
+ * Copyright(c) 2014 Jonathan Ong
160
+ * Copyright(c) 2015-2022 Douglas Christopher Wilson
161
+ * MIT Licensed
162
+ */
163
+
164
+ /*!
165
+ * mime-types
166
+ * Copyright(c) 2014 Jonathan Ong
167
+ * Copyright(c) 2015 Douglas Christopher Wilson
168
+ * MIT Licensed
169
+ */
170
+
171
+ /*!
172
+ * negotiator
173
+ * Copyright(c) 2012 Federico Romero
174
+ * Copyright(c) 2012-2014 Isaac Z. Schlueter
175
+ * Copyright(c) 2015 Douglas Christopher Wilson
176
+ * MIT Licensed
177
+ */
178
+
179
+ /*!
180
+ * on-finished
181
+ * Copyright(c) 2013 Jonathan Ong
182
+ * Copyright(c) 2014 Douglas Christopher Wilson
183
+ * MIT Licensed
184
+ */
185
+
186
+ /*!
187
+ * parseurl
188
+ * Copyright(c) 2014 Jonathan Ong
189
+ * Copyright(c) 2014-2017 Douglas Christopher Wilson
190
+ * MIT Licensed
191
+ */
192
+
193
+ /*!
194
+ * proxy-addr
195
+ * Copyright(c) 2014-2016 Douglas Christopher Wilson
196
+ * MIT Licensed
197
+ */
198
+
199
+ /*!
200
+ * range-parser
201
+ * Copyright(c) 2012-2014 TJ Holowaychuk
202
+ * Copyright(c) 2015-2016 Douglas Christopher Wilson
203
+ * MIT Licensed
204
+ */
205
+
206
+ /*!
207
+ * raw-body
208
+ * Copyright(c) 2013-2014 Jonathan Ong
209
+ * Copyright(c) 2014-2022 Douglas Christopher Wilson
210
+ * MIT Licensed
211
+ */
212
+
213
+ /*!
214
+ * send
215
+ * Copyright(c) 2012 TJ Holowaychuk
216
+ * Copyright(c) 2014-2022 Douglas Christopher Wilson
217
+ * MIT Licensed
218
+ */
219
+
220
+ /*!
221
+ * serve-static
222
+ * Copyright(c) 2010 Sencha Inc.
223
+ * Copyright(c) 2011 TJ Holowaychuk
224
+ * Copyright(c) 2014-2016 Douglas Christopher Wilson
225
+ * MIT Licensed
226
+ */
227
+
228
+ /*!
229
+ * statuses
230
+ * Copyright(c) 2014 Jonathan Ong
231
+ * Copyright(c) 2016 Douglas Christopher Wilson
232
+ * MIT Licensed
233
+ */
234
+
235
+ /*!
236
+ * toidentifier
237
+ * Copyright(c) 2016 Douglas Christopher Wilson
238
+ * MIT Licensed
239
+ */
240
+
241
+ /*!
242
+ * type-is
243
+ * Copyright(c) 2014 Jonathan Ong
244
+ * Copyright(c) 2014-2015 Douglas Christopher Wilson
245
+ * MIT Licensed
246
+ */
247
+
248
+ /*!
249
+ * unpipe
250
+ * Copyright(c) 2015 Douglas Christopher Wilson
251
+ * MIT Licensed
252
+ */
253
+
254
+ /*!
255
+ * vary
256
+ * Copyright(c) 2014-2017 Douglas Christopher Wilson
257
+ * MIT Licensed
258
+ */
259
+
260
+ /*! https://mths.be/utf8js v2.1.2 by @mathias */
261
+
262
+ /*! noble-hashes - MIT License (c) 2022 Paul Miller (paulmillr.com) */
263
+
264
+ /*! safe-buffer. MIT License. Feross Aboukhadijeh <https://feross.org/opensource> */
265
+
266
+ /**
267
+ * @license
268
+ * Lodash <https://lodash.com/>
269
+ * Copyright OpenJS Foundation and other contributors <https://openjsf.org/>
270
+ * Released under MIT license <https://lodash.com/license>
271
+ * Based on Underscore.js 1.8.3 <http://underscorejs.org/LICENSE>
272
+ * Copyright Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
273
+ */
@@ -0,0 +1,30 @@
1
+ import targz from 'targz';
2
+ import fs from 'fs';
3
+
4
+ const DEST = 'file-uploader.tar.gz';
5
+ const SRC = 'dist';
6
+
7
+ if (!fs.existsSync(SRC)) {
8
+ console.error(`Source folder "${SRC}" does not exist. Build first!`);
9
+ process.exit(1);
10
+ }
11
+
12
+ // Remove existing archive
13
+ if (fs.existsSync(DEST)) {
14
+ fs.unlinkSync(DEST);
15
+ console.log(`Removed existing archive: ${DEST}`);
16
+ }
17
+
18
+ console.log(`Creating archive from "${SRC}" → ${DEST}...`);
19
+
20
+ targz.compress({
21
+ src: SRC,
22
+ dest: DEST,
23
+ }, (err) => {
24
+ if (err) {
25
+ console.error('Packaging failed:', err);
26
+ process.exit(1);
27
+ } else {
28
+ console.log(`Packaging complete: ${DEST}`);
29
+ }
30
+ });
package/package.json ADDED
@@ -0,0 +1,107 @@
1
+ {
2
+ "name": "@leogps/file-uploader",
3
+ "version": "2.0.0",
4
+ "description": "Facilitates file uploader server.",
5
+ "main": "src/index.ts",
6
+ "bin": {
7
+ "file-uploader": "./dist/index.js"
8
+ },
9
+ "scripts": {
10
+ "clean": "rimraf dist file-uploader.tar.gz",
11
+ "precompile": "eslint -c .eslintrc.js --fix --ext .ts src src-client",
12
+ "compile-server": "./node_modules/webpack-cli/bin/cli.js --config webpack.config.js",
13
+ "compile-client-dev": "./node_modules/webpack-cli/bin/cli.js --config webpack-client.dev.js",
14
+ "compile-client-prod": "./node_modules/webpack-cli/bin/cli.js --config webpack-client.prod.js",
15
+ "compile-dev": "npm run precompile && npm run compile-server && npm run compile-client-dev",
16
+ "build-dev": "npm run compile-dev",
17
+ "compile-prod": "npm run precompile && npm run compile-server && npm run compile-client-prod",
18
+ "build-prod": "npm run compile-prod",
19
+ "start": "npm run build-dev && node dist/index.js",
20
+ "package": "cross-env NODE_ENV=production npm run build-prod && chmod +x ./dist/index.js && node ./package-gzip.js",
21
+ "test": "echo \"Error: no test specified\" && exit 1",
22
+ "build:server:once": "npm run compile-server && npm run compile-client-dev",
23
+ "dev:server": "npm run build:server:once && npm-run-all --parallel nodemon:prod watch:client",
24
+ "watch:client": "cross-env NODE_ENV=development ./node_modules/webpack-cli/bin/cli.js --config webpack-client.dev.js --watch",
25
+ "nodemon:prod": "cross-env NODE_ENV=development ./node_modules/node-dev/bin/node-dev ./dist/index.js",
26
+ "pre-publish": "npm run clean && npm run package"
27
+ },
28
+ "nodemonConfig": {
29
+ "watch": [
30
+ "./src",
31
+ "./src-client",
32
+ "./node_modules"
33
+ ],
34
+ "ext": "ts,js,html,css,scss",
35
+ "delay": 2500
36
+ },
37
+ "keywords": [
38
+ "file",
39
+ "uploader",
40
+ "upload",
41
+ "server",
42
+ "client"
43
+ ],
44
+ "author": "Paul Gundarapu",
45
+ "license": "ISC",
46
+ "dependencies": {
47
+ "@fortawesome/fontawesome-free": "^5.15.4",
48
+ "bulma": "^1.0.4",
49
+ "express": "^4.18.2",
50
+ "formidable": "^3.5.4",
51
+ "jquery": "^3.7.1",
52
+ "jquery-blockui": "^2.7.0",
53
+ "js-sha1": "^0.7.0",
54
+ "lodash.throttle": "^4.1.1",
55
+ "moment": "^2.29.4",
56
+ "mv": "^2.1.1",
57
+ "pretty-bytes": "^6.1.1",
58
+ "rimraf": "^5.0.5",
59
+ "serve-favicon": "^2.5.0",
60
+ "socket.io": "^4.7.2",
61
+ "socket.io-client": "^4.7.2",
62
+ "toastify-js": "^1.12.0",
63
+ "uuid": "^9.0.1",
64
+ "yargs": "^17.7.2"
65
+ },
66
+ "devDependencies": {
67
+ "@types/express": "^4.17.20",
68
+ "@types/formidable": "^3.4.6",
69
+ "@types/jquery": "^3.5.24",
70
+ "@types/jquery.blockui": "^0.0.31",
71
+ "@types/lodash": "^4.14.184",
72
+ "@types/mv": "^2.1.2",
73
+ "@types/serve-favicon": "^2.5.6",
74
+ "@types/toastify-js": "^1.12.2",
75
+ "@types/uuid": "^9.0.6",
76
+ "@types/yargs": "^17.0.12",
77
+ "@typescript-eslint/eslint-plugin": "^8.48.1",
78
+ "@typescript-eslint/parser": "^8.48.1",
79
+ "clean-webpack-plugin": "^4.0.0",
80
+ "concurrently": "^7.3.0",
81
+ "cross-env": "^7.0.3",
82
+ "css-loader": "^6.7.1",
83
+ "css-minimizer-webpack-plugin": "^4.0.0",
84
+ "eslint": "^9.39.1",
85
+ "eslint-config-prettier": "^10.1.8",
86
+ "eslint-plugin-jsdoc": "^61.4.1",
87
+ "eslint-plugin-prefer-arrow": "^1.2.3",
88
+ "gulp-sass": "^6.0.1",
89
+ "html-webpack-plugin": "^5.5.0",
90
+ "mini-css-extract-plugin": "^2.6.1",
91
+ "node-dev": "^8.0.0",
92
+ "nodemon": "^3.0.1",
93
+ "npm-run-all": "^4.1.5",
94
+ "postcss-loader": "^7.0.1",
95
+ "prettier": "^3.7.4",
96
+ "sass": "^1.94.2",
97
+ "sass-loader": "^13.3.3",
98
+ "style-loader": "^3.3.1",
99
+ "targz": "^1.0.1",
100
+ "ts-loader": "^9.5.4",
101
+ "tsc-watch": "^5.0.3",
102
+ "typescript": "^5.9.3",
103
+ "webpack": "^5.74.0",
104
+ "webpack-cli": "^6.0.1",
105
+ "webpack-merge": "^5.8.0"
106
+ }
107
+ }
package/src/globals.ts ADDED
@@ -0,0 +1,23 @@
1
+ import { Progress } from './model/progress';
2
+ import { ProgressWriter } from './service/progress_writer';
3
+ import _ from "lodash";
4
+
5
+ export const MAX_CHUNK_SIZE = 2 * 1024 * 1024;
6
+ export const MAX_PARALLEL_CHUNK_UPLOADS = 10;
7
+ export let uploadsDir: string;
8
+ export const progresses: Progress[] = [];
9
+ export const uploadsProgressMap: Map<string, Progress> = new Map();
10
+ let progressWriter: ProgressWriter;
11
+ export const throttleWaitTimeInMillis = 250;
12
+
13
+ export const setUploadsDir = (dir: string) => { uploadsDir = dir; };
14
+ export const getUploadsDir = () => uploadsDir;
15
+
16
+ export const setProgressWriter = (writer: ProgressWriter) => {
17
+ progressWriter = writer;
18
+ };
19
+ export const getProgressWriter = (): ProgressWriter => progressWriter;
20
+
21
+ export const throttledBroadcaster = _.throttle(() => {
22
+ getProgressWriter().writeProgress(progresses);
23
+ }, throttleWaitTimeInMillis);
package/src/index.ts ADDED
@@ -0,0 +1,87 @@
1
+
2
+
3
+ import express, { Express, Request, Response } from 'express';
4
+ import {createServer, Server} from "http"
5
+ import {router as uploadInitRouter} from "./routes/uploadInit";
6
+ import {router as uploadChunkRouter} from "./routes/uploadChunk";
7
+ import {router as uploadCompleteRouter} from "./routes/uploadComplete";
8
+ import {router as uploadStatusRouter} from "./routes/uploadStatus";
9
+ import {ProgressWriter} from "./service/progress_writer";
10
+ import * as socketio from "socket.io";
11
+ import yargs from "yargs";
12
+ import {hideBin} from "yargs/helpers";
13
+ import * as os from 'os';
14
+ import {getProgressWriter, progresses, setProgressWriter, setUploadsDir} from "./globals";
15
+
16
+ const homedir = os.homedir();
17
+ let port = 8082;
18
+ let uploadsDir = homedir + "/Downloads/uploads/"
19
+ const argv: any = yargs(hideBin(process.argv))
20
+ .option('upload_location', {
21
+ alias: 'l',
22
+ type: 'string',
23
+ description: 'upload location',
24
+ default: uploadsDir
25
+ })
26
+ .option('port', {
27
+ alias: 'p',
28
+ type: 'number',
29
+ description: 'server port'
30
+ })
31
+ .help()
32
+ .argv
33
+
34
+ if (argv.port) {
35
+ port = argv.port
36
+ }
37
+ if (argv.upload_location) {
38
+ uploadsDir = argv.upload_location.endsWith('/') ? argv.upload_location: argv.upload_location + '/'
39
+ }
40
+ setUploadsDir(uploadsDir)
41
+
42
+ console.log("Upload location: " + uploadsDir)
43
+ console.log("Server port: " + port)
44
+
45
+ const app: Express = express();
46
+ const httpServer: Server = createServer(app)
47
+ const io: socketio.Server = new socketio.Server(httpServer);
48
+
49
+ setProgressWriter(new ProgressWriter(io));
50
+
51
+ app.use(express.json());
52
+ app.use(express.urlencoded({ extended: true }));
53
+ app.use('/upload/init', uploadInitRouter);
54
+ app.use('/upload/chunk', uploadChunkRouter);
55
+ app.use('/upload/complete', uploadCompleteRouter);
56
+ app.use('/upload/status', uploadStatusRouter);
57
+
58
+ app.get('/', (_, res) => {
59
+ res.sendFile(__dirname + '/client/index.html');
60
+ });
61
+
62
+ app.get('/progresses', (_: Request, res: Response) => {
63
+ console.log("Progresses requested...");
64
+ res.writeHead(200, { 'content-type': 'application/json' });
65
+ res.write(JSON.stringify(progresses));
66
+ res.end();
67
+ });
68
+
69
+ io.on('connection', (socket: socketio.Socket) => {
70
+ console.log('a user connected');
71
+ // socket.emit('progresses', progresses);
72
+ getProgressWriter().writeProgress(progresses);
73
+ socket.on('disconnect', () => {
74
+ console.log('user disconnected');
75
+ });
76
+ socket.on('message', (msg) => {
77
+ console.log('message: ' + msg);
78
+ });
79
+ });
80
+
81
+ app.use('/', [
82
+ express.static(__dirname + '/client/')
83
+ ]);
84
+
85
+ httpServer.listen(port, () => {
86
+ console.log('Server listening on ' + port + ' ...');
87
+ });
@@ -0,0 +1,175 @@
1
+ import prettyBytes from 'pretty-bytes'
2
+
3
+ const TRANSFER_SAMPLE_FREQ = 1000; // 1second
4
+ const MIN_SAMPLE_FREQ = TRANSFER_SAMPLE_FREQ / 4;
5
+
6
+ export enum UploadStatus {
7
+ INITIATED = "INITIATED",
8
+ UPLOADING = "UPLOADING",
9
+ FINISHING = "FINISHING",
10
+ COMPLETE = "COMPLETE",
11
+ FAILED = "FAILED"
12
+ }
13
+
14
+ export interface Progress {
15
+ uuid?: any,
16
+ type?: string,
17
+ timestamp?: number,
18
+ bytesReceived?: number,
19
+ bytesExpected?: number,
20
+ bytesReceivedPretty?: string,
21
+ bytesExpectedPretty?: string,
22
+ fileName?: string,
23
+ savedLocation?: string,
24
+ completed?: number,
25
+ lastState: UploadStatus,
26
+ transferSamples?: TransferSample[]
27
+
28
+ markSample(): void
29
+ }
30
+
31
+ export interface TransferSample {
32
+ bytesReceived: number; // total bytes received at this sample
33
+ timestamp: number; // timestamp in ms
34
+ chunkIndex?: number; // optional: index of chunk this sample refers to
35
+ chunkBytes?: number; // optional: bytes in this chunk
36
+ }
37
+
38
+ export class FileTransferProgress implements Progress {
39
+ uuid?: any
40
+ type?: string | undefined
41
+ timestamp?: number | undefined
42
+ bytesReceived?: number | undefined
43
+ bytesExpected?: number | undefined
44
+ bytesReceivedPretty?: string | undefined
45
+ bytesExpectedPretty?: string | undefined
46
+ fileName?: string | undefined
47
+ savedLocation?: string | undefined
48
+ completed?: number | undefined
49
+ lastState: UploadStatus = UploadStatus.INITIATED;
50
+ transferSamples: TransferSample[] = []
51
+
52
+ chunkSize?: number; // size of each chunk
53
+ totalChunks?: number; // total number of chunks
54
+ chunkVerificationCount = 0;
55
+ uploadedChunks: Set<number> = new Set(); // track uploaded chunk indices
56
+ uploadingChunks: Set<number> = new Set();
57
+
58
+ constructor(uuid: string, timestamp: number) {
59
+ this.uuid = uuid;
60
+ this.type = 'progress';
61
+ this.timestamp = timestamp;
62
+ this.bytesReceived = 0;
63
+ this.bytesExpected = 0;
64
+ this.bytesReceivedPretty = prettyBytes(0);
65
+ this.bytesExpectedPretty = prettyBytes(0);
66
+ }
67
+
68
+ markSample(): void {
69
+ const timestampInMillis = new Date().getTime();
70
+ if (!this.shouldSample(timestampInMillis)) {
71
+ return;
72
+ }
73
+
74
+ const bytesReceived = this.bytesReceived as number;
75
+ this.transferSamples.push(
76
+ {
77
+ bytesReceived,
78
+ timestamp: timestampInMillis
79
+ }
80
+ );
81
+
82
+ this.cleanupSamples();
83
+ }
84
+
85
+ private shouldSample(millis: number): boolean {
86
+ if (this.transferSamples.length < 1) {
87
+ return true;
88
+ }
89
+ const lastSample = this.transferSamples[this.transferSamples.length - 1];
90
+ return (millis - lastSample.timestamp > MIN_SAMPLE_FREQ);
91
+ }
92
+
93
+ private cleanupSamples(): void {
94
+ if (this.transferSamples.length < 1) {
95
+ return;
96
+ }
97
+ const firstSample = this.transferSamples[0];
98
+ if (!firstSample) {
99
+ return;
100
+ }
101
+ const firstSampleTimestamp: number = firstSample.timestamp;
102
+ const nowInMillis = new Date().getTime();
103
+ if (firstSampleTimestamp - nowInMillis > TRANSFER_SAMPLE_FREQ) {
104
+ this.transferSamples.shift();
105
+ }
106
+ }
107
+
108
+ /** Calculate size of last chunk */
109
+ public lastChunkSize(): number {
110
+ if (!this.bytesExpected || !this.chunkSize || !this.totalChunks) {
111
+ return 0;
112
+ }
113
+ const remainder = this.bytesExpected % this.chunkSize;
114
+ return remainder > 0 ? remainder : this.chunkSize;
115
+ }
116
+
117
+ /** Calculate bytesReceived based on uploadedChunks */
118
+ public calculateBytesReceived(): number {
119
+ if (!this.chunkSize || !this.totalChunks) {
120
+ return this.bytesReceived || 0;
121
+ }
122
+
123
+ const lastChunkIndex = (this.totalChunks - 1);
124
+ const hasLastChunk = this.uploadedChunks.has(lastChunkIndex);
125
+ const chunksCount = this.uploadedChunks.size;
126
+
127
+ if (hasLastChunk) {
128
+ return ((chunksCount - 1) * this.chunkSize) + this.lastChunkSize();
129
+ }
130
+ return chunksCount * this.chunkSize;
131
+ }
132
+
133
+ /** Update bytesReceived and its pretty representation */
134
+ private updateBytesReceived(): void {
135
+ this.bytesReceived = this.calculateBytesReceived();
136
+ this.bytesReceivedPretty = prettyBytes(this.bytesReceived || 0);
137
+ this.bytesExpectedPretty = prettyBytes(this.bytesExpected || 0);
138
+ }
139
+
140
+ public addUploadedChunk(chunkIndex: number): void {
141
+ if (chunkIndex < 0) {
142
+ console.warn(`Invalid chunkIndex ${chunkIndex}. Must be >= 0.`);
143
+ return;
144
+ }
145
+ if (!this.totalChunks) {
146
+ console.warn(`Total chunks not set yet. Cannot add chunk ${chunkIndex}.`);
147
+ return;
148
+ }
149
+ if (chunkIndex >= this.totalChunks) {
150
+ console.warn(`Chunk index ${chunkIndex} exceeds totalChunks (${this.totalChunks - 1}).`);
151
+ return;
152
+ }
153
+ if (this.uploadedChunks.has(chunkIndex)) {
154
+ console.warn(`Chunk index ${chunkIndex} already marked as uploaded for ${this.fileName}.`);
155
+ return;
156
+ }
157
+
158
+ this.uploadedChunks.add(chunkIndex);
159
+ this.updateBytesReceived();
160
+ }
161
+
162
+ public resetUploadedChunks(): void {
163
+ this.uploadedChunks = new Set();
164
+ }
165
+
166
+ public chunkVerified(chunkIndex: number): void {
167
+ this.chunkVerificationCount++;
168
+ console.debug(`chunk ${chunkIndex} verified. Total verified: ${this.chunkVerificationCount} for ${this.fileName}.`)
169
+ }
170
+
171
+ /** Optional: reset */
172
+ public resetVerificationCount(): void {
173
+ this.chunkVerificationCount = 0;
174
+ }
175
+ }