Skip to content

Commit 8a74cd9

Browse files
committed
Layer tus-js-client on top of Axios
This ensures that requests coming from tus-js-client have the same defaults as the ones coming from the rest of the UI. In particular, this ensures that TUS requests include the `X-CSRFTOKEN` header. Currently, this doesn't matter much, because TUS requests are authenticated using the token. However, I'd like to get rid of token authentication in the UI, after which `X-CSRFTOKEN` will become important.
1 parent 9aeb115 commit 8a74cd9

File tree

3 files changed

+68
-11
lines changed

3 files changed

+68
-11
lines changed

cvat-core/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "cvat-core",
3-
"version": "15.1.0",
3+
"version": "15.1.1",
44
"type": "module",
55
"description": "Part of Computer Vision Tool which presents an interface for client-side integration",
66
"main": "src/api.ts",

cvat-core/src/axios-tus.ts

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
// Copyright (C) 2024 CVAT.ai Corporation
2+
//
3+
// SPDX-License-Identifier: MIT
4+
5+
import Axios, { AxiosRequestConfig, AxiosResponse } from 'axios';
6+
import * as tus from 'tus-js-client';
7+
8+
class AxiosHttpResponse implements tus.HttpResponse {
9+
readonly #axiosResponse: AxiosResponse;
10+
11+
constructor(axiosResponse: AxiosResponse) {
12+
this.#axiosResponse = axiosResponse;
13+
}
14+
15+
getStatus(): number { return this.#axiosResponse.status; }
16+
getHeader(header: string): string { return this.#axiosResponse.headers[header.toLowerCase()]; }
17+
getBody(): string { return this.#axiosResponse.data; }
18+
getUnderlyingObject(): any { return this.#axiosResponse; }
19+
}
20+
21+
class AxiosHttpRequest implements tus.HttpRequest {
22+
readonly #axiosConfig: AxiosRequestConfig;
23+
readonly #abortController: AbortController;
24+
25+
constructor(method: string, url: string) {
26+
this.#abortController = new AbortController();
27+
this.#axiosConfig = {
28+
method, url, headers: {}, signal: this.#abortController.signal,
29+
};
30+
}
31+
32+
getMethod(): string { return this.#axiosConfig.method; }
33+
getURL(): string { return this.#axiosConfig.url; }
34+
35+
setHeader(header: string, value: string): void {
36+
this.#axiosConfig.headers[header.toLowerCase()] = value;
37+
}
38+
getHeader(header: string): string | undefined {
39+
return this.#axiosConfig.headers[header.toLowerCase()];
40+
}
41+
42+
setProgressHandler(handler: (bytesSent: number) => void): void {
43+
this.#axiosConfig.onUploadProgress = (progressEvent) => {
44+
handler(progressEvent.loaded);
45+
};
46+
}
47+
48+
async send(body: any): Promise<tus.HttpResponse> {
49+
const axiosResponse = await Axios({ ...this.#axiosConfig, data: body });
50+
return new AxiosHttpResponse(axiosResponse);
51+
}
52+
53+
async abort(): Promise<void> { this.#abortController.abort(); }
54+
55+
getUnderlyingObject(): any { return this.#axiosConfig; }
56+
}
57+
58+
class AxiosHttpStack implements tus.HttpStack {
59+
createRequest(method: string, url: string): tus.HttpRequest {
60+
return new AxiosHttpRequest(method, url);
61+
}
62+
getName(): string { return 'AxiosHttpStack'; }
63+
}
64+
65+
export const axiosTusHttpStack = new AxiosHttpStack();

cvat-core/src/server-proxy.ts

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import * as tus from 'tus-js-client';
1010
import { ChunkQuality } from 'cvat-data';
1111

1212
import './axios-config';
13+
import { axiosTusHttpStack } from './axios-tus';
1314
import {
1415
SerializedLabel, SerializedAnnotationFormats, ProjectsFilter,
1516
SerializedProject, SerializedTask, TasksFilter, SerializedUser, SerializedOrganization,
@@ -117,7 +118,6 @@ function fetchAll(url, filter = {}): Promise<any> {
117118
}
118119

119120
async function chunkUpload(file: File, uploadConfig): Promise<{ uploadSentSize: number; filename: string }> {
120-
const params = enableOrganization();
121121
const {
122122
endpoint, chunkSize, totalSize, onUpdate, metadata, totalSentSize,
123123
} = uploadConfig;
@@ -130,9 +130,7 @@ async function chunkUpload(file: File, uploadConfig): Promise<{ uploadSentSize:
130130
filetype: file.type,
131131
...metadata,
132132
},
133-
headers: {
134-
Authorization: Axios.defaults.headers.common.Authorization,
135-
},
133+
httpStack: axiosTusHttpStack,
136134
chunkSize,
137135
retryDelays: [2000, 4000, 8000, 16000, 32000, 64000],
138136
onShouldRetry(err: tus.DetailedError | Error): boolean {
@@ -151,12 +149,6 @@ async function chunkUpload(file: File, uploadConfig): Promise<{ uploadSentSize:
151149
onError(error) {
152150
reject(error);
153151
},
154-
onBeforeRequest(req) {
155-
const xhr = req.getUnderlyingObject();
156-
const { org } = params;
157-
req.setHeader('X-Organization', org);
158-
xhr.withCredentials = true;
159-
},
160152
onProgress(bytesUploaded) {
161153
if (onUpdate && Number.isInteger(totalSentSize) && Number.isInteger(totalSize)) {
162154
const currentUploadedSize = totalSentSize + bytesUploaded;

0 commit comments

Comments
 (0)