Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
15 changes: 15 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,21 @@ jobs:
run: yarn playwright install
- name: Unit test run
run: yarn test
typecheck:
runs-on: ubuntu-22.04
strategy:
matrix:
node-version: [20.x]
steps:
- uses: actions/checkout@v6
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v6
with:
node-version: ${{ matrix.node-version }}
- name: Install
run: yarn
- name: Typecheck
run: yarn typecheck
build:
runs-on: ubuntu-latest
strategy:
Expand Down
2 changes: 1 addition & 1 deletion .husky/pre-commit
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"

yarn run lint:fix; npx lint-staged; yarn run test
yarn run lint:fix; npx lint-staged; yarn run typecheck; yarn run test

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@
"lint:scss:fix": "yarn run lint:scss --fix",
"lint": "yarn run lint:ts && yarn run lint:scss",
"lint:fix": "yarn run lint:ts:fix && yarn run lint:scss:fix",
"typecheck": "tsc --noEmit",
"analyze": "cross-env ANALYZE=true yarn build",
"add:npmrc": "echo \"registry=https://registry.yarnpkg.com/\" >> .npmrc && echo \"@internxt:registry=https://npm.pkg.github.com\" >> .npmrc && echo \"//npm.pkg.github.com/:_authToken=$NPM_TOKEN\" >> .npmrc && echo \"always-auth=true\" >> .npmrc"
},
Expand Down Expand Up @@ -137,7 +138,7 @@
"@types/react-redux": "^7.1.17",
"@types/react-router-dom": "^5.3.3",
"@types/react-window": "^1.8.8",
"@types/wicg-file-system-access": "^2020.9.4",
"@types/wicg-file-system-access": "^2023.10.7",
"@vitejs/plugin-react": "=4.5.1",
"@vitest/coverage-istanbul": "2.1.9",
"autoprefixer": "^10.4.16",
Expand Down
19 changes: 9 additions & 10 deletions src/app/analytics/impact.service.test.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { savePaymentDataInLocalStorage, trackPaymentConversion, trackSignUp } from './impact.service';
import { PriceWithTax } from '@internxt/sdk/dist/payments/types';
import { getProductAmount } from 'views/Checkout/utils';
import axios from 'axios';
import localStorageService from 'services/local-storage.service';
import { UserSettings } from '@internxt/sdk/dist/shared/types/userSettings';
import { bytesToString } from 'app/drive/services/size.service';
import errorService from 'services/error.service';
import dayjs from 'dayjs';
import axios from 'axios';
import envService from 'services/env.service';
import errorService from 'services/error.service';
import localStorageService from 'services/local-storage.service';
import { getProductAmount } from 'views/Checkout/utils';
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { savePaymentDataInLocalStorage, trackPaymentConversion, trackSignUp } from './impact.service';

