Skip to content

Commit 5df93c0

Browse files
committed
Enhance upload functionality and refactor API integration
- Updated the Upload component to utilize useOutletContext for improved context management. - Refactored MultipartUpload to accept requestConfig as a parameter, enhancing API request handling. - Streamlined error handling in MD5WorkerManager and MultipartUpload for clearer feedback during upload processes. - Removed deprecated error handling patterns and improved type safety across upload-related functions. - Cleaned up imports and organized code for better readability and maintainability.
1 parent 5b83e98 commit 5df93c0

File tree

6 files changed

+60
-92
lines changed

6 files changed

+60
-92
lines changed

apps/cyberstorm-remix/app/upload/upload.tsx

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ import {
3535
import { UserMedia } from "@thunderstore/ts-uploader/src/client/types";
3636
import { DapperTs } from "@thunderstore/dapper-ts";
3737
import { MetaFunction } from "@remix-run/node";
38-
import { useLoaderData } from "@remix-run/react";
38+
import { useLoaderData, useOutletContext } from "@remix-run/react";
3939
// import {
4040
// packageSubmissionErrorSchema,
4141
// packageSubmissionStatusSchema,
@@ -49,6 +49,7 @@ import {
4949
packageSubmissionErrorSchema,
5050
packageSubmissionStatusSchema,
5151
} from "@thunderstore/thunderstore-api";
52+
import { OutletContextShape } from "../root";
5253

5354
interface CommunityOption {
5455
value: string;
@@ -88,6 +89,9 @@ export async function clientLoader() {
8889
export default function Upload() {
8990
const uploadData = useLoaderData<typeof loader | typeof clientLoader>();
9091

92+
const outletContext = useOutletContext() as OutletContextShape;
93+
const requestConfig = outletContext.requestConfig;
94+
9195
const communityOptions: CommunityOption[] = [];
9296
const [categoryOptions, setCategoryOptions] = useState<
9397
{ communityId: string; categories: CategoryOption[] }[]
@@ -164,15 +168,12 @@ export default function Upload() {
164168
if (!config.apiHost) {
165169
throw new Error("API host is not configured");
166170
}
167-
const upload = new MultipartUpload({
168-
file,
169-
api: {
170-
domain: config.apiHost,
171-
authorization: config.sessionId
172-
? `Session ${config.sessionId}`
173-
: undefined,
171+
const upload = new MultipartUpload(
172+
{
173+
file,
174174
},
175-
});
175+
requestConfig
176+
);
176177

177178
setLock(true);
178179
setHandle(upload);

packages/ts-uploader/src/client/endpoints.ts

Lines changed: 2 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,14 @@
1+
import { RequestConfig } from "@thunderstore/thunderstore-api";
12
import {
23
UserMedia,
34
InitUploadResponse,
45
FinishUploadRequest,
56
InitUploadRequest,
67
} from "./types";
78
import { UsermediaUrls } from "./urls";
8-
import { apiFetch } from "./fetch";
9-
import { ApiConfig } from "./config";
10-
11-
type RequestConfig = {
12-
path: string;
13-
data?: object;
14-
};
15-
async function apiPost<T>(api: ApiConfig, request: RequestConfig) {
16-
const response = await apiFetch({
17-
url: `${api.domain}${request.path}`,
18-
data: request.data,
19-
authorization: api.authorization,
20-
method: "POST",
21-
});
22-
return (await response.json()) as T;
23-
}
249

2510
type ApiCall<Req = undefined, Res = undefined, Args = object> = (
26-
api: ApiConfig,
11+
requestConfig: RequestConfig,
2712
args: {
2813
data: Req;
2914
} & Args

packages/ts-uploader/src/state/BaseUpload.ts

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ import {
88
UploadProgress,
99
UploadStatus,
1010
} from "./types";
11-
import { RequestConfig } from "@thunderstore/thunderstore-api";
1211

1312
export abstract class BaseUpload implements IBaseUploadHandle {
1413
protected status: UploadStatus = "not_started";
@@ -24,14 +23,13 @@ export abstract class BaseUpload implements IBaseUploadHandle {
2423
protected error?: UploadError;
2524
protected partsProgress: { [key: string]: UploadPartProgress } = {};
2625
protected config: UploadConfig;
27-
protected requestConfig: RequestConfig;
2826
protected isAborted = false;
2927

3028
readonly onProgress = new TypedEventEmitter<UploadProgress>();
3129
readonly onStatusChange = new TypedEventEmitter<UploadStatus>();
3230
readonly onError = new TypedEventEmitter<UploadError>();
3331

34-
constructor(config: UploadConfig = {}, requestConfig: RequestConfig = {}) {
32+
constructor(config: UploadConfig = {}) {
3533
this.config = {
3634
maxRetries: 3,
3735
retryDelay: 1000,
@@ -40,7 +38,6 @@ export abstract class BaseUpload implements IBaseUploadHandle {
4038
timeout: 30000,
4139
...config,
4240
};
43-
this.requestConfig = requestConfig;
4441
}
4542

4643
get progress(): UploadProgress {

packages/ts-uploader/src/state/MultipartUpload.ts

Lines changed: 38 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,16 @@
11
import { UserMedia } from "../client/types";
2-
import { UsermediaEndpoints } from "../client/endpoints";
3-
import { ApiConfig } from "../client/config";
42
import { TypedEventEmitter } from "@thunderstore/typed-event-emitter";
53
import { BaseUpload } from "./BaseUpload";
64
import { UploadConfig, UploadProgress } from "./types";
5+
import { getMD5WorkerManager, MD5WorkerManager } from "../workers";
76
import {
8-
getMD5WorkerManager,
9-
MD5WorkerManager,
10-
MD5WorkerManagerError,
11-
} from "../workers";
12-
import {
7+
postUsermediaAbort,
138
postUsermediaFinish,
149
postUsermediaInitiate,
10+
RequestConfig,
1511
} from "@thunderstore/thunderstore-api";
1612

1713
export type MultiPartUploadOptions = {
18-
api: ApiConfig;
1914
file: File;
2015
maxConcurrentParts?: number;
2116
bandwidthLimit?: number;
@@ -48,7 +43,6 @@ export interface IUploadHandle {
4843

4944
export class MultipartUpload extends BaseUpload {
5045
private file: File;
51-
private api: ApiConfig;
5246
private parts: UploadPart[] = [];
5347
private partStates: {
5448
[key: string]: {
@@ -70,13 +64,18 @@ export class MultipartUpload extends BaseUpload {
7064
// private completedParts: { ETag: string; PartNumber: number }[] = [];
7165
private handle?: UserMedia;
7266
private md5WorkerManager: MD5WorkerManager;
67+
private requestConfig: () => RequestConfig;
7368

74-
constructor(options: MultiPartUploadOptions, config?: UploadConfig) {
69+
constructor(
70+
options: MultiPartUploadOptions,
71+
requestConfig: () => RequestConfig,
72+
config?: UploadConfig
73+
) {
7574
super(config);
7675
this.file = options.file;
77-
this.api = options.api;
7876
this.metrics.totalBytes = this.file.size;
7977
this.md5WorkerManager = getMD5WorkerManager();
78+
this.requestConfig = requestConfig;
8079
}
8180

8281
get uploadHandle(): UserMedia | undefined {
@@ -101,19 +100,23 @@ export class MultipartUpload extends BaseUpload {
101100
this.metrics.startTime = Date.now();
102101
this.metrics.lastUpdateTime = this.metrics.startTime;
103102

103+
console.log("requestConfig", this.requestConfig);
104+
104105
// Initialize upload
105106
const initiateResult = await postUsermediaInitiate({
106-
config: () => this.requestConfig,
107+
config: this.requestConfig,
107108
params: {},
108109
data: {
109110
filename: this.file.name,
110111
file_size_bytes: this.file.size,
111112
},
112113
queryParams: {},
114+
useSession: true,
113115
});
114116

115117
this.handle = initiateResult.user_media;
116118

119+
console.log("initiateResult", initiateResult);
117120
// Create parts
118121
this.parts = initiateResult.upload_urls.map((x) => ({
119122
payload: this.slicePart(x.offset, x.length),
@@ -123,8 +126,11 @@ export class MultipartUpload extends BaseUpload {
123126
},
124127
}));
125128

129+
console.log("parts", this.parts);
130+
126131
// Prepare parts
127-
for (let i = 0; i < this.parts.length; i) {
132+
for (let i = 0; i < this.parts.length; i++) {
133+
console.log("part", this.parts[i]);
128134
const uniqueId = `${this.handle.uuid}-${this.parts[i].meta.part_number}`;
129135
this.partStates[uniqueId] = {
130136
part: this.parts[i],
@@ -145,13 +151,15 @@ export class MultipartUpload extends BaseUpload {
145151
if (!this.handle) {
146152
throw new Error("Upload handle not found");
147153
}
154+
console.log("uploading part", part);
148155
this.onGoingUploads.push(
149156
this.uploadPart(
150157
`${this.handle.uuid}-${part.meta.part_number}`,
151158
part,
152159
this.md5WorkerManager
153160
)
154161
);
162+
console.log("onGoingUploads", this.onGoingUploads);
155163
});
156164
await Promise.all(this.onGoingUploads);
157165
}
@@ -176,28 +184,28 @@ export class MultipartUpload extends BaseUpload {
176184

177185
// Complete upload
178186
const finishResult = await postUsermediaFinish({
179-
config: () => this.requestConfig,
187+
config: this.requestConfig,
180188
params: {
181189
uuid: this.handle.uuid,
182190
},
183191
data: {
184192
parts: completeParts,
185193
},
186194
queryParams: {},
195+
useSession: true,
187196
});
188197

189198
this.handle = finishResult;
190199

191200
this.setStatus("complete");
192201
} catch (error) {
193-
if (error instanceof Error) {
194-
this.setError({
195-
code: "UPLOAD_FAILED",
196-
message: error.message,
197-
retryable: true,
198-
details: error,
199-
});
200-
}
202+
console.log("error", error);
203+
this.setError({
204+
code: "UPLOAD_FAILED",
205+
message: error.message,
206+
retryable: true,
207+
details: error,
208+
});
201209
this.setStatus("failed");
202210
}
203211
}
@@ -212,10 +220,6 @@ export class MultipartUpload extends BaseUpload {
212220
part.payload
213221
);
214222

215-
if (checksum instanceof MD5WorkerManagerError) {
216-
throw checksum;
217-
}
218-
219223
this.partStates[uniqueId] = {
220224
part,
221225
uniqueId,
@@ -307,9 +311,14 @@ export class MultipartUpload extends BaseUpload {
307311
}
308312
// TODO: This should probably change some status to aborted, so that the
309313
// frontend can show that the upload was aborted.
310-
await UsermediaEndpoints.abort(this.api, {
311-
uuid: this.handle.uuid,
312-
data: undefined,
314+
await postUsermediaAbort({
315+
config: this.requestConfig,
316+
params: {
317+
uuid: this.handle.uuid,
318+
},
319+
data: {},
320+
queryParams: {},
321+
useSession: true,
313322
});
314323
}
315324

packages/ts-uploader/src/workers/MD5WorkerManager.ts

Lines changed: 8 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -10,18 +10,6 @@ export type MD5ErrorEvent = {
1010
uniqueId: string;
1111
};
1212

13-
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
14-
interface MD5WorkerManagerError extends Error {}
15-
16-
interface MD5WorkerManagerErrorConstructor extends ErrorConstructor {
17-
new (message?: string): MD5WorkerManagerError;
18-
(message?: string): MD5WorkerManagerError;
19-
readonly prototype: MD5WorkerManagerError;
20-
}
21-
22-
// eslint-disable-next-line no-var
23-
export var MD5WorkerManagerError: MD5WorkerManagerErrorConstructor;
24-
2513
export class MD5WorkerManager {
2614
private workers: Worker[] = [];
2715
private isInitialized = false;
@@ -50,14 +38,9 @@ export class MD5WorkerManager {
5038
this.isInitialized = false;
5139
}
5240

53-
calculateMD5(
54-
uniqueId: string,
55-
data: Blob
56-
): Promise<string | MD5WorkerManagerError> {
41+
calculateMD5(uniqueId: string, data: Blob): Promise<string> {
5742
if (!this.isInitialized) {
58-
return Promise.reject(
59-
new MD5WorkerManagerError("MD5 worker not initialized")
60-
);
43+
throw new Error("MD5 worker not initialized");
6144
}
6245

6346
let worker: Worker | null = null;
@@ -68,31 +51,24 @@ export class MD5WorkerManager {
6851
type: "module",
6952
});
7053
if (!worker) {
71-
return Promise.reject(
72-
new MD5WorkerManagerError("Failed to create MD5 worker")
73-
);
54+
throw new Error("Failed to create MD5 worker");
7455
}
7556
this.workers.push(worker);
7657
} catch (error) {
77-
return Promise.reject(
78-
new MD5WorkerManagerError(`Failed to initialize MD5 worker: ${error}`)
79-
);
58+
throw new Error(`Failed to initialize MD5 worker: ${error}`);
8059
}
8160

82-
return new Promise((resolve, reject) => {
61+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
62+
return new Promise((resolve, _reject) => {
8363
worker!.onmessage = (event: MessageEvent) => {
8464
const typeCastedEvent = event.data as MD5CompleteEvent | MD5ErrorEvent;
8565
if (typeCastedEvent.uniqueId === uniqueId) {
8666
if (typeCastedEvent.type === "complete") {
8767
resolve(typeCastedEvent.md5);
8868
} else if (typeCastedEvent.type === "error") {
89-
reject(
90-
new MD5WorkerManagerError(
91-
`MD5 worker error: ${typeCastedEvent.error}`
92-
)
93-
);
69+
throw new Error(`MD5 worker error: ${typeCastedEvent.error}`);
9470
} else {
95-
reject(new MD5WorkerManagerError("Unknown event type"));
71+
throw new Error("Unknown event type");
9672
}
9773
}
9874
};
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
1-
export { MD5WorkerManager, MD5WorkerManagerError } from "./MD5WorkerManager";
1+
export { MD5WorkerManager } from "./MD5WorkerManager";
22
export { getMD5WorkerManager, terminateWorkers } from "./workerUtils";

0 commit comments

Comments
 (0)