@wlindabla/file_uploader 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +2172 -0
- package/dist/cache/index.js +295 -0
- package/dist/cache/index.js.map +1 -0
- package/dist/core/index.js +724 -0
- package/dist/core/index.js.map +1 -0
- package/dist/events/chunk/index.js +43 -0
- package/dist/events/chunk/index.js.map +1 -0
- package/dist/events/complete/index.js +117 -0
- package/dist/events/complete/index.js.map +1 -0
- package/dist/events/index.js +84 -0
- package/dist/events/index.js.map +1 -0
- package/dist/events/initialize/index.js +70 -0
- package/dist/events/initialize/index.js.map +1 -0
- package/dist/events/state/index.js +97 -0
- package/dist/events/state/index.js.map +1 -0
- package/dist/exceptions/index.js +133 -0
- package/dist/exceptions/index.js.map +1 -0
- package/dist/index.js +49 -0
- package/dist/index.js.map +1 -0
- package/dist/subscribers/index.js +162 -0
- package/dist/subscribers/index.js.map +1 -0
- package/dist/types/index.js +30 -0
- package/dist/types/index.js.map +1 -0
- package/dist/utils/index.js +183 -0
- package/dist/utils/index.js.map +1 -0
- package/package.json +95 -0
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
5
|
+
class InitializeUploadFailureException extends Error {
|
|
6
|
+
constructor(responseData, message) {
|
|
7
|
+
super(message);
|
|
8
|
+
this.responseData = responseData;
|
|
9
|
+
this.name = "InitializeUploadFailureException";
|
|
10
|
+
if (Error.captureStackTrace) {
|
|
11
|
+
Error.captureStackTrace(this, InitializeUploadFailureException);
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
static {
|
|
15
|
+
__name(this, "InitializeUploadFailureException");
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
class ChunkUploadHttpErrorException extends Error {
|
|
19
|
+
constructor(errorPayload, statusResponse) {
|
|
20
|
+
super();
|
|
21
|
+
this.errorPayload = errorPayload;
|
|
22
|
+
this.statusResponse = statusResponse;
|
|
23
|
+
this.name = "ChunkUploadHttpErrorException";
|
|
24
|
+
if (Error.captureStackTrace) {
|
|
25
|
+
Error.captureStackTrace(this, ChunkUploadHttpErrorException);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
static {
|
|
29
|
+
__name(this, "ChunkUploadHttpErrorException");
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
class FileUploadChunkError extends Error {
|
|
33
|
+
static {
|
|
34
|
+
__name(this, "FileUploadChunkError");
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* The detailed chunk error information
|
|
38
|
+
*/
|
|
39
|
+
chunkError;
|
|
40
|
+
/**
|
|
41
|
+
* Creates a new FileUploadChunkError
|
|
42
|
+
*
|
|
43
|
+
* @param message - Human-readable error message
|
|
44
|
+
* @param chunkError - Detailed information about the chunk failure
|
|
45
|
+
*/
|
|
46
|
+
constructor(message, chunkError) {
|
|
47
|
+
super(message, { cause: chunkError.error });
|
|
48
|
+
this.name = "FileUploadChunkError";
|
|
49
|
+
this.chunkError = chunkError;
|
|
50
|
+
if (Error.captureStackTrace) {
|
|
51
|
+
Error.captureStackTrace(this, FileUploadChunkError);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Gets the index of the chunk that failed
|
|
56
|
+
*/
|
|
57
|
+
get chunkIndex() {
|
|
58
|
+
return this.chunkError.chunk.index;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Gets the attempt number when the chunk failed
|
|
62
|
+
*/
|
|
63
|
+
get attemptNumber() {
|
|
64
|
+
return this.chunkError.attempt;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Indicates whether a retry will be attempted
|
|
68
|
+
*/
|
|
69
|
+
get willRetry() {
|
|
70
|
+
return this.chunkError.willRetry;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Gets the underlying error that caused the chunk upload to fail
|
|
74
|
+
*/
|
|
75
|
+
get underlyingError() {
|
|
76
|
+
return this.chunkError.error;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Gets the full chunk information
|
|
80
|
+
*/
|
|
81
|
+
get chunk() {
|
|
82
|
+
return this.chunkError.chunk;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Returns a detailed string representation of the error
|
|
86
|
+
*/
|
|
87
|
+
toString() {
|
|
88
|
+
return `${this.name}: ${this.message}
|
|
89
|
+
Chunk: ${this.chunkIndex + 1}/${this.chunk.size} bytes
|
|
90
|
+
Attempt: ${this.attemptNumber}
|
|
91
|
+
Will Retry: ${this.willRetry}
|
|
92
|
+
Cause: ${this.underlyingError.message}`;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Returns a JSON representation of the error
|
|
96
|
+
*/
|
|
97
|
+
toJSON() {
|
|
98
|
+
return {
|
|
99
|
+
name: this.name,
|
|
100
|
+
message: this.message,
|
|
101
|
+
chunkIndex: this.chunkIndex,
|
|
102
|
+
attempt: this.attemptNumber,
|
|
103
|
+
willRetry: this.willRetry,
|
|
104
|
+
underlyingError: {
|
|
105
|
+
name: this.underlyingError.name,
|
|
106
|
+
message: this.underlyingError.message
|
|
107
|
+
},
|
|
108
|
+
stack: this.stack
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
class UploadCancelledException extends Error {
|
|
113
|
+
constructor(chunkIndex, totalChunks, uploadedBytes, message) {
|
|
114
|
+
super(message);
|
|
115
|
+
this.chunkIndex = chunkIndex;
|
|
116
|
+
this.totalChunks = totalChunks;
|
|
117
|
+
this.uploadedBytes = uploadedBytes;
|
|
118
|
+
this.name = "UploadCancelledException";
|
|
119
|
+
if (Error.captureStackTrace) {
|
|
120
|
+
Error.captureStackTrace(this, UploadCancelledException);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
static {
|
|
124
|
+
__name(this, "UploadCancelledException");
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
exports.ChunkUploadHttpErrorException = ChunkUploadHttpErrorException;
|
|
129
|
+
exports.FileUploadChunkError = FileUploadChunkError;
|
|
130
|
+
exports.InitializeUploadFailureException = InitializeUploadFailureException;
|
|
131
|
+
exports.UploadCancelledException = UploadCancelledException;
|
|
132
|
+
//# sourceMappingURL=index.js.map
|
|
133
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/exceptions/index.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["/*\n * This file is part of the project by AGBOKOUDJO Franck.\n *\n * (c) AGBOKOUDJO Franck <internationaleswebservices@gmail.com>\n * Phone: +229 0167 25 18 86\n * LinkedIn: https://www.linkedin.com/in/internationales-web-apps-services-120520193/\n * Company: INTERNATIONALES WEB APPS & SERVICES\n *\n * For more information, please feel free to contact the author.\n */\n\nimport { ChunkInfo, ChunkError } from \"../types\";\n\nexport class InitializeUploadFailureException extends Error {\n constructor(public readonly responseData: any, message: string) {\n super(message);\n this.name = \"InitializeUploadFailureException\";\n\n if (Error.captureStackTrace) {\n Error.captureStackTrace(this, InitializeUploadFailureException);\n }\n }\n}\n\n/**\n * Exception data for MEDIA_CHUNK_UPLOAD_HTTP_ERROR_RESPONSE\n */\nexport class ChunkUploadHttpErrorException extends Error {\n constructor(\n public readonly errorPayload: string | Record<string, string | unknown> | unknown,\n public readonly statusResponse: number\n ) {\n super();\n\n this.name = \"ChunkUploadHttpErrorException\";\n\n if (Error.captureStackTrace) {\n Error.captureStackTrace(this, ChunkUploadHttpErrorException);\n }\n }\n}\n\n/**\n * Custom error thrown during chunked file upload operations.\n * \n * This error provides detailed information about which chunk failed,\n * the underlying error, and whether a retry will be attempted.\n * \n * @example\n * ```typescript\n * try {\n * await uploadChunk(chunk);\n * } catch (error) {\n * throw new FileUploadChunkError(\n * `Failed to upload chunk ${chunkIndex}`,\n * chunkError\n * );\n * }\n * \n * // Later in error handler\n * if (error instanceof FileUploadChunkError) {\n * console.log(`Chunk ${error.chunkIndex} failed`);\n * console.log(`Will retry: ${error.willRetry}`);\n * }\n * ```\n */\nexport class FileUploadChunkError extends Error {\n\n /**\n * The detailed chunk error information\n */\n public readonly chunkError: ChunkError;\n\n /**\n * Creates a new FileUploadChunkError\n * \n * @param message - Human-readable error message\n * @param chunkError - Detailed information about the chunk failure\n */\n constructor(message: string, chunkError: ChunkError) {\n super(message, { cause: chunkError.error });\n\n this.name = \"FileUploadChunkError\";\n this.chunkError = chunkError;\n\n // Maintain proper stack trace in V8 engines (Chrome, Node.js)\n if (Error.captureStackTrace) {\n Error.captureStackTrace(this, FileUploadChunkError);\n }\n }\n\n /**\n * Gets the index of the chunk that failed\n */\n get chunkIndex(): number {\n return this.chunkError.chunk.index;\n }\n\n /**\n * Gets the attempt number when the chunk failed\n */\n get attemptNumber(): number {\n return this.chunkError.attempt;\n }\n\n /**\n * Indicates whether a retry will be attempted\n */\n get willRetry(): boolean {\n return this.chunkError.willRetry;\n }\n\n /**\n * Gets the underlying error that caused the chunk upload to fail\n */\n get underlyingError(): Error {\n return this.chunkError.error;\n }\n\n /**\n * Gets the full chunk information\n */\n get chunk(): ChunkInfo {\n return this.chunkError.chunk;\n }\n\n /**\n * Returns a detailed string representation of the error\n */\n toString(): string {\n return `${this.name}: ${this.message}\\n` +\n ` Chunk: ${this.chunkIndex + 1}/${this.chunk.size} bytes\\n` +\n ` Attempt: ${this.attemptNumber}\\n` +\n ` Will Retry: ${this.willRetry}\\n` +\n ` Cause: ${this.underlyingError.message}`;\n }\n\n /**\n * Returns a JSON representation of the error\n */\n toJSON(): object {\n return {\n name: this.name,\n message: this.message,\n chunkIndex: this.chunkIndex,\n attempt: this.attemptNumber,\n willRetry: this.willRetry,\n underlyingError: {\n name: this.underlyingError.name,\n message: this.underlyingError.message\n },\n stack: this.stack\n };\n }\n}\n\n\nexport class UploadCancelledException extends Error {\n constructor(\n public readonly chunkIndex: number,\n public readonly totalChunks: number,\n public readonly uploadedBytes: number,\n message: string\n ) {\n super(message);\n this.name = \"UploadCancelledException\";\n\n if (Error.captureStackTrace) {\n Error.captureStackTrace(this, UploadCancelledException);\n }\n }\n}"]}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var utils = require('./utils');
|
|
4
|
+
var exceptions = require('./exceptions');
|
|
5
|
+
var events = require('./events');
|
|
6
|
+
var subscribers = require('./subscribers');
|
|
7
|
+
var cache = require('./cache');
|
|
8
|
+
var core = require('./core');
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
Object.keys(utils).forEach(function (k) {
|
|
13
|
+
if (k !== 'default' && !Object.prototype.hasOwnProperty.call(exports, k)) Object.defineProperty(exports, k, {
|
|
14
|
+
enumerable: true,
|
|
15
|
+
get: function () { return utils[k]; }
|
|
16
|
+
});
|
|
17
|
+
});
|
|
18
|
+
Object.keys(exceptions).forEach(function (k) {
|
|
19
|
+
if (k !== 'default' && !Object.prototype.hasOwnProperty.call(exports, k)) Object.defineProperty(exports, k, {
|
|
20
|
+
enumerable: true,
|
|
21
|
+
get: function () { return exceptions[k]; }
|
|
22
|
+
});
|
|
23
|
+
});
|
|
24
|
+
Object.keys(events).forEach(function (k) {
|
|
25
|
+
if (k !== 'default' && !Object.prototype.hasOwnProperty.call(exports, k)) Object.defineProperty(exports, k, {
|
|
26
|
+
enumerable: true,
|
|
27
|
+
get: function () { return events[k]; }
|
|
28
|
+
});
|
|
29
|
+
});
|
|
30
|
+
Object.keys(subscribers).forEach(function (k) {
|
|
31
|
+
if (k !== 'default' && !Object.prototype.hasOwnProperty.call(exports, k)) Object.defineProperty(exports, k, {
|
|
32
|
+
enumerable: true,
|
|
33
|
+
get: function () { return subscribers[k]; }
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
Object.keys(cache).forEach(function (k) {
|
|
37
|
+
if (k !== 'default' && !Object.prototype.hasOwnProperty.call(exports, k)) Object.defineProperty(exports, k, {
|
|
38
|
+
enumerable: true,
|
|
39
|
+
get: function () { return cache[k]; }
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
Object.keys(core).forEach(function (k) {
|
|
43
|
+
if (k !== 'default' && !Object.prototype.hasOwnProperty.call(exports, k)) Object.defineProperty(exports, k, {
|
|
44
|
+
enumerable: true,
|
|
45
|
+
get: function () { return core[k]; }
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
//# sourceMappingURL=index.js.map
|
|
49
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"names":[],"mappings":"","file":"index.js","sourcesContent":[]}
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var events = require('../events');
|
|
4
|
+
var http_client = require('@wlindabla/http_client');
|
|
5
|
+
var exceptions = require('../exceptions');
|
|
6
|
+
|
|
7
|
+
var __defProp = Object.defineProperty;
|
|
8
|
+
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
9
|
+
class InitializeUploadSubscriber {
|
|
10
|
+
constructor(eventDispatcher) {
|
|
11
|
+
this.eventDispatcher = eventDispatcher;
|
|
12
|
+
}
|
|
13
|
+
static {
|
|
14
|
+
__name(this, "InitializeUploadSubscriber");
|
|
15
|
+
}
|
|
16
|
+
getSubscribedEvents() {
|
|
17
|
+
return {
|
|
18
|
+
[events.HttpFileUploaderEvents.INITIALIZE_UPLOAD]: { listener: "onInitializeUpload", priority: 100 }
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Initializes upload session with the server.
|
|
23
|
+
*
|
|
24
|
+
* @param fileHash - SHA-256 hash of the file (first 1MB)
|
|
25
|
+
* @returns Session ID from server, or null if initialization failed
|
|
26
|
+
* @throws {FileUploadInitializationError} If server returns error or network fails
|
|
27
|
+
*/
|
|
28
|
+
async onInitializeUpload(event) {
|
|
29
|
+
event.stopPropagation();
|
|
30
|
+
const initialzeUploadRequestOptions = event.initUploadOptions;
|
|
31
|
+
this.eventDispatcher.dispatch(
|
|
32
|
+
new events.InitializeUploadStartedEvent(
|
|
33
|
+
initialzeUploadRequestOptions.fileName,
|
|
34
|
+
initialzeUploadRequestOptions.fileSize,
|
|
35
|
+
initialzeUploadRequestOptions.fileHash
|
|
36
|
+
),
|
|
37
|
+
events.HttpFileUploaderEvents.INITIALIZE_UPLOAD_STARTED
|
|
38
|
+
);
|
|
39
|
+
try {
|
|
40
|
+
const response = await http_client.safeFetch({
|
|
41
|
+
url: initialzeUploadRequestOptions.endpointInit,
|
|
42
|
+
methodSend: "POST",
|
|
43
|
+
headers: {
|
|
44
|
+
"Content-Type": "application/json",
|
|
45
|
+
...initialzeUploadRequestOptions.headers
|
|
46
|
+
},
|
|
47
|
+
data: {
|
|
48
|
+
fileName: initialzeUploadRequestOptions.fileName,
|
|
49
|
+
fileSize: initialzeUploadRequestOptions.fileSize,
|
|
50
|
+
fileHash: initialzeUploadRequestOptions.fileHash,
|
|
51
|
+
fileType: initialzeUploadRequestOptions.fileType,
|
|
52
|
+
metadata: initialzeUploadRequestOptions.metadata
|
|
53
|
+
},
|
|
54
|
+
responseType: "json",
|
|
55
|
+
retryCount: 3,
|
|
56
|
+
retryOnStatusCode: true,
|
|
57
|
+
timeout: 45e3
|
|
58
|
+
});
|
|
59
|
+
const status = response.status;
|
|
60
|
+
if (response.failed) {
|
|
61
|
+
console.error(`Initialize upload failed (HTTP ${status}):`, response);
|
|
62
|
+
const errorUploadFailure = new exceptions.InitializeUploadFailureException(
|
|
63
|
+
response,
|
|
64
|
+
`Server returned error: HTTP ${response.status}`
|
|
65
|
+
);
|
|
66
|
+
this.eventDispatcher.dispatch(
|
|
67
|
+
new events.InitializeUploadFailureEvent(
|
|
68
|
+
errorUploadFailure,
|
|
69
|
+
status,
|
|
70
|
+
response.data
|
|
71
|
+
),
|
|
72
|
+
events.HttpFileUploaderEvents.INITIALIZE_UPLOAD_FAILURE
|
|
73
|
+
);
|
|
74
|
+
throw errorUploadFailure;
|
|
75
|
+
}
|
|
76
|
+
const responseData = response.data;
|
|
77
|
+
if (!responseData || typeof responseData !== "object") {
|
|
78
|
+
const validationError = "Invalid server response: expected object, got " + typeof responseData;
|
|
79
|
+
console.error("Invalid response structure:", responseData);
|
|
80
|
+
throw new exceptions.InitializeUploadFailureException(
|
|
81
|
+
responseData,
|
|
82
|
+
validationError
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
let sessionId = responseData.mediaId || responseData.mediaIdFromServer || responseData.sessionId || responseData.uploadId;
|
|
86
|
+
if (!sessionId) {
|
|
87
|
+
const missingKeyError = 'Server response missing required field: "mediaId","mediaIdFromServer", "sessionId", or "uploadId"';
|
|
88
|
+
console.error("Missing session ID in response:", responseData);
|
|
89
|
+
throw new exceptions.InitializeUploadFailureException(
|
|
90
|
+
responseData,
|
|
91
|
+
missingKeyError
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
if (typeof sessionId === "number") {
|
|
95
|
+
sessionId = sessionId.toString();
|
|
96
|
+
}
|
|
97
|
+
event.setMediaId(sessionId);
|
|
98
|
+
this.eventDispatcher.dispatch(
|
|
99
|
+
new events.InitializeUploadSuccessEvent(
|
|
100
|
+
status,
|
|
101
|
+
sessionId,
|
|
102
|
+
responseData
|
|
103
|
+
),
|
|
104
|
+
events.HttpFileUploaderEvents.INITIALIZE_UPLOAD_SUCCESS
|
|
105
|
+
);
|
|
106
|
+
} catch (error) {
|
|
107
|
+
if (error instanceof http_client.HttpFetchError) {
|
|
108
|
+
this.eventDispatcher.dispatch(
|
|
109
|
+
new events.InitializeUploadFailureEvent(
|
|
110
|
+
error instanceof Error ? error : new Error(String(error))
|
|
111
|
+
),
|
|
112
|
+
events.HttpFileUploaderEvents.INITIALIZE_UPLOAD_FAILURE
|
|
113
|
+
);
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
console.error("Initialize upload exception:", error);
|
|
117
|
+
throw error;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
class FinalizeUploadSubscriber {
|
|
122
|
+
constructor(eventDispatcher) {
|
|
123
|
+
this.eventDispatcher = eventDispatcher;
|
|
124
|
+
}
|
|
125
|
+
static {
|
|
126
|
+
__name(this, "FinalizeUploadSubscriber");
|
|
127
|
+
}
|
|
128
|
+
getSubscribedEvents() {
|
|
129
|
+
return {
|
|
130
|
+
[events.HttpFileUploaderEvents.FINALIZE_UPLOAD]: { listener: "onFinalizeUpload", priority: 100 }
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
async onFinalizeUpload(event) {
|
|
134
|
+
event.stopPropagation();
|
|
135
|
+
try {
|
|
136
|
+
const responseFinalizeUpload = await http_client.safeFetch({
|
|
137
|
+
url: event.endPoint,
|
|
138
|
+
methodSend: "POST",
|
|
139
|
+
headers: {
|
|
140
|
+
"Content-Type": "application/json",
|
|
141
|
+
...event.headers
|
|
142
|
+
},
|
|
143
|
+
data: { mediaId: event.mediaId, mediaHash: event.mediaHash }
|
|
144
|
+
});
|
|
145
|
+
event.setResponse(responseFinalizeUpload);
|
|
146
|
+
} catch (error) {
|
|
147
|
+
if (error instanceof http_client.HttpFetchError) {
|
|
148
|
+
this.eventDispatcher.dispatch(
|
|
149
|
+
new events.FinalizeUploadFailureEvent(error),
|
|
150
|
+
events.HttpFileUploaderEvents.FINALIZE_UPLOAD_FAILURE
|
|
151
|
+
);
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
throw error;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
exports.FinalizeUploadSubscriber = FinalizeUploadSubscriber;
|
|
160
|
+
exports.InitializeUploadSubscriber = InitializeUploadSubscriber;
|
|
161
|
+
//# sourceMappingURL=index.js.map
|
|
162
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/subscribers/index.ts"],"names":["HttpFileUploaderEvents","InitializeUploadStartedEvent","safeFetch","InitializeUploadFailureException","InitializeUploadFailureEvent","InitializeUploadSuccessEvent","HttpFetchError","FinalizeUploadFailureEvent"],"mappings":"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","file":"index.js","sourcesContent":["/*\n * This file is part of the project by AGBOKOUDJO Franck.\n *\n * (c) AGBOKOUDJO Franck <internationaleswebservices@gmail.com>\n * Phone: +229 0167 25 18 86\n * LinkedIn: https://www.linkedin.com/in/internationales-web-apps-services-120520193/\n * Company: INTERNATIONALES WEB APPS & SERVICES\n *\n * For more information, please feel free to contact the author.\n */\n\nimport {\n EventSubscriberInterface,\n EventDispatcherInterface\n} from \"@wlindabla/event_dispatcher\";\n\nimport {\n HttpFileUploaderEvents,\n InitializingUploadEvent,\n InitializeUploadStartedEvent,\n InitializeUploadSuccessEvent,\n InitializeUploadFailureEvent,\n FinalizeUploadEvent,\n FinalizeUploadFailureEvent\n} from \"../events\";\n\nimport {\n HttpFetchError,\n safeFetch\n} from \"@wlindabla/http_client\";\n\nimport { InitializeUploadResponse } from \"../types\";\n\nimport { InitializeUploadFailureException } from \"../exceptions\";\n\n\n//src/subsciber/index.ts\n\nexport class InitializeUploadSubscriber implements EventSubscriberInterface {\n constructor(private readonly eventDispatcher: EventDispatcherInterface) {\n \n }\n\n public getSubscribedEvents(): Record<string, string | { listener: string; priority?: number | undefined; }> {\n return {\n [HttpFileUploaderEvents.INITIALIZE_UPLOAD]: { listener: \"onInitializeUpload\", priority: 100}\n }\n }\n\n /**\n * Initializes upload session with the server.\n * \n * @param fileHash - SHA-256 hash of the file (first 1MB)\n * @returns Session ID from server, or null if initialization failed\n * @throws {FileUploadInitializationError} If server returns error or network fails\n */\n public async onInitializeUpload(event:InitializingUploadEvent):Promise<void>{\n event.stopPropagation();\n const initialzeUploadRequestOptions = event.initUploadOptions;\n\n this.eventDispatcher.dispatch(\n new InitializeUploadStartedEvent(\n initialzeUploadRequestOptions.fileName,\n initialzeUploadRequestOptions.fileSize,\n initialzeUploadRequestOptions.fileHash\n ),\n HttpFileUploaderEvents.INITIALIZE_UPLOAD_STARTED\n );\n \n try {\n const response = await safeFetch({\n url:initialzeUploadRequestOptions.endpointInit,\n methodSend: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n ...initialzeUploadRequestOptions.headers\n },\n data: {\n fileName:initialzeUploadRequestOptions.fileName,\n fileSize:initialzeUploadRequestOptions.fileSize,\n fileHash:initialzeUploadRequestOptions.fileHash,\n fileType: initialzeUploadRequestOptions.fileType,\n metadata: initialzeUploadRequestOptions.metadata,\n },\n responseType: \"json\",\n retryCount: 3,\n retryOnStatusCode: true,\n timeout: 45000\n });\n\n const status = response.status;\n \n // Handle error responses (4xx, 5xx)\n if (response.failed) {\n console.error(`Initialize upload failed (HTTP ${status}):`, response);\n\n const errorUploadFailure =new InitializeUploadFailureException(\n response,\n `Server returned error: HTTP ${response.status}`\n );\n\n this.eventDispatcher.dispatch(\n new InitializeUploadFailureEvent(\n errorUploadFailure,\n status,\n response.data),\n HttpFileUploaderEvents.INITIALIZE_UPLOAD_FAILURE\n\n );\n \n throw errorUploadFailure;\n }\n\n const responseData = response.data as InitializeUploadResponse;\n\n // Validate server response struct\n if (!responseData || typeof responseData !== 'object') {\n const validationError = 'Invalid server response: expected object, got ' + typeof responseData\n console.error('Invalid response structure:', responseData);\n\n throw new InitializeUploadFailureException(\n responseData,\n validationError\n );\n }\n // Extract session ID\n let sessionId =\n responseData.mediaId ||\n responseData.mediaIdFromServer ||\n responseData.sessionId ||\n responseData.uploadId;\n\n if (!sessionId) {\n const missingKeyError = 'Server response missing required field: \"mediaId\",\"mediaIdFromServer\", \"sessionId\", or \"uploadId\"';\n\n console.error('Missing session ID in response:', responseData);\n throw new InitializeUploadFailureException(\n responseData,\n missingKeyError\n );\n }\n\n if (typeof sessionId === \"number\") { sessionId = sessionId.toString(); }\n\n event.setMediaId(sessionId);\n\n this.eventDispatcher.dispatch(\n new InitializeUploadSuccessEvent(\n status,\n sessionId,\n responseData\n ),\n HttpFileUploaderEvents.INITIALIZE_UPLOAD_SUCCESS\n );\n\n } catch (error) {\n if (error instanceof HttpFetchError) {\n this.eventDispatcher.dispatch(\n new InitializeUploadFailureEvent(\n error instanceof Error ? error : new Error(String(error)),\n ),\n HttpFileUploaderEvents.INITIALIZE_UPLOAD_FAILURE\n );\n return;\n }\n console.error('Initialize upload exception:', error);\n\n throw error;\n }\n }\n}\n\nexport class FinalizeUploadSubscriber implements EventSubscriberInterface{\n\n constructor(private readonly eventDispatcher: EventDispatcherInterface) {\n\n }\n\n public getSubscribedEvents(): Record<string, string | { listener: string; priority?: number | undefined; }> {\n return {\n [HttpFileUploaderEvents.FINALIZE_UPLOAD]: { listener: \"onFinalizeUpload\", priority: 100 }\n }\n }\n\n public async onFinalizeUpload(event: FinalizeUploadEvent): Promise<void>{\n event.stopPropagation();\n try {\n const responseFinalizeUpload = await safeFetch({\n url: event.endPoint,\n methodSend: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n ...event.headers\n },\n data: { mediaId: event.mediaId, mediaHash: event.mediaHash }\n });\n\n event.setResponse(responseFinalizeUpload)\n } catch (error) {\n if (error instanceof HttpFetchError) {\n this.eventDispatcher.dispatch(\n new FinalizeUploadFailureEvent(error),\n HttpFileUploaderEvents.FINALIZE_UPLOAD_FAILURE\n );\n return;\n }\n\n throw error;\n }\n }\n}"]}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var UploadState = /* @__PURE__ */ ((UploadState2) => {
|
|
4
|
+
UploadState2["IDLE"] = "idle";
|
|
5
|
+
UploadState2["INITIALIZING"] = "initializing";
|
|
6
|
+
UploadState2["UPLOADING"] = "uploading";
|
|
7
|
+
UploadState2["PAUSED"] = "paused";
|
|
8
|
+
UploadState2["CANCELLED"] = "cancelled";
|
|
9
|
+
UploadState2["FINALIZING"] = "finalizing";
|
|
10
|
+
UploadState2["COMPLETED"] = "completed";
|
|
11
|
+
UploadState2["FAILED"] = "failed";
|
|
12
|
+
return UploadState2;
|
|
13
|
+
})(UploadState || {});
|
|
14
|
+
const DEFAULT_CONFIG = Object.freeze({
|
|
15
|
+
defaultChunkSizeMB: 50,
|
|
16
|
+
slowSpeedThresholdMbps: 5,
|
|
17
|
+
slowSpeedChunkSizeMB: 2,
|
|
18
|
+
fileSizeThresholds: [
|
|
19
|
+
{ maxSizeMB: 200, chunkSizeMB: 50 },
|
|
20
|
+
{ maxSizeMB: 400, chunkSizeMB: 100 },
|
|
21
|
+
{ maxSizeMB: 800, chunkSizeMB: 300 },
|
|
22
|
+
{ maxSizeMB: 1e3, chunkSizeMB: 500 },
|
|
23
|
+
{ maxSizeMB: Infinity, chunkSizeMB: 700 }
|
|
24
|
+
]
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
exports.DEFAULT_CONFIG = DEFAULT_CONFIG;
|
|
28
|
+
exports.UploadState = UploadState;
|
|
29
|
+
//# sourceMappingURL=index.js.map
|
|
30
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/types/index.ts"],"names":["UploadState"],"mappings":";;AAkEO,IAAK,WAAA,qBAAAA,YAAAA,KAAL;AACH,EAAAA,aAAA,MAAA,CAAA,GAAO,MAAA;AACP,EAAAA,aAAA,cAAA,CAAA,GAAe,cAAA;AACf,EAAAA,aAAA,WAAA,CAAA,GAAY,WAAA;AACZ,EAAAA,aAAA,QAAA,CAAA,GAAS,QAAA;AACT,EAAAA,aAAA,WAAA,CAAA,GAAY,WAAA;AACZ,EAAAA,aAAA,YAAA,CAAA,GAAa,YAAA;AACb,EAAAA,aAAA,WAAA,CAAA,GAAY,WAAA;AACZ,EAAAA,aAAA,QAAA,CAAA,GAAS,QAAA;AARD,EAAA,OAAAA,YAAAA;AAAA,CAAA,EAAA,WAAA,IAAA,EAAA;AAiCL,MAAM,cAAA,GAA4C,OAAO,MAAA,CAAO;AAAA,EACnE,kBAAA,EAAoB,EAAA;AAAA,EACpB,sBAAA,EAAwB,CAAA;AAAA,EACxB,oBAAA,EAAsB,CAAA;AAAA,EACtB,kBAAA,EAAoB;AAAA,IAChB,EAAE,SAAA,EAAW,GAAA,EAAK,WAAA,EAAa,EAAA,EAAG;AAAA,IAClC,EAAE,SAAA,EAAW,GAAA,EAAK,WAAA,EAAa,GAAA,EAAI;AAAA,IACnC,EAAE,SAAA,EAAW,GAAA,EAAK,WAAA,EAAa,GAAA,EAAI;AAAA,IACnC,EAAE,SAAA,EAAW,GAAA,EAAM,WAAA,EAAa,GAAA,EAAI;AAAA,IACpC,EAAE,SAAA,EAAW,QAAA,EAAU,WAAA,EAAa,GAAA;AAAI;AAEhD,CAAC","file":"index.js","sourcesContent":["/*\n * This file is part of the project by AGBOKOUDJO Franck.\n *\n * (c) AGBOKOUDJO Franck <internationaleswebservices@gmail.com>\n * Phone: +229 0167 25 18 86\n * LinkedIn: https://www.linkedin.com/in/internationales-web-apps-services-120520193/\n * Company: INTERNATIONALES WEB APPS & SERVICES\n *\n * For more information, please feel free to contact the author.\n */\n\nimport { FetchResponseInterface } from \"@wlindabla/http_client\";\n\nexport interface UploadOptions {\n autoSave?: boolean,\n chunkSize?: number;\n speedMbps?: number;\n config?: ChunkSizeConfig;\n headers?: HeadersInit;\n chunkOtherData?: Record<string, any>;\n metadata?: Record<string, any>;\n maxRetries?: number;\n timeout?: number;\n initTimeout?: number;\n headerInitialzingUpload?: HeadersInit;\n headerFinalezingUpload?: HeadersInit;\n concurrency?: number; // Default: 3\n}\n\nexport interface InitializeUploadOptions{\n metadata?: Record<string, any>;\n fileHash: string;\n fileName: string;\n fileSize: number;\n fileType: string;\n endpointInit: string | URL;\n headers?: HeadersInit;\n}\n\nexport interface UploadEndpoints{\n init: string | URL;\n upload: string | URL;\n finalize: string | URL;\n}\n\nexport interface ChunkSizeConfig {\n defaultChunkSizeMB: number;\n slowSpeedThresholdMbps: number;\n slowSpeedChunkSizeMB: number;\n fileSizeThresholds: Array<{\n maxSizeMB: number;\n chunkSizeMB: number;\n }>;\n}\n\nexport type UploadStatus = 'pending' | 'uploading' | 'success' | 'error';\n\nexport interface ChunkInfo {\n index: number;\n start: number;\n end: number;\n size: number;\n attempt: number;\n status: UploadStatus;\n}\n\nexport enum UploadState {\n IDLE = 'idle',\n INITIALIZING = 'initializing',\n UPLOADING = 'uploading',\n PAUSED = 'paused',\n CANCELLED = 'cancelled',\n FINALIZING = 'finalizing',\n COMPLETED = 'completed',\n FAILED = 'failed'\n}\n\nexport interface UploadResult {\n success: boolean;\n fileId?: string;\n totalChunks: number;\n totalBytes: number;\n duration: number;\n averageSpeed: number;\n finalizeUploadResponse?: FetchResponseInterface | null;\n statusResponse?: number;\n}\n\nexport interface ResumeData {\n fileId: string;\n fileName: string;\n fileSize: number;\n uploadedChunks: number;\n lastChunkIndex: number;\n lastBytePosition: number;\n chunkSize: number;\n concurrency: number;\n}\n\nexport const DEFAULT_CONFIG: Readonly<ChunkSizeConfig> = Object.freeze({\n defaultChunkSizeMB: 50,\n slowSpeedThresholdMbps: 5,\n slowSpeedChunkSizeMB: 2,\n fileSizeThresholds: [\n { maxSizeMB: 200, chunkSizeMB: 50 },\n { maxSizeMB: 400, chunkSizeMB: 100 },\n { maxSizeMB: 800, chunkSizeMB: 300 },\n { maxSizeMB: 1000, chunkSizeMB: 500 },\n { maxSizeMB: Infinity, chunkSizeMB: 700 }\n ]\n});\n\nexport interface UploadProgress {\n uploadedChunks: number;\n totalChunks: number;\n uploadedBytes: number;\n totalBytes: number;\n percentage: number;\n currentChunk: number;\n speed?: number; // bytes per second\n estimatedTimeRemaining?: number | null; // seconds\n elapsed: number;// secondes écoulées\n}\nexport interface ChunkError {\n chunk: ChunkInfo;\n error: Error;\n attempt: number;\n willRetry: boolean;\n}\n\n/**\n * Expected response structure from initialize endpoint\n */\nexport interface InitializeUploadResponse {\n mediaIdFromServer?: string | number;\n sessionId?: string | number;\n uploadId?: string | number;\n mediaId?: string | number;\n message?: string | number;\n [key: string]: any; // Allow extra fields\n}"]}
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var types = require('../types');
|
|
4
|
+
|
|
5
|
+
var __defProp = Object.defineProperty;
|
|
6
|
+
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
7
|
+
class FileUtils {
|
|
8
|
+
static {
|
|
9
|
+
__name(this, "FileUtils");
|
|
10
|
+
}
|
|
11
|
+
static MB = 1024 * 1024;
|
|
12
|
+
static GB = 1024 * 1024 * 1024;
|
|
13
|
+
static bytesToMB(bytes) {
|
|
14
|
+
return parseFloat((bytes / this.MB).toFixed(2));
|
|
15
|
+
}
|
|
16
|
+
static bytesToGB(bytes) {
|
|
17
|
+
return parseFloat((bytes / this.GB).toFixed(2));
|
|
18
|
+
}
|
|
19
|
+
static formatBytes(bytes) {
|
|
20
|
+
if (bytes === 0) return "0 Bytes";
|
|
21
|
+
const k = 1024;
|
|
22
|
+
const sizes = ["Bytes", "KB", "MB", "GB", "TB"];
|
|
23
|
+
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
24
|
+
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + " " + sizes[i];
|
|
25
|
+
}
|
|
26
|
+
static formatDuration(seconds) {
|
|
27
|
+
const hours = Math.floor(seconds / 3600);
|
|
28
|
+
const minutes = Math.floor(seconds % 3600 / 60);
|
|
29
|
+
const secs = Math.floor(seconds % 60);
|
|
30
|
+
if (hours > 0) return `${hours}h ${minutes}m ${secs}s`;
|
|
31
|
+
if (minutes > 0) return `${minutes}m ${secs}s`;
|
|
32
|
+
return `${secs}s`;
|
|
33
|
+
}
|
|
34
|
+
static calculateChunkSize(fileSize, speedMbps, config = types.DEFAULT_CONFIG) {
|
|
35
|
+
const fileSizeMB = fileSize / this.MB;
|
|
36
|
+
if (speedMbps && speedMbps < config.slowSpeedThresholdMbps) {
|
|
37
|
+
return Math.min(
|
|
38
|
+
config.defaultChunkSizeMB * this.MB,
|
|
39
|
+
config.slowSpeedChunkSizeMB * this.MB
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
for (const threshold of config.fileSizeThresholds) {
|
|
43
|
+
if (fileSizeMB <= threshold.maxSizeMB) {
|
|
44
|
+
return threshold.chunkSizeMB * this.MB;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return config.defaultChunkSizeMB * this.MB;
|
|
48
|
+
}
|
|
49
|
+
static generateFileHash(file) {
|
|
50
|
+
return new Promise((resolve, reject) => {
|
|
51
|
+
const reader = new FileReader();
|
|
52
|
+
reader.onload = async (e) => {
|
|
53
|
+
try {
|
|
54
|
+
const buffer = e.target?.result;
|
|
55
|
+
const hashBuffer = await crypto.subtle.digest("SHA-256", buffer.slice(0, 1024 * 1024));
|
|
56
|
+
const hashArray = Array.from(new Uint8Array(hashBuffer));
|
|
57
|
+
const hashHex = hashArray.map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
58
|
+
resolve(hashHex);
|
|
59
|
+
} catch (error) {
|
|
60
|
+
reject(error);
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
reader.onerror = reject;
|
|
64
|
+
reader.readAsArrayBuffer(file.slice(0, 1024 * 1024));
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
function updateProgressBarHTMLNotified(progress, media_id, filename, providerName = "LocalVideo") {
|
|
69
|
+
const progressBarId = `progress-bar-item_${providerName}_${media_id}`;
|
|
70
|
+
const progressBarContainer = document.querySelector(`#${progressBarId}`);
|
|
71
|
+
const progressBarInner = progressBarContainer?.querySelector(".progress-bar");
|
|
72
|
+
if (progressBarInner) {
|
|
73
|
+
const roundedProgress = Math.round(progress);
|
|
74
|
+
progressBarInner.style.width = `${progress}%`;
|
|
75
|
+
progressBarInner.setAttribute("aria-valuenow", progress.toString());
|
|
76
|
+
progressBarInner.innerText = `${roundedProgress}%`;
|
|
77
|
+
return progressBarContainer.innerHTML;
|
|
78
|
+
} else {
|
|
79
|
+
return `
|
|
80
|
+
<div id="${progressBarId}" class="mb-2" style="width:100%;">
|
|
81
|
+
<small class="control-label text-dark fw-bolder filename-label w-100 d-block text-truncate" title="${filename}">
|
|
82
|
+
${filename}
|
|
83
|
+
</small>
|
|
84
|
+
<div class="progress">
|
|
85
|
+
<div class="progress-bar bg-success progress-bar-striped progress-bar-animated"
|
|
86
|
+
role="progressbar"
|
|
87
|
+
style="width: ${progress}%;"
|
|
88
|
+
aria-valuenow="${progress}"
|
|
89
|
+
aria-valuemin="0"
|
|
90
|
+
aria-valuemax="100">${Math.round(progress)}%</div>
|
|
91
|
+
</div>
|
|
92
|
+
</div>`;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
__name(updateProgressBarHTMLNotified, "updateProgressBarHTMLNotified");
|
|
96
|
+
function createChunkFormData(chunk, options) {
|
|
97
|
+
const formData = new FormData();
|
|
98
|
+
formData.append("chunk", chunk);
|
|
99
|
+
formData.append("chunkIndex", options.chunkIndex.toString());
|
|
100
|
+
formData.append("chunkSize", chunk.size.toString());
|
|
101
|
+
formData.append("totalChunks", options.totalChunks.toString());
|
|
102
|
+
formData.append("fileName", options.fileName);
|
|
103
|
+
formData.append("fileSize", options.fileSize.toString());
|
|
104
|
+
formData.append("fileHash", options.fileHash);
|
|
105
|
+
formData.append("mediaId", options.mediaId);
|
|
106
|
+
if (options.metadata) {
|
|
107
|
+
for (const [key, value] of Object.entries(options.metadata)) {
|
|
108
|
+
if (typeof value === "string" || value instanceof Blob) {
|
|
109
|
+
formData.append(key, value);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
return formData;
|
|
114
|
+
}
|
|
115
|
+
__name(createChunkFormData, "createChunkFormData");
|
|
116
|
+
class ChunkFormDataBuilder {
|
|
117
|
+
static {
|
|
118
|
+
__name(this, "ChunkFormDataBuilder");
|
|
119
|
+
}
|
|
120
|
+
formData;
|
|
121
|
+
constructor(chunk) {
|
|
122
|
+
this.formData = new FormData();
|
|
123
|
+
this.formData.append("chunk", chunk);
|
|
124
|
+
this.formData.append("chunkSize", chunk.size.toString());
|
|
125
|
+
}
|
|
126
|
+
withChunkInfo(chunkIndex, totalChunks) {
|
|
127
|
+
this.formData.append("chunkIndex", chunkIndex.toString());
|
|
128
|
+
this.formData.append("totalChunks", totalChunks.toString());
|
|
129
|
+
return this;
|
|
130
|
+
}
|
|
131
|
+
withFileInfo(fileName, fileSize, fileHash) {
|
|
132
|
+
this.formData.append("fileName", fileName);
|
|
133
|
+
this.formData.append("fileSize", fileSize.toString());
|
|
134
|
+
this.formData.append("fileHash", fileHash);
|
|
135
|
+
return this;
|
|
136
|
+
}
|
|
137
|
+
withSessionId(sessionId) {
|
|
138
|
+
this.formData.append("sessionId", sessionId);
|
|
139
|
+
return this;
|
|
140
|
+
}
|
|
141
|
+
withMetadata(metadata) {
|
|
142
|
+
for (const [key, value] of Object.entries(metadata)) {
|
|
143
|
+
if (typeof value === "string" || value instanceof Blob) {
|
|
144
|
+
this.formData.append(key, value);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
return this;
|
|
148
|
+
}
|
|
149
|
+
build() {
|
|
150
|
+
return this.formData;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
class ExponentialBackoffStrategy {
|
|
154
|
+
static {
|
|
155
|
+
__name(this, "ExponentialBackoffStrategy");
|
|
156
|
+
}
|
|
157
|
+
shouldRetry(attempt, maxRetries) {
|
|
158
|
+
return attempt < maxRetries;
|
|
159
|
+
}
|
|
160
|
+
getDelay(attempt) {
|
|
161
|
+
return Math.pow(2, attempt) * 1e3;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
class LinearBackoffStrategy {
|
|
165
|
+
static {
|
|
166
|
+
__name(this, "LinearBackoffStrategy");
|
|
167
|
+
}
|
|
168
|
+
shouldRetry(attempt, maxRetries) {
|
|
169
|
+
return attempt < maxRetries;
|
|
170
|
+
}
|
|
171
|
+
getDelay(attempt) {
|
|
172
|
+
return (attempt + 1) * 1e3;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
exports.ChunkFormDataBuilder = ChunkFormDataBuilder;
|
|
177
|
+
exports.ExponentialBackoffStrategy = ExponentialBackoffStrategy;
|
|
178
|
+
exports.FileUtils = FileUtils;
|
|
179
|
+
exports.LinearBackoffStrategy = LinearBackoffStrategy;
|
|
180
|
+
exports.createChunkFormData = createChunkFormData;
|
|
181
|
+
exports.updateProgressBarHTMLNotified = updateProgressBarHTMLNotified;
|
|
182
|
+
//# sourceMappingURL=index.js.map
|
|
183
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/utils/index.ts"],"names":["DEFAULT_CONFIG"],"mappings":"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","file":"index.js","sourcesContent":["/*\n * This file is part of the project by AGBOKOUDJO Franck.\n *\n * (c) AGBOKOUDJO Franck <internationaleswebservices@gmail.com>\n * Phone: +229 0167 25 18 86\n * LinkedIn: https://www.linkedin.com/in/internationales-web-apps-services-120520193/\n * Company: INTERNATIONALES WEB APPS & SERVICES\n *\n * For more information, please feel free to contact the author.\n */\nimport {\n ChunkSizeConfig,\n DEFAULT_CONFIG\n} from \"../types\";\n\nexport class FileUtils {\n static readonly MB = 1024 * 1024;\n static readonly GB = 1024 * 1024 * 1024;\n\n static bytesToMB(bytes: number): number {\n return parseFloat((bytes / this.MB).toFixed(2));\n }\n\n static bytesToGB(bytes: number): number {\n return parseFloat((bytes / this.GB).toFixed(2));\n }\n\n static formatBytes(bytes: number): string {\n if (bytes === 0) return '0 Bytes';\n const k = 1024;\n const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];\n const i = Math.floor(Math.log(bytes) / Math.log(k));\n return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];\n }\n\n static formatDuration(seconds: number): string {\n const hours = Math.floor(seconds / 3600);\n const minutes = Math.floor((seconds % 3600) / 60);\n const secs = Math.floor(seconds % 60);\n\n if (hours > 0) return `${hours}h ${minutes}m ${secs}s`;\n if (minutes > 0) return `${minutes}m ${secs}s`;\n return `${secs}s`;\n }\n\n static calculateChunkSize(\n fileSize: number,\n speedMbps?: number,\n config: ChunkSizeConfig = DEFAULT_CONFIG\n ): number {\n const fileSizeMB = fileSize / this.MB;\n\n // Handle slow connections\n if (speedMbps && speedMbps < config.slowSpeedThresholdMbps) {\n return Math.min(\n config.defaultChunkSizeMB * this.MB,\n config.slowSpeedChunkSizeMB * this.MB\n );\n }\n\n // Adjust based on file size\n for (const threshold of config.fileSizeThresholds) {\n if (fileSizeMB <= threshold.maxSizeMB) {\n return threshold.chunkSizeMB * this.MB;\n }\n }\n\n return config.defaultChunkSizeMB * this.MB;\n }\n\n static generateFileHash(file: File|Blob): Promise<string> {\n return new Promise((resolve, reject) => {\n const reader = new FileReader();\n reader.onload = async (e) => {\n try {\n const buffer = e.target?.result as ArrayBuffer;\n const hashBuffer = await crypto.subtle.digest('SHA-256', buffer.slice(0, 1024 * 1024));\n const hashArray = Array.from(new Uint8Array(hashBuffer));\n const hashHex = hashArray.map(b => b.toString(16).padStart(2, '0')).join('');\n resolve(hashHex);\n } catch (error) {\n reject(error);\n }\n };\n reader.onerror = reject;\n reader.readAsArrayBuffer(file.slice(0, 1024 * 1024)); // Hash first 1MB\n });\n }\n}\n\nexport function updateProgressBarHTMLNotified(\n progress: number,\n media_id: number,\n filename: string,\n providerName = \"LocalVideo\"\n): string {\n const progressBarId = `progress-bar-item_${providerName}_${media_id}`;\n // On cherche la barre de progression interne (celle qui a la classe .progress-bar)\n const progressBarContainer = document.querySelector(`#${progressBarId}`);\n const progressBarInner = progressBarContainer?.querySelector('.progress-bar') as HTMLElement;\n\n if (progressBarInner) {\n // Mise à jour du DOM existant\n const roundedProgress = Math.round(progress);\n progressBarInner.style.width = `${progress}%`;\n progressBarInner.setAttribute('aria-valuenow', progress.toString());\n progressBarInner.innerText = `${roundedProgress}%`;\n\n return progressBarContainer!.innerHTML;\n } else {\n // Génération du template initial\n return `\n <div id=\"${progressBarId}\" class=\"mb-2\" style=\"width:100%;\">\n <small class=\"control-label text-dark fw-bolder filename-label w-100 d-block text-truncate\" title=\"${filename}\">\n ${filename}\n </small>\n <div class=\"progress\">\n <div class=\"progress-bar bg-success progress-bar-striped progress-bar-animated\"\n role=\"progressbar\"\n style=\"width: ${progress}%;\"\n aria-valuenow=\"${progress}\"\n aria-valuemin=\"0\"\n aria-valuemax=\"100\">${Math.round(progress)}%</div>\n </div>\n </div>`;\n }\n}\n\n/**\n * Options for creating chunk FormData\n */\nexport interface ChunkFormDataOptions {\n chunkIndex: number;\n totalChunks: number;\n mediaId: string;\n fileName: string;\n fileSize: number;\n fileHash: string;\n metadata?: Record<string, string | Blob>;\n}\n\n/**\n * Creates FormData for uploading a single file chunk.\n * \n * @param chunk - The chunk blob to upload\n * @param options - Chunk upload options\n * @returns FormData ready to be sent to the server\n * \n * @example\n * ```typescript\n * const formData = createChunkFormData(blob, {\n * chunkIndex: 5,\n * totalChunks: 10,\n * mediaId: 'abc123',\n * fileName: 'video.mp4',\n * fileSize: 104857600,\n * fileHash: 'sha256...'\n * });\n * ```\n */\nexport function createChunkFormData(\n chunk: Blob,\n options: ChunkFormDataOptions\n): FormData {\n const formData = new FormData();\n\n // Chunk data\n formData.append('chunk', chunk);\n formData.append('chunkIndex', options.chunkIndex.toString());\n formData.append('chunkSize', chunk.size.toString());\n formData.append('totalChunks', options.totalChunks.toString());\n\n // File metadata\n formData.append('fileName', options.fileName);\n formData.append('fileSize', options.fileSize.toString());\n formData.append('fileHash', options.fileHash);\n\n // Media ID (returned by server after metadata registration)\n formData.append('mediaId', options.mediaId);\n\n // Optional additional data\n if (options.metadata) {\n for (const [key, value] of Object.entries(options.metadata)) {\n if (typeof value === 'string' || value instanceof Blob) {\n formData.append(key, value);\n }\n }\n }\n\n return formData;\n}\n\n/**\n * Builder pattern for creating chunk FormData with fluent API.\n */\nexport class ChunkFormDataBuilder {\n private formData: FormData ;\n\n constructor(chunk: Blob) {\n this.formData = new FormData();\n\n this.formData.append('chunk', chunk);\n this.formData.append('chunkSize', chunk.size.toString());\n }\n\n withChunkInfo(chunkIndex: number, totalChunks: number): this {\n this.formData.append('chunkIndex', chunkIndex.toString());\n this.formData.append('totalChunks', totalChunks.toString());\n return this;\n }\n\n withFileInfo(fileName: string, fileSize: number, fileHash: string): this {\n this.formData.append('fileName', fileName);\n this.formData.append('fileSize', fileSize.toString());\n this.formData.append('fileHash', fileHash);\n return this;\n }\n\n withSessionId(sessionId: string): this {\n this.formData.append('sessionId', sessionId);\n return this;\n }\n\n withMetadata(metadata: Record<string, string | Blob>): this {\n for (const [key, value] of Object.entries(metadata)) {\n if (typeof value === 'string' || value instanceof Blob) {\n this.formData.append(key, value);\n }\n }\n return this;\n }\n\n build(): FormData {\n return this.formData;\n }\n}\n\n// Stratégie de retry configurable\ninterface RetryStrategyInterface {\n shouldRetry(attempt: number, maxRetries: number, error: Error): boolean;\n getDelay(attempt: number): number;\n}\n\nexport class ExponentialBackoffStrategy implements RetryStrategyInterface {\n shouldRetry(attempt: number, maxRetries: number): boolean {\n return attempt < maxRetries;\n }\n\n getDelay(attempt: number): number {\n return Math.pow(2, attempt) * 1000;\n }\n}\n\nexport class LinearBackoffStrategy implements RetryStrategyInterface {\n shouldRetry(attempt: number, maxRetries: number): boolean {\n return attempt < maxRetries;\n }\n\n getDelay(attempt: number): number {\n return (attempt + 1) * 1000; // 1s, 2s, 3s...\n }\n}\n"]}
|