vi.mock('services/local-storage.service', () => ({
default: {
Expand Down Expand Up @@ -201,7 +200,7 @@ describe('Testing Impact Service', () => {

await trackSignUp(mockedUserUuid);

const callArgs = axiosSpy.mock.calls[0][1];
const callArgs = axiosSpy.mock.calls[0][1] as { messageId: string };
expect(callArgs).toHaveProperty('messageId');
expect(callArgs.messageId).toBe(mockedUserUuid);
});
Expand Down Expand Up @@ -259,7 +258,7 @@ describe('Testing Impact Service', () => {

await trackPaymentConversion();

const callArgs = axiosSpy.mock.calls[0][1];
const callArgs = axiosSpy.mock.calls[0][1] as { messageId: string; properties: Record<string, unknown> };
expect(callArgs.properties.impact_value).toBe(0.01);
});

Expand All @@ -268,7 +267,7 @@ describe('Testing Impact Service', () => {

await trackPaymentConversion();

const callArgs = axiosSpy.mock.calls[0][1];
const callArgs = axiosSpy.mock.calls[0][1] as { messageId: string; properties: Record<string, unknown> };
expect(callArgs.properties).toHaveProperty('order_promo_code', promoCode.codeName);
});

Expand Down
6 changes: 4 additions & 2 deletions src/app/crypto/services/pgp.service.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import { Buffer } from 'buffer';
import { Data, MaybeStream, WebStream, PrivateKey, PublicKey, Message } from 'openpgp';
import { MaybeStream, Message, PrivateKey, PublicKey, WebStream } from 'openpgp';

import kemBuilder from '@dashlane/pqc-kem-kyber512-browser';
import { extendSecret } from './utils';

const WORDS_HYBRID_MODE_IN_BASE64 = 'SHlicmlkTW9kZQ=='; // 'HybridMode' in BASE64 format
type Data = Uint8Array | string;

export async function getOpenpgp(): Promise<typeof import('openpgp')> {
return import('openpgp');
Expand Down Expand Up @@ -32,7 +34,7 @@ export async function generateNewKeys(): Promise<{

const { privateKey, publicKey, revocationCertificate } = await openpgp.generateKey({
userIDs: [{ email: 'inxt@inxt.com' }],
curve: 'ed25519',
curve: 'ed25519Legacy',
});

const kem = await kemBuilder();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { useEffect, useState } from 'react';
import heic2any from 'heic2any';
import { useEffect, useState } from 'react';

import { FormatFileViewerProps } from '../../FileViewer';

Expand All @@ -25,19 +25,23 @@ const FileImageViewer = ({
const updatedFile = { ...file };
handlersForSpecialItems?.handleUpdateProgress(PROGRESS_BAR_STATUS.PENDING);

const convertedBlob = await heic2any({ blob: blob });
const convertedBlob = await heic2any({ blob: blob as Blob });
updatedFile.type = 'png';

setImageBlob(convertedBlob as Blob);
handlersForSpecialItems?.handleUpdateProgress(PROGRESS_BAR_STATUS.COMPLETED);

await handlersForSpecialItems?.handleUpdateThumbnail(updatedFile, convertedBlob as Blob);
} else {
if (!blob) {
setIsPreviewAvailable(false);
return;
}
setImageBlob(blob);
}
} catch (error) {
console.error('Error converting HEIC to another format:', error);
setIsPreviewAvailable(false);
} finally {
handlersForSpecialItems?.handleUpdateProgress(PROGRESS_BAR_STATUS.COMPLETED);
}
};
Expand All @@ -59,7 +63,15 @@ const FileImageViewer = ({
return (
<div className="flex max-h-screen max-w-full flex-col items-center justify-center text-white">
<div className="relative max-h-screen max-w-full">
<img src={fileUrl} className="relative max-h-screen object-contain" draggable={false} />
{fileUrl && (
<img
src={fileUrl}
alt={file.name}
className="relative max-h-screen object-contain"
draggable={false}
onError={() => setIsPreviewAvailable(false)}
/>
)}
</div>
</div>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { Document, Page, pdfjs } from 'react-pdf';
import { useState, useEffect } from 'react';
import { FormatFileViewerProps } from '../../FileViewer';
import { MagnifyingGlassMinus, MagnifyingGlassPlus } from '@phosphor-icons/react';
import { useTranslationContext } from 'app/i18n/provider/TranslationProvider';
import { useEffect, useState } from 'react';
import { Document, Page, pdfjs } from 'react-pdf';
import { FormatFileViewerProps } from '../../FileViewer';

import workerUrl from 'pdfjs-dist/build/pdf.worker.min.mjs?raw';
const blob = new Blob([workerUrl], { type: 'application/javascript' });
Expand Down Expand Up @@ -59,9 +59,9 @@ const PageWithObserver: React.FC<PageWithObserverProps> = ({ pageNumber, zoom, o
};
const DEFAULT_ZOOM = 1;

const FilePdfViewer = (props: FormatFileViewerProps): JSX.Element => {
const FilePdfViewer = ({ blob, setIsPreviewAvailable }: FormatFileViewerProps): JSX.Element => {
const { translate } = useTranslationContext();
const [fileUrl] = useState(URL.createObjectURL(new Blob([props.blob], { type: 'application/pdf' })));
const [fileUrl] = useState(blob ? URL.createObjectURL(new Blob([blob], { type: 'application/pdf' })) : null);
const [numPages, setNumPages] = useState<number>(0);
const [currentPage, setCurrentPage] = useState(1);
const [zoom, setZoom] = useState(DEFAULT_ZOOM);
Expand Down Expand Up @@ -99,7 +99,12 @@ const FilePdfViewer = (props: FormatFileViewerProps): JSX.Element => {
<div className="flex max-h-full w-full items-center justify-center">
<div>
<div className="flex items-center justify-center">
<Document file={fileUrl} loading="" onLoadSuccess={onDocumentLoadSuccess}>
<Document
file={fileUrl}
loading=""
onLoadSuccess={onDocumentLoadSuccess}
onLoadError={() => setIsPreviewAvailable(false)}
>
<div className="flex flex-col items-center space-y-3">
{Array.from(new Array(renderPages), (el, index) => (
<PageWithObserver
Expand Down
8 changes: 4 additions & 4 deletions src/app/drive/services/download.service/downloadFile.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import streamSaver from '../../../../libs/streamSaver';
import { isFirefox } from 'react-device-detect';
import { ErrorMessages } from 'app/core/constants';
import { ConnectionLostError } from 'app/network/requests';
import { isFirefox } from 'react-device-detect';
import streamSaver from '../../../../libs/streamSaver';
import { DriveFileData } from '../../types';
import { BlobWritable, downloadFileAsBlob } from './downloadFileAsBlob';
import fetchFileStream from './fetchFileStream';
import fetchFileStreamUsingCredentials from './fetchFileStreamUsingCredentials';
import { ErrorMessages } from 'app/core/constants';
import { BlobWritable, downloadFileAsBlob } from './downloadFileAsBlob';

async function pipe(readable: ReadableStream, writable: BlobWritable): Promise<void> {
const reader = readable.getReader();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ export class DownloadWorkerHandler {
}: HandleWorkerMessagesPayload) {
const fileName = itemData.plainName ?? itemData.name;
const completeFilename = downloadName || (itemData.type ? `${fileName}.${itemData.type}` : fileName);
const downloadId = itemData.fileId;
const downloadId = itemData.fileId as string;
const fileSize = itemData.size;

return new Promise((resolve, reject) => {
Expand Down
2 changes: 1 addition & 1 deletion src/app/drive/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ export interface DriveFileData {
deleted: boolean;
deletedAt: null;
encrypt_version: string;
fileId: string;
fileId: string | null;
folderId: number;
folder_id: number;
folderUuid: string;
Expand Down
8 changes: 4 additions & 4 deletions src/app/network/NetworkFacade.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,23 +6,23 @@ import { validateMnemonic } from 'bip39';
import { createCipheriv, createDecipheriv, randomBytes } from 'crypto';

import { EncryptFileFunction, UploadFileMultipartFunction } from '@internxt/sdk/dist/network';
import { queue, QueueObject } from 'async';
import envService from 'services/env.service';
import { buildProgressStream, decryptStream } from 'services/stream.service';
import { queue, QueueObject } from 'async';

import { WORKER_MESSAGE_STATES } from 'app/drive/services/worker.service/types/upload';
import { waitForContinueUploadSignal } from 'app/drive/services/worker.service/uploadWorkerUtils';
import { TaskStatus } from '../tasks/types';
import { encryptStreamInParts, generateFileKey, getEncryptedFile, processEveryFileBlobReturnHash } from './crypto';
import { DownloadProgressCallback, getDecryptedStream } from './download';
import { uploadFileUint8Array, UploadProgressCallback } from './upload-utils';
import { UPLOAD_CHUNK_SIZE, ALLOWED_CHUNK_OVERHEAD } from './networkConstants';
import { WORKER_MESSAGE_STATES } from 'app/drive/services/worker.service/types/upload';
import {
DownloadAbortedByUserError,
DownloadFailedWithUnknownError,
NoContentReceivedError,
} from './errors/download.errors';
import { ALLOWED_CHUNK_OVERHEAD, UPLOAD_CHUNK_SIZE } from './networkConstants';
import { DownloadChunkPayload } from './types/index';
import { uploadFileUint8Array, UploadProgressCallback } from './upload-utils';

interface UploadOptions {
uploadingCallback: UploadProgressCallback;
Expand Down
41 changes: 6 additions & 35 deletions src/app/network/download.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { createDecipheriv, Decipher } from 'crypto';

import { buildProgressStream, joinReadableBinaryStreams } from 'services/stream.service';
import { Abortable } from './Abortable';
import { getFileInfoWithAuth, getFileInfoWithToken, getMirrors, Mirror } from './requests';

import { FileVersionOneError } from '@internxt/sdk/dist/network/download';
Expand All @@ -11,28 +10,7 @@ import { generateFileKey } from './crypto';
import downloadFileV2, { multipartDownload } from './download/v2';

export type DownloadProgressCallback = (totalBytes: number, downloadedBytes: number) => void;
export type Downloadable = { fileId: string; bucketId: string };

type BinaryStream = ReadableStream<Uint8Array>;

export async function binaryStreamToBlob(stream: BinaryStream): Promise<Blob> {
const reader = stream.getReader();
const slices: Uint8Array[] = [];

let finish = false;

while (!finish) {
const { done, value } = await reader.read();

if (!done) {
slices.push(value as Uint8Array);
}

finish = done;
}

return new Blob(slices);
}
export type Downloadable = { fileId: string | null; bucketId: string };

interface FileInfo {
bucket: string;
Expand Down Expand Up @@ -115,7 +93,7 @@ export interface NetworkCredentials {

export interface IDownloadParams {
bucketId: string;
fileId: string;
fileId: string | null;
creds?: NetworkCredentials;
mnemonic?: string;
encryptionKey?: Buffer;
Expand Down Expand Up @@ -169,7 +147,8 @@ export function downloadFile(params: IDownloadParams): Promise<ReadableStream<Ui
export async function multipartDownloadFile(
params: IDownloadParams & { fileSize?: number },
): Promise<ReadableStream<Uint8Array>> {
const downloadMultipartPromise = multipartDownload(params);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const downloadMultipartPromise = multipartDownload(params as any);

return downloadMultipartPromise.catch((err) => {
if (err instanceof FileVersionOneError) {
Expand All @@ -186,9 +165,9 @@ export async function _downloadFile(params: IDownloadParams): Promise<ReadableSt
let metadata: MetadataRequiredForDownload;

if (creds) {
metadata = await getRequiredFileMetadataWithAuth(bucketId, fileId, creds);
metadata = await getRequiredFileMetadataWithAuth(bucketId, fileId ?? '', creds);
} else if (token) {
metadata = await getRequiredFileMetadataWithToken(bucketId, fileId, token);
metadata = await getRequiredFileMetadataWithToken(bucketId, fileId ?? '', token);
} else {
throw new Error('Download error 1');
}
Expand Down Expand Up @@ -218,11 +197,3 @@ export async function _downloadFile(params: IDownloadParams): Promise<ReadableSt
params.options?.notifyProgress(metadata.fileMeta.size, readBytes);
});
}

export function downloadFileToFileSystem(
params: IDownloadParams & { destination: WritableStream },
): [Promise<void>, Abortable] {
const downloadStreamPromise = downloadFile(params);

return [downloadStreamPromise.then((readable) => readable.pipeTo(params.destination)), { abort: () => null }];
}
2 changes: 1 addition & 1 deletion src/app/store/slices/ui/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
import { DriveItemData, DriveItemDetails, FileInfoMenuItem, UpgradePlanDialogInfo } from 'app/drive/types';
import { PreviewFileItem } from '../../../share/types';
import { FileVersion } from 'views/Drive/components/VersionHistory/types';
import { FileVersion } from '@internxt/sdk/dist/drive/storage/types';

interface UISliceState {
isSidenavCollapsed: boolean;
Expand Down
2 changes: 1 addition & 1 deletion src/app/workers/downloadWorker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ export class DownloadWorker {
await this.downloadUsingChunks(streamReader, callbacks.onChunk);
}

callbacks.onSuccess(file.fileId);
callbacks.onSuccess(file.fileId as string);
} catch (err) {
console.log('[DOWNLOAD-WORKER] ERROR -->', err);
callbacks.onError(err);
Expand Down
2 changes: 1 addition & 1 deletion src/components/BreadcrumbsBackupsView.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { DriveFolderData } from '@internxt/sdk/dist/drive/storage/types';
import { DriveFolderData } from 'app/drive/types';
import { useAppDispatch, useAppSelector } from 'app/store/hooks';
import { backupsActions } from 'views/Backups/store/backupsSlice';
import { t } from 'i18next';
Expand Down
5 changes: 3 additions & 2 deletions src/libs/streamSaver/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/* eslint-disable quotes */
import { StreamSaverOptions, MitmTransporter, ServiceWorkerMessage, ServiceWorkerResponse } from './types';
import { MitmTransporter, ServiceWorkerMessage, ServiceWorkerResponse, StreamSaverOptions } from './types';

export const STREAM_SAVER_MITM = '/streamsaver/mitm.html?version=2.0.0';

Expand Down Expand Up @@ -40,7 +40,8 @@ export class StreamSaver {
const transport: MitmTransporter = {
frame: iframe,
loaded: false,
postMessage: (...args: any[]) => iframe.contentWindow?.postMessage(...args),
postMessage: (message: ServiceWorkerMessage, targetOrigin: string, transfer?: Transferable[]) =>
iframe.contentWindow?.postMessage(message, targetOrigin, transfer),
remove: () => iframe.remove(),
addEventListener: (type: string, listener: EventListener, options?: AddEventListenerOptions) =>
iframe.addEventListener(type, listener, options),
Expand Down
2 changes: 1 addition & 1 deletion src/libs/streamSaver/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ export interface StreamSaverOptions {
export interface MitmTransporter {
frame?: Window | HTMLIFrameElement | null;
loaded: boolean;
postMessage: (...args: any[]) => void;
postMessage: (message: ServiceWorkerMessage, targetOrigin: string, transfer: Transferable[]) => void;
remove: () => void;
addEventListener: (type: string, listener: EventListener, options?: AddEventListenerOptions) => void;
removeEventListener: (type: string, listener: EventListener) => void;
Expand Down
Loading
Loading