Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
cec2f53
Updated dependencies and updated related tests
CandelR Apr 10, 2026
4dfefb5
Merge branch 'master' into feature/update-sdk-and-fix-security-issues
CandelR Apr 10, 2026
b04549c
exclude 'openpgp'from optimizeDeps and remove cache to fix failing ci…
CandelR Apr 10, 2026
db0410b
Added no cache flag to ci tests
CandelR Apr 10, 2026
3fd2b7e
Exclude 'openpgp' from optimizer dependencies in vitest configuration
CandelR Apr 10, 2026
907be85
Added retries and optimize deps for vitest
CandelR Apr 13, 2026
f979f61
Add server warmup configuration with client files for improved perfor…
CandelR Apr 13, 2026
907beed
Update warmup client files for improved server performance
CandelR Apr 13, 2026
f01023b
Added all files to warmup
CandelR Apr 13, 2026
c3a7884
Remove unused import
CandelR Apr 13, 2026
7904b60
Merge branch 'master' into feature/update-sdk-and-fix-security-issues
CandelR Apr 13, 2026
4400fae
Merge branch 'master' into feature/update-sdk-and-fix-security-issues
CandelR Apr 28, 2026
2159d26
Updated test descriptions, remove HTTP codes as magic numbers and vit…
CandelR Apr 28, 2026
46a24ba
Removed cache option from unit test command in CI workflow
CandelR Apr 28, 2026
80f8b2f
Merge branch 'master' into feature/update-sdk-and-fix-security-issues
CandelR May 4, 2026
b1b5873
Merge branch 'master' into feature/update-sdk-and-fix-security-issues
CandelR May 5, 2026
e793e66
Merge branch 'master' into feature/update-sdk-and-fix-security-issues
xabg2 May 5, 2026
357cf8e
Merge branch 'master' into feature/update-sdk-and-fix-security-issues
CandelR May 5, 2026
bea6e3d
Added rate limit throttler button for QA
CandelR May 5, 2026
acb1b86
Display ratelimit throttler in prod env
CandelR May 5, 2026
1aa8089
Remove RateLimitThrottler component and clean up imports in SidenavWr…
CandelR May 5, 2026
6ce2a3f
Merge branch 'master' into feature/update-sdk-and-fix-security-issues
CandelR May 6, 2026
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
19 changes: 10 additions & 9 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
"@stripe/react-stripe-js": "^2.7.1",
"@stripe/stripe-js": "^3.5.0",
"@typeform/embed-react": "^1.19.0",
"@vitest/browser": "^2.1.9",
"@vitest/browser": "^3.2.4",
"assert": "^2.1.0",
"axios": "^1.15.0",
"bip39": "^3.0.3",
Expand All @@ -38,7 +38,7 @@
"i18next-browser-languagedetector": "^7.2.0",
"idb": "^6.1.5",
"js-file-download": "^0.4.12",
"lint-staged": "^13.1.0",
"lint-staged": "^16.0.0",
"lodash": "^4.18.1",
"openpgp": "^6.3.0",
"os-browserify": "^0.3.0",
Expand Down Expand Up @@ -140,7 +140,7 @@
"@types/react-window": "^1.8.8",
"@types/wicg-file-system-access": "^2023.10.7",
"@vitejs/plugin-react": "=4.5.1",
"@vitest/coverage-istanbul": "2.1.9",
"@vitest/coverage-istanbul": "3.2.4",
"autoprefixer": "^10.4.16",
"browserslist": "^4.25.0",
"buffer": "^6.0.3",
Expand All @@ -153,18 +153,18 @@
"prettier": "^3.3.3",
"process": "^0.11.10",
"prop-types": "^15.7.2",
"sass": "^1.32.4",
"sass": "^1.72.0",
"stylelint": "^16.10.0",
"stylelint-config-standard": "^36.0.1",
"stylelint-scss": "^6.8.1",
"tailwindcss": "^3.3.4",
"tailwindcss": "^3.4.19",
"typescript": "^4.4.2",
"vite": "=6.4.2",
"vite-plugin-bundle-obfuscator": "^1.7.0",
"vite-plugin-node-polyfills": "=0.23.0",
"vite-plugin-node-polyfills": "=0.26.0",
"vite-plugin-static-copy": "^3.1.2",
"vite-plugin-svgr": "^4.3.0",
"vitest": "2.1.9",
"vitest": "3.2.4",
"webpack-bundle-analyzer": "^4.9.1"
},
"engines": {
Expand All @@ -178,8 +178,9 @@
"resolutions": {
"glob": "^10.5.0",
"js-yaml": "^4.1.1",
"vitest/vite": "5.4.21",
"readdir-glob/minimatch": "5.1.8",
"strip-ansi": "6.0.1"
"strip-ansi": "6.0.1",
"readdir-glob/brace-expansion": "2.0.3",
"babel-plugin-macros/yaml": "1.10.3"
}
}
1 change: 1 addition & 0 deletions src/app/core/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ export const HTTP_CODES = {
MAX_SPACE_USED: 420,
FORBIDDEN: 403,
NOT_FOUND: 404,
INTERNAL_SERVER_ERROR: 500,
};
export enum ErrorMessages {
ServerUnavailable = 'Server Unavailable',
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
import { describe, expect, vi, beforeEach, test } from 'vitest';
import { DriveFileData } from '../../types';
import createFileDownloadStream from './createFileDownloadStream';
import fetchFileStream from './fetchFileStream';
import fetchFileStreamUsingCredentials from './fetchFileStreamUsingCredentials';

vi.mock('./fetchFileStream', () => ({ default: vi.fn() }));
vi.mock('./fetchFileStreamUsingCredentials', () => ({ default: vi.fn() }));

const mockReadableStream = new ReadableStream();

Expand All @@ -13,23 +19,13 @@ const baseFile: DriveFileData = {
describe('createFileDownloadStream', () => {
const mockProgress = vi.fn();

beforeEach(async () => {
beforeEach(() => {
vi.clearAllMocks();
vi.resetModules();
vi.mocked(fetchFileStream).mockResolvedValue(mockReadableStream);
vi.mocked(fetchFileStreamUsingCredentials).mockResolvedValue(mockReadableStream);
});

test('When sharing options (credentials) are not provided, then fetchFileStream is called', async () => {
vi.doMock('./fetchFileStream', () => ({
default: vi.fn().mockResolvedValue(mockReadableStream),
}));

vi.doMock('./fetchFileStreamUsingCredentials', () => ({
default: vi.fn().mockResolvedValue(mockReadableStream),
}));
const createFileDownloadStream = (await import('./createFileDownloadStream')).default;
const fetchFileStream = (await import('./fetchFileStream')).default;
const fetchFileStreamUsingCredentials = (await import('./fetchFileStreamUsingCredentials')).default;

test('When sharing options are not provided, then the file is downloaded using the default method', async () => {
const result = await createFileDownloadStream(baseFile, true, mockProgress);

expect(fetchFileStream).toHaveBeenCalledWith(
Expand All @@ -40,22 +36,23 @@ describe('createFileDownloadStream', () => {
expect(result).toBe(mockReadableStream);
});

test('When sharing options (credentials) are provided, then fetchFileStreamUsingCredentials is called', async () => {
test('When a cancellation signal is provided without sharing options, then it is forwarded to the download', async () => {
const abortController = new AbortController();

await createFileDownloadStream(baseFile, false, mockProgress, abortController);

expect(fetchFileStream).toHaveBeenCalledWith(
{ ...baseFile, bucketId: baseFile.bucket },
{ isWorkspace: false, updateProgressCallback: mockProgress, abortController },
);
});

test('When sharing options with credentials are provided, then the file is downloaded using those credentials', async () => {
const abortController = new AbortController();
const sharingOptions = {
credentials: { user: 'test-user', pass: 'test-pass' },
mnemonic: 'test-mnemonic',
};
vi.doMock('./fetchFileStream', () => ({
default: vi.fn().mockResolvedValue(mockReadableStream),
}));

vi.doMock('./fetchFileStreamUsingCredentials', () => ({
default: vi.fn().mockResolvedValue(mockReadableStream),
}));
const createFileDownloadStream = (await import('./createFileDownloadStream')).default;
const fetchFileStream = (await import('./fetchFileStream')).default;
const fetchFileStreamUsingCredentials = (await import('./fetchFileStreamUsingCredentials')).default;

const result = await createFileDownloadStream(baseFile, false, mockProgress, abortController, sharingOptions);

Expand Down
3 changes: 2 additions & 1 deletion src/app/drive/services/network.service/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { Abortable } from 'app/network/Abortable';
import { createUploadWebWorker } from '../../../../WebWorker';
import localStorageService from 'services/local-storage.service';
import { createWorkerMessageHandlerPromise } from '../worker.service/uploadWorkerUtils';
import { notifyUserWithCooldown } from 'app/core/factory/sdk/retryStrategies';
import { EnvironmentConfig, IUploadParams } from './types';

export const MAX_ALLOWED_UPLOAD_SIZE = 40 * 1024 * 1024 * 1024;
Expand Down Expand Up @@ -77,7 +78,7 @@ export class Network {

worker.postMessage({ bucketId, params: payload, type: 'upload' });

return createWorkerMessageHandlerPromise(worker, params, continueUploadOptions);
return createWorkerMessageHandlerPromise(worker, params, continueUploadOptions, notifyUserWithCooldown);
}
}

Expand Down
23 changes: 12 additions & 11 deletions src/app/drive/services/thumbnail.service.test.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest';
import { Thumbnail } from '@internxt/sdk/dist/drive/storage/types';
import Resizer from 'react-image-file-resizer';
import { ErrorLoadingVideoFileError } from './errors/thumbnail.service.errors';
import { getVideoFrame, getImageThumbnail, downloadThumbnail } from './thumbnail.service';
import localStorageService from 'services/local-storage.service';
import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest';
import fetchFileBlob from './download.service/fetchFileBlob';
import { Thumbnail } from '@internxt/sdk/dist/drive/storage/types';

vi.mock('react-image-file-resizer');
vi.mock('services/local-storage.service');
import { ErrorLoadingVideoFileError } from './errors/thumbnail.service.errors';
import { downloadThumbnail, getImageThumbnail, getVideoFrame } from './thumbnail.service';

vi.mock('react-image-file-resizer', () => ({
default: { imageFileResizer: vi.fn() },
}));
vi.mock('services/local-storage.service', () => ({
default: { getUser: vi.fn() },
}));
vi.mock('./download.service/fetchFileBlob');

const flushPromises = () => new Promise((resolve) => setTimeout(resolve, 0));
Expand Down Expand Up @@ -196,16 +200,13 @@ describe('Thumbnail Service', () => {

beforeEach(() => {
vi.clearAllMocks();
(Resizer as any).imageFileResizer = vi.fn();
URL.createObjectURL = vi.fn(() => 'blob:mock-url');

mockImage = { onload: null, onerror: null };
globalThis.Image = vi.fn(() => mockImage) as any;
});

afterEach(() => {
vi.restoreAllMocks();
});

test('When image is valid, then it should return a resized thumbnail', async () => {
const imageFile = new File(['image-content'], 'test-image.jpg', { type: 'image/jpeg' });
const mockThumbnailFile = new File(['thumbnail'], 'thumbnail.png', { type: 'image/png' });
Expand Down
15 changes: 8 additions & 7 deletions src/app/drive/services/worker.service/uploadWorkerUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import DatabaseUploadRepository from 'app/repositories/DatabaseUploadRepository'
import { TaskStatus } from 'app/tasks/types';
import { IUploadParams } from '../network.service/types';
import { WORKER_MESSAGE_STATES } from './types/upload';
import { notifyUserWithCooldown } from 'app/core/factory/sdk/retryStrategies';

/**
* Checks the upload progress for the specified task.
Expand Down Expand Up @@ -84,13 +83,13 @@ const handleCheckUploadStatus = async ({ continueUploadOptions, worker }) => {
});
};

const messageResultHandlers = {
const buildMessageResultHandlers = (onRateLimited: () => void) => ({
[WORKER_MESSAGE_STATES.SUCCESS]: handleSuccess,
[WORKER_MESSAGE_STATES.ERROR]: handleError,
[WORKER_MESSAGE_STATES.ABORT]: handleAbort,
[WORKER_MESSAGE_STATES.CHECK_UPLOAD_STATUS]: handleCheckUploadStatus,
[WORKER_MESSAGE_STATES.RATE_LIMITED]: notifyUserWithCooldown,
};
[WORKER_MESSAGE_STATES.RATE_LIMITED]: () => onRateLimited(),
});

/**
* Handles messages received from the Web Worker.
Expand All @@ -102,13 +101,13 @@ const messageResultHandlers = {
* @param {Worker} worker - The Web Worker instance.
* @param {Object} continueUploadOptions - Options for continuing an upload.
*/
const handleMessage = (msgData, params, resolve, reject, worker, continueUploadOptions) => {
const handleMessage = (msgData, params, resolve, reject, worker, continueUploadOptions, onRateLimited: () => void) => {
if (msgData.progress) {
handleProgress({ msgData, params });
return;
}

const messageHandler = messageResultHandlers[msgData.result];
const messageHandler = buildMessageResultHandlers(onRateLimited)[msgData.result];

if (messageHandler) {
messageHandler({ msgData, params, resolve, reject, worker, continueUploadOptions });
Expand All @@ -125,6 +124,7 @@ const handleMessage = (msgData, params, resolve, reject, worker, continueUploadO
* @param {IUploadParams} params - Upload parameters.
* @param {Object} continueUploadOptions - Options for continuing an upload.
* @param {string} continueUploadOptions.taskId - The task ID for continuing the upload.
* @param {Function} onRateLimited - Callback invoked when the worker signals a rate limit.
*
* @returns {[Promise<string>, Abortable | undefined]} A tuple containing a Promise that resolves to a file ID
* and an Abortable object for aborting the upload (if applicable).
Expand All @@ -135,13 +135,14 @@ const createWorkerMessageHandlerPromise = (
continueUploadOptions: {
taskId: string;
},
onRateLimited: () => void,
): [Promise<string>, Abortable | undefined] => {
return [
new Promise((resolve, reject) => {
worker.addEventListener('error', reject);
worker.addEventListener('message', (msg) => {
console.log('[MAIN_THREAD]: Message received from worker', msg);
handleMessage(msg.data, params, resolve, reject, worker, continueUploadOptions);
handleMessage(msg.data, params, resolve, reject, worker, continueUploadOptions, onRateLimited);
});
}),
{
Expand Down
4 changes: 3 additions & 1 deletion src/app/network/NetworkFacade.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@ import {
NoContentReceivedError,
} from './errors/download.errors';

vi.mock('@internxt/sdk/dist/network/download');
vi.mock('@internxt/sdk/dist/network/download', () => ({
downloadFile: vi.fn(),
}));
vi.mock('services/stream.service', () => ({
binaryStreamToBlob: vi.fn(),
buildProgressStream: vi.fn(),
Expand Down
8 changes: 7 additions & 1 deletion src/app/network/upload-utils.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,13 @@ import { describe, it, expect, vi, beforeEach } from 'vitest';
import axios, { AxiosError } from 'axios';
import { uploadFileUint8Array } from './upload-utils';

vi.mock('axios');
vi.mock('axios', async () => {
const { AxiosError } = await vi.importActual<typeof import('axios')>('axios');
return {
default: { isCancel: vi.fn(), create: vi.fn() },
AxiosError,
};
});

describe('uploadFileUint8Array error handling', () => {
let mockAxiosInstance: any;
Expand Down
62 changes: 54 additions & 8 deletions src/app/share/services/redirections.service.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,8 +116,13 @@ describe('handlePrivateSharedFolderAccess', () => {

describe('Error scenarios', () => {
it('should handle 403 error (access denied) and navigate to request access', async () => {
const error403 = new AxiosError('Forbidden');
error403.status = 403;
const error403 = new AxiosError('Forbidden', undefined, undefined, undefined, {
status: 403,
data: {},
statusText: 'Forbidden',
headers: {},
config: {} as never,
});
(shareService.getSharedFolderContent as Mock).mockRejectedValue(error403);

await handlePrivateSharedFolderAccess({
Expand All @@ -136,8 +141,13 @@ describe('handlePrivateSharedFolderAccess', () => {
});

it('should handle 404 error (folder not found) and show appropriate error', async () => {
const error404 = new AxiosError('Not Found');
error404.status = 404;
const error404 = new AxiosError('Not Found', undefined, undefined, undefined, {
status: 404,
data: {},
statusText: 'Not Found',
headers: {},
config: {} as never,
});
(shareService.getSharedFolderContent as Mock).mockRejectedValue(error404);

await handlePrivateSharedFolderAccess({
Expand All @@ -154,8 +164,13 @@ describe('handlePrivateSharedFolderAccess', () => {
});

it('should handle generic error and show default error message', async () => {
const genericError = new AxiosError('Server Error');
genericError.status = 500;
const genericError = new AxiosError('Server Error', undefined, undefined, undefined, {
status: 500,
data: {},
statusText: 'Internal Server Error',
headers: {},
config: {} as never,
});
(shareService.getSharedFolderContent as Mock).mockRejectedValue(genericError);

await handlePrivateSharedFolderAccess({
Expand Down Expand Up @@ -188,9 +203,40 @@ describe('handlePrivateSharedFolderAccess', () => {
expect(navigateToFolder).not.toHaveBeenCalled();
});

it('should handle workspace 403 error and navigate to request access', async () => {
const error403 = new AxiosError('Forbidden', undefined, undefined, undefined, {
status: 403,
data: {},
statusText: 'Forbidden',
headers: {},
config: {} as never,
});
const mockPromise = Promise.reject(error403);
(workspacesService.getAllWorkspaceTeamSharedFolderFiles as Mock).mockReturnValue([mockPromise]);

await handlePrivateSharedFolderAccess({
folderUUID: mockFolderUUID,
history,
navigateToFolder,
onError,
workspaceItemData: { workspaceId: mockWorkspaceId },
});

expect(navigationService.push).toHaveBeenCalledWith(AppView.RequestAccess, {
folderuuid: mockFolderUUID,
});
expect(navigateToFolder).not.toHaveBeenCalled();
expect(onError).not.toHaveBeenCalled();
});

it('should handle workspace generic error', async () => {
const workspaceError = new AxiosError('Workspace Error');
workspaceError.status = 500;
const workspaceError = new AxiosError('Workspace Error', undefined, undefined, undefined, {
status: 500,
data: {},
statusText: 'Internal Server Error',
headers: {},
config: {} as never,
});
const mockPromise = Promise.reject(workspaceError);
(workspacesService.getAllWorkspaceTeamSharedFolderFiles as Mock).mockReturnValue([mockPromise]);

Expand Down
Loading
Loading