Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "@appwrite.io/console",
"homepage": "https://appwrite.io/support",
"description": "Appwrite is an open-source self-hosted backend server that abstracts and simplifies complex and repetitive development tasks behind a very simple REST API",
"version": "13.0.0",
"version": "13.1.0",
"license": "BSD-3-Clause",
"main": "dist/cjs/sdk.js",
"exports": {
Expand Down
127 changes: 101 additions & 26 deletions src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -390,8 +390,8 @@ class Client {
'x-sdk-name': 'Console',
'x-sdk-platform': 'console',
'x-sdk-language': 'web',
'x-sdk-version': '13.0.0',
'X-Appwrite-Response-Format': '1.9.4',
'x-sdk-version': '13.1.0',
'X-Appwrite-Response-Format': '1.9.5',
};

/**
Expand Down Expand Up @@ -978,44 +978,119 @@ class Client {
return await this.call(method, url, headers, originalPayload);
}

let start = 0;
let response = null;
const totalChunks = Math.ceil(file.size / Client.CHUNK_SIZE);

// Upload first chunk alone to get the upload ID
const firstChunkEnd = Math.min(Client.CHUNK_SIZE, file.size);
const firstChunkHeaders = { ...headers, 'content-range': `bytes 0-${firstChunkEnd - 1}/${file.size}` };
const firstChunk = file.slice(0, firstChunkEnd);
const firstPayload = { ...originalPayload };
firstPayload[fileParam] = new File([firstChunk], file.name);

let response = await this.call(method, url, firstChunkHeaders, firstPayload);
const uploadId = response?.$id;

if (onProgress && typeof onProgress === 'function') {
onProgress({
$id: uploadId,
progress: Math.round((firstChunkEnd / file.size) * 100),
sizeUploaded: firstChunkEnd,
chunksTotal: totalChunks,
chunksUploaded: 1
});
}

while (start < file.size) {
let end = start + Client.CHUNK_SIZE; // Prepare end for the next chunk
if (end >= file.size) {
end = file.size; // Adjust for the last chunk to include the last byte
}
if (totalChunks === 1) {
return response;
}

headers['content-range'] = `bytes ${start}-${end-1}/${file.size}`;
const chunk = file.slice(start, end);
// Prepare remaining chunks
Comment thread
greptile-apps[bot] marked this conversation as resolved.
const chunks: { start: number; end: number }[] = [];
for (let i = 1; i < totalChunks; i++) {
const start = i * Client.CHUNK_SIZE;
const end = Math.min(start + Client.CHUNK_SIZE, file.size);
chunks.push({ start, end });
}

let payload = { ...originalPayload };
payload[fileParam] = new File([chunk], file.name);
// Upload remaining chunks with max concurrency of 8
const CONCURRENCY = 8;
let completedCount = 1;
let uploadedBytes = firstChunkEnd;
let lastResponse = response;
let finalResponse = null;

const isUploadComplete = (chunkResponse: any) => {
const chunksUploaded = chunkResponse?.chunksUploaded;
const chunksTotal = chunkResponse?.chunksTotal ?? totalChunks;
return typeof chunksUploaded === 'number' && typeof chunksTotal === 'number' && chunksUploaded >= chunksTotal;
};

response = await this.call(method, url, headers, payload);
const uploadChunk = async (chunk: typeof chunks[0]) => {
const chunkHeaders = { ...headers };
if (uploadId) {
chunkHeaders['x-appwrite-id'] = uploadId;
}
chunkHeaders['content-range'] = `bytes ${chunk.start}-${chunk.end - 1}/${file.size}`;

const chunkBlob = file.slice(chunk.start, chunk.end);
const chunkPayload = { ...originalPayload };
chunkPayload[fileParam] = new File([chunkBlob], file.name);

const chunkResponse = await this.call(method, url, chunkHeaders, chunkPayload);

completedCount++;
uploadedBytes += (chunk.end - chunk.start);

lastResponse = chunkResponse;
if (isUploadComplete(chunkResponse)) {
finalResponse = chunkResponse;
}

if (onProgress && typeof onProgress === 'function') {
onProgress({
$id: response.$id,
progress: Math.round((end / file.size) * 100),
sizeUploaded: end,
chunksTotal: Math.ceil(file.size / Client.CHUNK_SIZE),
chunksUploaded: Math.ceil(end / Client.CHUNK_SIZE)
$id: uploadId,
progress: Math.round((uploadedBytes / file.size) * 100),
sizeUploaded: uploadedBytes,
chunksTotal: totalChunks,
chunksUploaded: completedCount
});
}

if (response && response.$id) {
headers['x-appwrite-id'] = response.$id;
}
return chunkResponse;
};

start = end;
}
await new Promise<void>((resolve, reject) => {
let nextChunk = 0;
let inFlight = 0;
let completed = 0;

const uploadNext = () => {
if (completed === chunks.length) {
resolve();
return;
}

while (inFlight < CONCURRENCY && nextChunk < chunks.length) {
const chunk = chunks[nextChunk++];
inFlight++;

uploadChunk(chunk)
.then(() => {
inFlight--;
completed++;
uploadNext();
})
.catch(reject);
}
};

uploadNext();
});
Comment thread
greptile-apps[bot] marked this conversation as resolved.

return response;
return finalResponse ?? lastResponse;
}

async ping(): Promise<string> {
async ping(): Promise<any> {
return this.call('GET', new URL(this.config.endpoint + '/ping'));
}

Expand Down