Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
bfcb788
Go fuse daemon: Skeleton, mount and unix socket connection (#303)
AlexisMora Apr 15, 2026
ab1358a
feat: Get Attributes operation + Electron 21 (#315)
AlexisMora Apr 20, 2026
df53dfb
feat(localization): remove unused translations from English, Spanish,…
egalvis27 Apr 23, 2026
0c0a221
feat: remove non-Linux system references and clean up platform-specif…
egalvis27 Apr 23, 2026
09b04f2
Chore: add golang lint + testing pipline + testing http server (#317)
AlexisMora Apr 23, 2026
8b497f1
reafctor: device service.ts (#316)
egalvis27 Apr 27, 2026
91c6e04
fix: [PB-6255] implement PendingFolderCreationTracker to manage folde…
egalvis27 Apr 27, 2026
180deb9
feat: Refactor device management functions to use Result type for err…
egalvis27 Apr 14, 2026
fe236f3
refactor: update backup handling to use structured error responses an…
egalvis27 Apr 27, 2026
e8db0f6
refactor: improve formatting of mockResolvedValue calls in backup tests
egalvis27 Apr 27, 2026
9d79310
feat: Implement Open and OpenDir FUSE opeartions (#319)
AlexisMora Apr 28, 2026
3a38f48
read operation + blocklist processes (#321)
AlexisMora Apr 28, 2026
fa1a7c2
feat: release operation + read tests (#323)
AlexisMora Apr 29, 2026
8ddf068
feat: add unlink and rmdir operations with corresponding controllers,…
egalvis27 Apr 29, 2026
b21d44b
feat: implement create and write operations with corresponding contro…
egalvis27 Apr 30, 2026
5a3f46a
fix: update mocked return value for migrateBackupEntryIfNeeded in cha…
egalvis27 May 5, 2026
75d91db
refactor: remove duplicate imports and clean up test files; ensure ne…
egalvis27 Jun 5, 2026
c298b94
refactor: remove unused minimatch dependency from package-lock.json; …
egalvis27 Jun 5, 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
15 changes: 0 additions & 15 deletions package-lock.json

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

Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@ export class BackupConfiguration {
const { error, data } = await DeviceModule.getOrCreateDevice();
if (error) return [];

const enabledBackupEntries = await DeviceModule.getBackupsFromDevice(data, true);
const { error: backupsError, data: enabledBackupEntries } = await DeviceModule.getBackupsFromDevice(data, true);
if (backupsError || !enabledBackupEntries) return [];

return this.map(enabledBackupEntries, data.bucket);
}
Expand Down
4 changes: 2 additions & 2 deletions src/apps/main/interface.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ export interface IElectronAPI {

getOrCreateDevice: () => Promise<Result<Device, Error>>;

getBackupsFromDevice: (device: Device, isCurrent?: boolean) => Promise<Array<BackupInfo>>;
getBackupsFromDevice: (device: Device, isCurrent?: boolean) => Promise<Result<Array<BackupInfo>, Error>>;

addBackup: () => Promise<Result<BackupInfo, Error>>;

Expand All @@ -62,7 +62,7 @@ export interface IElectronAPI {

abortDownloadBackups: (deviceId: string) => void;

renameDevice: (deviceName: string) => Promise<Device>;
renameDevice: (deviceName: string) => Promise<Result<Device, Error>>;
devices: {
getDevices: () => Promise<Array<Device>>;
};
Expand Down
15 changes: 12 additions & 3 deletions src/apps/main/preload.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,17 +100,26 @@ declare interface Window {

path: typeof import('path');

getOrCreateDevice: typeof import('../../backend/features/device/device.module').DeviceModule.getOrCreateDevice;
getOrCreateDevice: () => Promise<
import('../../context/shared/domain/Result').Result<import('../main/device/service').Device, Error>
>;

renameDevice: typeof import('../../backend/features/device/device.module').DeviceModule.renameDevice;
renameDevice: (
deviceName: string,
) => Promise<import('../../context/shared/domain/Result').Result<import('../main/device/service').Device, Error>>;

devices: {
getDevices: () => Promise<Array<Device>>;
};

onDeviceCreated(func: (value: Device) => void): () => void;

getBackupsFromDevice: typeof import('../../backend/features/device/device.module').DeviceModule.getBackupsFromDevice;
getBackupsFromDevice: (
device: import('../main/device/service').Device,
isCurrent?: boolean,
) => Promise<
import('../../context/shared/domain/Result').Result<import('../backups/BackupInfo').BackupInfo[], Error>
>;

addBackup: typeof import('../../backend/features/backup/add-backup').addBackup;

Expand Down
17 changes: 7 additions & 10 deletions src/apps/renderer/context/DeviceContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -58,18 +58,15 @@
const deviceRename = async (deviceName: string) => {
setDeviceState({ status: 'LOADING' });

try {
const updatedDevice = await window.electron.renameDevice(deviceName);
setDeviceState({ status: 'SUCCESS', device: updatedDevice });
setCurrent(updatedDevice);
setSelected(updatedDevice);
} catch (err) {
window.electron.logger.error({
msg: '[RENDERER] Failed to rename device',
error: err,
});
const { error, data: updatedDevice } = await window.electron.renameDevice(deviceName);

Check warning on line 61 in src/apps/renderer/context/DeviceContext.tsx

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Prefer `globalThis` over `window`.

See more on https://sonarcloud.io/project/issues?id=internxt_drive-desktop-linux&issues=AZ6ZkMHLSN6RPekSh8wA&open=AZ6ZkMHLSN6RPekSh8wA&pullRequest=311
if (error || !updatedDevice) {
setDeviceState({ status: 'ERROR' });
return;
}

setDeviceState({ status: 'SUCCESS', device: updatedDevice });
setCurrent(updatedDevice);
setSelected(updatedDevice);
};

return (
Expand Down
24 changes: 16 additions & 8 deletions src/apps/renderer/hooks/backups/useBackups.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,17 @@
const [backups, setBackups] = useState<Array<BackupInfo>>([]);
const [hasExistingBackups, setHasExistingBackups] = useState(false);

async function fetchBackups(): Promise<void> {
if (!selected) return;
const backups = await window.electron.getBackupsFromDevice(selected, selected === current);
setBackups(backups);
async function fetchBackups(): Promise<boolean> {
if (!selected) return true;

const { error, data } = await window.electron.getBackupsFromDevice(selected, selected === current);

Check warning on line 29 in src/apps/renderer/hooks/backups/useBackups.tsx

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Prefer `globalThis` over `window`.

See more on https://sonarcloud.io/project/issues?id=internxt_drive-desktop-linux&issues=AZ6ZkMKTSN6RPekSh8wB&open=AZ6ZkMKTSN6RPekSh8wB&pullRequest=311
if (error || !data) {
setBackups([]);
return false;
}

setBackups(data);
return true;
}

const validateIfBackupExists = async () => {
Expand All @@ -38,13 +45,14 @@
setBackupsState('LOADING');
setBackups([]);

try {
await fetchBackups();
setBackupsState('SUCCESS');
} catch {
const isLoaded = await fetchBackups();
if (!isLoaded) {
setBackupsState('ERROR');
setBackups([]);
return;
}

setBackupsState('SUCCESS');
}

useEffect(() => {
Expand Down
2 changes: 1 addition & 1 deletion src/backend/features/backup/change-backup-path.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ describe('change-backup-path', () => {
mockedConfigStoreGet.mockReturnValue(backupList);
mockedGetBackupFolderUuid.mockResolvedValue({ data: 'remote-folder-uuid' });
mockedRenameFolder.mockResolvedValue({ data: {} });
mockedMigrateBackupEntryIfNeeded.mockResolvedValue(migratedBackup);
mockedMigrateBackupEntryIfNeeded.mockResolvedValue({ data: migratedBackup });

const result = await changeBackupPath({ currentPath, newPath });

Expand Down
6 changes: 4 additions & 2 deletions src/backend/features/backup/change-backup-path.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,11 +42,13 @@ export async function changeBackupPath({ currentPath, newPath }: Props): Promise

delete backupsList[currentPath];

const migratedExistingBackup = await migrateBackupEntryIfNeeded({
const { error, data } = await migrateBackupEntryIfNeeded({
pathname: newPath,
backup: existingBackup,
});
backupsList[newPath] = migratedExistingBackup;
if (error) return { error };

backupsList[newPath] = data;

configStore.set('backupList', backupsList);

Expand Down
8 changes: 4 additions & 4 deletions src/backend/features/backup/delete-device-backups.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,8 @@ describe('delete-device-backups', () => {
},
];

getBackupsFromDeviceMock.mockResolvedValue(backups);
deleteBackupMock.mockResolvedValue(undefined);
getBackupsFromDeviceMock.mockResolvedValue({ data: backups });
deleteBackupMock.mockResolvedValue({ data: undefined });
getBackupFolderTreeSnapshotMock.mockResolvedValue({
data: {
tree: {
Expand Down Expand Up @@ -68,8 +68,8 @@ describe('delete-device-backups', () => {
},
];

getBackupsFromDeviceMock.mockResolvedValue(backups);
deleteBackupMock.mockResolvedValue(undefined);
getBackupsFromDeviceMock.mockResolvedValue({ data: backups });
deleteBackupMock.mockResolvedValue({ data: undefined });
getBackupFolderTreeSnapshotMock.mockResolvedValue({
data: { tree: { children: [{ id: 10, uuid: 'folder-uuid-1' }] } },
} as never);
Expand Down
7 changes: 6 additions & 1 deletion src/backend/features/backup/delete-device-backups.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,12 @@ type Props = {
};

export async function deleteDeviceBackups({ device, isCurrent }: Props) {
const backups = await DeviceModule.getBackupsFromDevice(device, isCurrent);
const { error: getBackupsError, data: backups } = await DeviceModule.getBackupsFromDevice(device, isCurrent);
if (getBackupsError) {
logger.error({ tag: 'BACKUPS', msg: 'Error fetching backups from device', error: getBackupsError });
return;
}

logger.debug({ tag: 'BACKUPS', msg: '[BACKUPS] Deleting backups from device', count: backups.length });
logger.debug({ tag: 'BACKUPS', msg: '[BACKUPS] Backups details', backups });

Expand Down
3 changes: 3 additions & 0 deletions src/backend/features/backup/download-backup.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ describe('download-backup', () => {
it('should download backup and broadcast progress when not aborted', async () => {
downloadDeviceBackupZipMock.mockImplementation(async ({ updateProgress }) => {
updateProgress(33);
return { data: true };
});

await downloadBackup({ device, pathname });
Expand Down Expand Up @@ -86,6 +87,7 @@ describe('download-backup', () => {
const abortListener = ipcMainOnMock.mock.calls[0]?.[1];
abortListener?.({} as never, device.uuid);
updateProgress(90);
return { data: true };
});

await downloadBackup({ device, pathname });
Expand All @@ -98,6 +100,7 @@ describe('download-backup', () => {
const abortListener = ipcMainOnMock.mock.calls[0]?.[1];
abortListener?.({} as never, 'other-device-uuid');
updateProgress(12);
return { data: true };
});

await downloadBackup({ device, pathname });
Expand Down
103 changes: 103 additions & 0 deletions src/backend/features/device/createAndSetupNewDevice.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import { BrowserWindow } from 'electron';
import { broadcastToWindows } from '../../../apps/main/windows';
import { DependencyInjectionUserProvider } from '../../../apps/shared/dependency-injection/DependencyInjectionUserProvider';
import { createNewDevice } from './createNewDevice';
import { createAndSetupNewDevice } from './createAndSetupNewDevice';
import { getDeviceIdentifier } from './getDeviceIdentifier';

vi.mock('electron', async (importOriginal) => {
const actual = await importOriginal<typeof import('electron')>();

return {
...actual,
app: {
...actual.app,
getPath: vi.fn().mockReturnValue('/tmp/backups'),
},
ipcMain: {
...actual.ipcMain,
on: vi.fn(),
handle: vi.fn(),
removeHandler: vi.fn(),
},
BrowserWindow: {
...actual.BrowserWindow,
getAllWindows: vi.fn(),
},
};
});
vi.mock('./getDeviceIdentifier');
vi.mock('./createNewDevice');
vi.mock('../../../apps/main/windows', () => ({
broadcastToWindows: vi.fn(),
}));
vi.mock('../../../apps/shared/dependency-injection/DependencyInjectionUserProvider', () => ({
DependencyInjectionUserProvider: { get: vi.fn(), updateUser: vi.fn() },
}));

describe('createAndSetupNewDevice', () => {
const mockedGetDeviceIdentifier = vi.mocked(getDeviceIdentifier);
const mockedCreateNewDevice = vi.mocked(createNewDevice);
const mockedBroadcastToWindows = vi.mocked(broadcastToWindows);
const mockedBrowserWindowGetAllWindows = vi.mocked(BrowserWindow.getAllWindows);
const mockedUserProviderGet = vi.mocked(DependencyInjectionUserProvider.get);
const mockedUserProviderUpdate = vi.mocked(DependencyInjectionUserProvider.updateUser);

beforeEach(() => {
vi.clearAllMocks();
mockedUserProviderGet.mockReturnValue({ backupsBucket: '' } as never);
mockedBrowserWindowGetAllWindows.mockReturnValue([] as never);
});

it('should return only error when the device identifier is unavailable', async () => {
const error = new Error('Missing device identifier');
mockedGetDeviceIdentifier.mockReturnValue({ error });

const result = await createAndSetupNewDevice();

expect(result).toStrictEqual({ error });
expect(mockedCreateNewDevice).not.toHaveBeenCalled();
expect(mockedBroadcastToWindows).not.toHaveBeenCalled();
});

it('should return only error when the device creation fails', async () => {
const error = new Error('Create device failed');
mockedGetDeviceIdentifier.mockReturnValue({
data: { key: 'key', platform: 'linux', hostname: 'host' },
});
mockedCreateNewDevice.mockResolvedValue({ error });

const result = await createAndSetupNewDevice();

expect(result).toStrictEqual({ error });
expect(mockedBroadcastToWindows).not.toHaveBeenCalled();
expect(mockedUserProviderUpdate).not.toHaveBeenCalled();
});

it('should update the user and notify windows when the device is created', async () => {
const user = { backupsBucket: '' };
const send = vi.fn();
const device = {
id: 1,
uuid: 'device-uuid',
name: 'Laptop',
bucket: 'bucket-1',
removed: false,
hasBackups: true,
};
mockedUserProviderGet.mockReturnValue(user as never);
mockedBrowserWindowGetAllWindows.mockReturnValue([{ webContents: { send } }] as never);
mockedGetDeviceIdentifier.mockReturnValue({
data: { key: 'key', platform: 'linux', hostname: 'host' },
});
mockedCreateNewDevice.mockResolvedValue({ data: device });

const result = await createAndSetupNewDevice();

expect(result).toStrictEqual({ data: device });
expect(user.backupsBucket).toBe('bucket-1');
expect(mockedUserProviderUpdate).toHaveBeenCalledWith(user);
expect(send).toHaveBeenCalledWith('reinitialize-backups');
expect(mockedBroadcastToWindows).toHaveBeenCalledWith('device-created', device);
});
});
9 changes: 4 additions & 5 deletions src/backend/features/device/createAndSetupNewDevice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,16 @@ export async function createAndSetupNewDevice() {
const { error, data: deviceIdentifier } = getDeviceIdentifier();
if (error) return { error };

const createNewDeviceEither = await createNewDevice(deviceIdentifier);
if (createNewDeviceEither.isLeft()) {
const { error: createDeviceError, data: device } = await createNewDevice(deviceIdentifier);
if (createDeviceError) {
logger.error({
tag: 'BACKUPS',
msg: '[DEVICE] Error creating new device',
error: createNewDeviceEither.getLeft(),
error: createDeviceError,
});
return { error: createNewDeviceEither.getLeft() };
return { error: createDeviceError };
}

const device = createNewDeviceEither.getRight();
const user = DependencyInjectionUserProvider.get();
user.backupsBucket = device.bucket;
DependencyInjectionUserProvider.updateUser(user);
Expand Down
Loading
Loading