@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.
- package/.eslintrc.js +178 -0
- package/LICENSE +21 -0
- package/README.md +115 -0
- package/dist/client/1551f4f60c37af51121f.woff2 +0 -0
- package/dist/client/2285773e6b4b172f07d9.woff +0 -0
- package/dist/client/23f19bb08961f37aaf69.eot +0 -0
- package/dist/client/2f517e09eb2ca6650ff5.svg +3717 -0
- package/dist/client/4689f52cc96215721344.svg +801 -0
- package/dist/client/491974d108fe4002b2aa.ttf +0 -0
- package/dist/client/527940b104eb2ea366c8.ttf +0 -0
- package/dist/client/77206a6bb316fa0aded5.eot +0 -0
- package/dist/client/7a3337626410ca2f4071.woff2 +0 -0
- package/dist/client/7a8b4f130182d19a2d7c.svg +5034 -0
- package/dist/client/9bbb245e67a133f6e486.eot +0 -0
- package/dist/client/bb58e57c48a3e911f15f.woff +0 -0
- package/dist/client/be9ee23c0c6390141475.ttf +0 -0
- package/dist/client/d878b0a6a1144760244f.woff2 +0 -0
- package/dist/client/eeccf4f66002c6f2ba24.woff +0 -0
- package/dist/client/favicon.ico +0 -0
- package/dist/client/index.html +1 -0
- package/dist/client/main.66a16cbe5e2ce036e9a7.bundle.js +39507 -0
- package/dist/client/main.6db272040eaab1c51019.css +14 -0
- package/dist/index.js +3 -0
- package/dist/index.js.LICENSE.txt +273 -0
- package/package-gzip.js +30 -0
- package/package.json +107 -0
- package/src/globals.ts +23 -0
- package/src/index.ts +87 -0
- package/src/model/progress.ts +175 -0
- package/src/model/progress_utils.ts +17 -0
- package/src/routes/uploadChunk.ts +125 -0
- package/src/routes/uploadComplete.ts +53 -0
- package/src/routes/uploadInit.ts +83 -0
- package/src/routes/uploadStatus.ts +137 -0
- package/src/service/progress_writer.ts +52 -0
- package/src-client/entrypoint.ts +273 -0
- package/src-client/progress-handler.ts +233 -0
- package/src-client/public/favicon.ico +0 -0
- package/src-client/public/index.html +67 -0
- package/src-client/sha1.ts +19 -0
- package/src-client/style.scss +87 -0
- package/tsconfig.json +107 -0
- package/webpack-client.common.js +29 -0
- package/webpack-client.dev.js +51 -0
- package/webpack-client.prod.js +65 -0
- 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
|
+
*/
|
package/package-gzip.js
ADDED
|
@@ -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
|
+
}
|