snapshot of https://github.com/RocketChat/Rocket.Chat.git (develop, 2026-09-16; blobs > 131072B omitted)
Some checks failed
Create issues from TODOs / todos (push) Failing after 3s
Close inactive issues / close-issues (push) Has been cancelled
Deploy GitHub Pages / deploy-preview (push) Has been cancelled
CI / ⚙️ Variables Setup (push) Has been cancelled
CI / ⚙️ Test Guard (push) Has been cancelled
CI / 🚀 Notify external services - draft (push) Has been cancelled
CI / 📦 Build Packages (push) Has been cancelled
CI / 📦 Meteor Build (coverage) (push) Has been cancelled
CI / 📦 Meteor Build (production) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [account-service presence-service omnichannel-transcript-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [account-service presence-service omnichannel-transcript-service], fips) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [authorization-service queue-worker-service ddp-streamer-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [authorization-service queue-worker-service ddp-streamer-service], fips) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], coverage) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], fips) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [account-service presence-service omnichannel-transcript-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [authorization-service queue-worker-service ddp-streamer-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [rocketchat], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [rocketchat], coverage) (push) Has been cancelled
CI / 🚢 Publish Docker Images (ghcr.io) (push) Has been cancelled
CI / 📦 Track Image Sizes (push) Has been cancelled
CI / 🔎 Code Check - Action Lint (push) Has been cancelled
CI / 🔎 Code Check (push) Has been cancelled
CI / 🔨 Test Storybook (push) Has been cancelled
CI / 🔨 Test Unit (push) Has been cancelled
CI / 🔨 Test API (CE) (push) Has been cancelled
CI / 🔨 Test API Livechat (CE) (push) Has been cancelled
CI / 🔨 Test UI (CE) (push) Has been cancelled
CI / 🔨 Test API (EE) (push) Has been cancelled
CI / 🔨 Test API Livechat (EE) (push) Has been cancelled
CI / 🔨 Test API Apps (deno-runtime - EE) (push) Has been cancelled
CI / 🔨 Test UI (EE) (push) Has been cancelled
CI / 🔨 Test API (FIPS) (push) Has been cancelled
CI / 🔨 Test API Livechat (FIPS) (push) Has been cancelled
CI / 🔨 Test UI (FIPS) (push) Has been cancelled
CI / 🔨 Test Federation Matrix (push) Has been cancelled
CI / 📊 Report Coverage (push) Has been cancelled
CI / ✅ Tests Done (push) Has been cancelled
CI / 🚀 Publish build assets (push) Has been cancelled
CI / 🚀 Publish Docker Images (DockerHub) (push) Has been cancelled
CI / 🚀 Notify external services (push) Has been cancelled
CI / Update Version Durability (push) Has been cancelled
Release / Release (push) Has been cancelled
Auto-close duplicate issues / auto-close-duplicates (push) Failing after 1s
Code scanning - action / CodeQL-Build (push) Failing after 1s
Some checks failed
Create issues from TODOs / todos (push) Failing after 3s
Close inactive issues / close-issues (push) Has been cancelled
Deploy GitHub Pages / deploy-preview (push) Has been cancelled
CI / ⚙️ Variables Setup (push) Has been cancelled
CI / ⚙️ Test Guard (push) Has been cancelled
CI / 🚀 Notify external services - draft (push) Has been cancelled
CI / 📦 Build Packages (push) Has been cancelled
CI / 📦 Meteor Build (coverage) (push) Has been cancelled
CI / 📦 Meteor Build (production) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [account-service presence-service omnichannel-transcript-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [account-service presence-service omnichannel-transcript-service], fips) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [authorization-service queue-worker-service ddp-streamer-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [authorization-service queue-worker-service ddp-streamer-service], fips) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], coverage) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], fips) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [account-service presence-service omnichannel-transcript-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [authorization-service queue-worker-service ddp-streamer-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [rocketchat], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [rocketchat], coverage) (push) Has been cancelled
CI / 🚢 Publish Docker Images (ghcr.io) (push) Has been cancelled
CI / 📦 Track Image Sizes (push) Has been cancelled
CI / 🔎 Code Check - Action Lint (push) Has been cancelled
CI / 🔎 Code Check (push) Has been cancelled
CI / 🔨 Test Storybook (push) Has been cancelled
CI / 🔨 Test Unit (push) Has been cancelled
CI / 🔨 Test API (CE) (push) Has been cancelled
CI / 🔨 Test API Livechat (CE) (push) Has been cancelled
CI / 🔨 Test UI (CE) (push) Has been cancelled
CI / 🔨 Test API (EE) (push) Has been cancelled
CI / 🔨 Test API Livechat (EE) (push) Has been cancelled
CI / 🔨 Test API Apps (deno-runtime - EE) (push) Has been cancelled
CI / 🔨 Test UI (EE) (push) Has been cancelled
CI / 🔨 Test API (FIPS) (push) Has been cancelled
CI / 🔨 Test API Livechat (FIPS) (push) Has been cancelled
CI / 🔨 Test UI (FIPS) (push) Has been cancelled
CI / 🔨 Test Federation Matrix (push) Has been cancelled
CI / 📊 Report Coverage (push) Has been cancelled
CI / ✅ Tests Done (push) Has been cancelled
CI / 🚀 Publish build assets (push) Has been cancelled
CI / 🚀 Publish Docker Images (DockerHub) (push) Has been cancelled
CI / 🚀 Notify external services (push) Has been cancelled
CI / Update Version Durability (push) Has been cancelled
Release / Release (push) Has been cancelled
Auto-close duplicate issues / auto-close-duplicates (push) Failing after 1s
Code scanning - action / CodeQL-Build (push) Failing after 1s
This commit is contained in:
219
apps/meteor/client/lib/chats/uploads.ts
Normal file
219
apps/meteor/client/lib/chats/uploads.ts
Normal file
@@ -0,0 +1,219 @@
|
||||
import type { IMessage, IRoom } from '@rocket.chat/core-typings';
|
||||
import { Emitter } from '@rocket.chat/emitter';
|
||||
import { Random } from '@rocket.chat/random';
|
||||
import fileSize from 'filesize';
|
||||
|
||||
import { getErrorMessage } from '../errorHandling';
|
||||
import type { UploadsAPI, EncryptedFileUploadContent } from './ChatAPI';
|
||||
import { isEncryptedUpload, type Upload } from './Upload';
|
||||
import { i18n } from '../../../app/utils/lib/i18n';
|
||||
import { sdk } from '../SDKClient';
|
||||
import { USER_ACTIVITIES, UserAction } from '../UserAction';
|
||||
import { settings } from '../settings';
|
||||
import { fileUploadIsValidContentType } from '../utils/restrictions';
|
||||
|
||||
class UploadsStore extends Emitter<{ update: void; [x: `cancelling-${Upload['id']}`]: void }> implements UploadsAPI {
|
||||
private rid: string;
|
||||
|
||||
private tmid?: string;
|
||||
|
||||
constructor({ rid, tmid }: { rid: IRoom['_id']; tmid?: IMessage['_id'] }) {
|
||||
super();
|
||||
this.rid = rid;
|
||||
this.tmid = tmid;
|
||||
}
|
||||
|
||||
private uploads: readonly Upload[] = [];
|
||||
|
||||
private processingUploads: boolean = false;
|
||||
|
||||
set = (uploads: Upload[]): void => {
|
||||
this.uploads = uploads;
|
||||
this.emit('update');
|
||||
};
|
||||
|
||||
get = (): readonly Upload[] => this.uploads;
|
||||
|
||||
subscribe = (callback: () => void): (() => void) => this.on('update', callback);
|
||||
|
||||
setProcessingUploads = (processing: boolean): void => {
|
||||
this.processingUploads = processing;
|
||||
this.emit('update');
|
||||
};
|
||||
|
||||
getProcessingUploads = (): boolean => this.processingUploads;
|
||||
|
||||
cancel = (id: Upload['id']): void => {
|
||||
this.emit(`cancelling-${id}`);
|
||||
};
|
||||
|
||||
wipeFailedOnes = (): void => {
|
||||
this.set(this.uploads.filter((upload) => !upload.error));
|
||||
};
|
||||
|
||||
private updateUpload(id: Upload['id'], patch: Partial<Upload>): void {
|
||||
this.set(this.uploads.map((upload) => (upload.id !== id ? upload : { ...upload, ...patch })));
|
||||
}
|
||||
|
||||
removeUpload = (id: Upload['id']): void => {
|
||||
this.set(this.uploads.filter((upload) => upload.id !== id));
|
||||
|
||||
if (this.uploads.length === 0) {
|
||||
UserAction.stop(this.rid, USER_ACTIVITIES.USER_UPLOADING, { tmid: this.tmid });
|
||||
}
|
||||
};
|
||||
|
||||
editUploadAltText = (uploadId: Upload['id'], altText: string) => {
|
||||
this.set(
|
||||
this.uploads.map((upload) => {
|
||||
if (upload.id !== uploadId) {
|
||||
return upload;
|
||||
}
|
||||
|
||||
return {
|
||||
...upload,
|
||||
altText,
|
||||
...(isEncryptedUpload(upload) && {
|
||||
metadataForEncryption: { ...upload.metadataForEncryption, altText },
|
||||
}),
|
||||
};
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
editUploadFileName = (uploadId: Upload['id'], fileName: Upload['file']['name']) => {
|
||||
try {
|
||||
this.set(
|
||||
this.uploads.map((upload) => {
|
||||
if (upload.id !== uploadId) {
|
||||
return upload;
|
||||
}
|
||||
|
||||
return {
|
||||
...upload,
|
||||
file: new File([upload.file], fileName, upload.file),
|
||||
...(isEncryptedUpload(upload) && {
|
||||
metadataForEncryption: { ...upload.metadataForEncryption, name: fileName },
|
||||
}),
|
||||
};
|
||||
}),
|
||||
);
|
||||
} catch (error) {
|
||||
this.set(
|
||||
this.uploads.map((upload) => {
|
||||
if (upload.id !== uploadId) {
|
||||
return upload;
|
||||
}
|
||||
|
||||
return {
|
||||
...upload,
|
||||
percentage: 0,
|
||||
error: new Error(i18n.t('FileUpload_Update_Failed')),
|
||||
};
|
||||
}),
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
clear = () => {
|
||||
this.set([]);
|
||||
UserAction.stop(this.rid, USER_ACTIVITIES.USER_UPLOADING, { tmid: this.tmid });
|
||||
};
|
||||
|
||||
async send(file: File, encrypted?: EncryptedFileUploadContent): Promise<void> {
|
||||
const maxFileSize = settings.peek('FileUpload_MaxFileSize');
|
||||
const invalidContentType = !fileUploadIsValidContentType(encrypted ? encrypted.rawFile.type : file.type);
|
||||
const id = Random.id();
|
||||
|
||||
this.set([
|
||||
...this.uploads,
|
||||
{
|
||||
id,
|
||||
file: encrypted ? encrypted.rawFile : file,
|
||||
percentage: 0,
|
||||
...(encrypted && {
|
||||
encryptedFile: encrypted.encryptedFile,
|
||||
metadataForEncryption: encrypted.fileContent.raw,
|
||||
}),
|
||||
},
|
||||
]);
|
||||
|
||||
try {
|
||||
await new Promise((resolve, reject) => {
|
||||
if (file.size === 0) {
|
||||
return reject(new Error(i18n.t('FileUpload_File_Empty')));
|
||||
}
|
||||
|
||||
// -1 maxFileSize means there is no limit
|
||||
if (maxFileSize > -1 && (file.size || 0) > maxFileSize) {
|
||||
return reject(new Error(i18n.t('File_exceeds_allowed_size_of_bytes', { size: fileSize(maxFileSize) })));
|
||||
}
|
||||
|
||||
if (invalidContentType) {
|
||||
return reject(new Error(i18n.t('FileUpload_MediaType_NotAccepted__type__', { type: file.type })));
|
||||
}
|
||||
|
||||
const xhr = sdk.rest.upload(
|
||||
`/v1/rooms.media/${this.rid}`,
|
||||
{
|
||||
file,
|
||||
...(encrypted && {
|
||||
content: JSON.stringify(encrypted.fileContent.encrypted),
|
||||
}),
|
||||
},
|
||||
{
|
||||
load: (event) => {
|
||||
resolve(event);
|
||||
},
|
||||
progress: (event) => {
|
||||
if (!event.lengthComputable) {
|
||||
return;
|
||||
}
|
||||
const progress = (event.loaded / event.total) * 100;
|
||||
this.updateUpload(id, { percentage: Math.min(Math.round(progress), 99) || 0 });
|
||||
},
|
||||
error: (event) => {
|
||||
this.updateUpload(id, { percentage: 0, error: new Error(xhr.responseText) });
|
||||
reject(event);
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
xhr.onload = () => {
|
||||
try {
|
||||
if (xhr.readyState !== xhr.DONE) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (xhr.status === 400) {
|
||||
const error = JSON.parse(xhr.responseText);
|
||||
this.updateUpload(id, { percentage: 0, error: new Error(error.error) });
|
||||
return;
|
||||
}
|
||||
|
||||
if (xhr.status === 200) {
|
||||
const result = JSON.parse(xhr.responseText);
|
||||
this.updateUpload(id, { id: result.file._id, url: result.file.url, percentage: 100 });
|
||||
return;
|
||||
}
|
||||
|
||||
this.updateUpload(id, { percentage: 0, error: new Error(i18n.t('FileUpload_Error')) });
|
||||
} catch (error) {
|
||||
this.updateUpload(id, { percentage: 0, error: new Error(getErrorMessage(error)) });
|
||||
}
|
||||
};
|
||||
|
||||
this.once(`cancelling-${id}`, () => {
|
||||
xhr.abort();
|
||||
this.removeUpload(id);
|
||||
reject(new Error(i18n.t('FileUpload_Canceled')));
|
||||
});
|
||||
});
|
||||
} catch (error: unknown) {
|
||||
this.updateUpload(id, { percentage: 0, error: new Error(getErrorMessage(error)) });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const createUploadsAPI = ({ rid, tmid }: { rid: IRoom['_id']; tmid?: IMessage['_id'] }): UploadsAPI =>
|
||||
new UploadsStore({ rid, tmid });
|
||||
Reference in New Issue
Block a user