Skip to content
Merged
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
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,14 @@
"test:cov": "vitest run --coverage",
"clean": "rimraf dist coverage .wrangler tsconfig.tsbuildinfo",
"lint": "eslint .",
"pretty": "prettier --write **/*.{js,jsx,tsx,ts}",
"format": "prettier --write **/*.{js,jsx,tsx,ts}",
"prepare": "husky",
"cloudflare:dev": "yarn build && wrangler dev"
},
"dependencies": {
"@headlessui/react": "2.2.9",
"@internxt/lib": "1.4.1",
"@internxt/sdk": "1.12.0",
"@internxt/sdk": "1.12.5",
"async": "3.2.6",
"axios": "1.13.4",
"bip39": "3.1.0",
Expand Down
24 changes: 7 additions & 17 deletions src/network/download.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,8 @@
import { Network } from '@internxt/sdk/dist/network';
import { Decipheriv } from 'crypto';

import { getSha256 } from './crypto';
import { NetworkFacade } from './NetworkFacade';
import { joinReadableBinaryStreams } from './streams';
import envService from '../services/env.service';
import packageJson from '../../package.json';
import { SdkManager } from '../services/sdk-manager.service';

export type DownloadProgressCallback = (totalBytes: number, downloadedBytes: number) => void;
export type Downloadable = { fileId: string; bucketId: string };
Expand Down Expand Up @@ -121,19 +118,12 @@ const downloadOwnFile: DownloadOwnFileFunction = async (params) => {
const { bucketId, fileId, mnemonic, options } = params;
const auth = await getAuthFromCredentials(params.creds);

return new NetworkFacade(
Network.client(
envService.getVariable('networkUrl'),
{
clientName: packageJson.name,
clientVersion: packageJson.version,
},
{
bridgeUser: auth.username,
userId: auth.password,
},
),
).download(bucketId, fileId, mnemonic, {
const networkClient = SdkManager.instance.getNetwork({
user: auth.username,
pass: auth.password,
});

return new NetworkFacade(networkClient).download(bucketId, fileId, mnemonic, {
downloadingCallback: options?.notifyProgress,
abortController: options?.abortController,
});
Expand Down
23 changes: 7 additions & 16 deletions src/network/upload.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
import { Network } from '@internxt/sdk/dist/network';
import { ErrorWithContext } from '@internxt/sdk/dist/network/errors';

import { getSha256 } from './crypto';
import { NetworkFacade } from './NetworkFacade';
import axios, { AxiosError, AxiosProgressEvent } from 'axios';
import envService from '../services/env.service';
import packageJson from '../../package.json';
import { SdkManager } from '../services/sdk-manager.service';

export type UploadProgressCallback = (totalBytes: number, uploadedBytes: number) => void;

Expand Down Expand Up @@ -80,19 +78,12 @@ export async function uploadFile(bucketId: string, params: IUploadParams): Promi
pass: params.creds.pass,
});

const facade = new NetworkFacade(
Network.client(
envService.getVariable('networkUrl'),
{
clientName: packageJson.name,
clientVersion: packageJson.version,
},
{
bridgeUser: auth.username,
userId: auth.password,
},
),
);
const networkClient = SdkManager.instance.getNetwork({
user: auth.username,
pass: auth.password,
});

const facade = new NetworkFacade(networkClient);

if (params.parts) {
return facade
Expand Down
25 changes: 3 additions & 22 deletions src/services/download.service.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import axios from 'axios';
import fileDownload from 'js-file-download';

import { NetworkService, ProgressOptions } from './network.service';
Expand All @@ -8,7 +7,8 @@ import { binaryStreamToBlob } from '../network/streams';
import { SendItem } from '../models/SendItem';
import { StreamService } from './stream.service';
import { getAllItemsList } from './items.service';
import envService from './env.service';
import { SdkManager } from './sdk-manager.service';
import { GetSendLinkResponse } from '@internxt/sdk/dist/send/types';

/**
* This service has *the only responsability* of downloading
Expand Down Expand Up @@ -112,25 +112,6 @@ export class DownloadService {
}
}

/**
* TODO: SDK
*/
export interface GetSendLinkResponse {
id: string;
title: string;
subject: string;
code: string;
views: number;
userId: number | null;
items: SendItem[];
createdAt: string;
updatedAt: string;
expirationAt: string;
size: number;
}

export async function getSendLink(linkId: string): Promise<GetSendLinkResponse> {
const res = await axios.get<GetSendLinkResponse>(envService.getVariable('sendApiUrl') + '/links/' + linkId);

return res.data;
return SdkManager.instance.getSend().getSendLink(linkId);
}
44 changes: 44 additions & 0 deletions src/services/sdk-manager.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import packageJson from '../../package.json';
import envService from './env.service';
import { AppDetails } from '@internxt/sdk/dist/shared';
import { Network } from '@internxt/sdk/dist/network';
import { Send } from '@internxt/sdk/dist/send';

/**
* Manages all the sdk submodules initialization
* based on the current apiSecurity details
*/
export class SdkManager {
public static readonly instance: SdkManager = new SdkManager();

/**
* Returns the application details from package.json
* @returns The name and the version of the app from package.json
**/
public static readonly getAppDetails = (): AppDetails => {
return {
clientName: packageJson.name,
clientVersion: packageJson.version,
};
};

/** Network SDK */
public getNetwork = (credentials: { user: string; pass: string }) => {
const appDetails = SdkManager.getAppDetails();

return Network.client(envService.getVariable('networkUrl'), appDetails, {
bridgeUser: credentials.user,
userId: credentials.pass,
});
};

/** Send SDK */
public getSend = (customHeaders?: Record<string, string>) => {
const appDetails = SdkManager.getAppDetails();

return Send.client(envService.getVariable('sendApiUrl'), {
...appDetails,
...(customHeaders ? { customHeaders } : {}),
});
};
}
52 changes: 52 additions & 0 deletions src/services/sdkmanager.service.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { beforeEach, describe, expect, test, vi } from 'vitest';
import { randomBytes } from 'node:crypto';
import { Network } from '@internxt/sdk/dist/network';
import { SdkManager } from '../../src/services/sdk-manager.service';
import { AppDetails } from '@internxt/sdk/dist/shared';
import packageJson from '../../package.json';
import envService from './env.service';

describe('SDKManager service', () => {
const appDetails: AppDetails = {
clientName: randomBytes(16).toString('hex'),
clientVersion: randomBytes(16).toString('hex'),
};

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

test('When getAppDetails is requested, then it is generated using packageJson values', () => {
const expectedAppdetails = {
clientName: packageJson.name,
clientVersion: packageJson.version,
};

const appDetailsResponse = SdkManager.getAppDetails();
expect(expectedAppdetails).to.be.deep.equal(appDetailsResponse);
});

test('When Network client is requested, then it is generated using internxt sdk', () => {
const envEndpoint: { key: string; value: string } = {
key: 'networkUrl',
value: 'test/network',
};

const client = Network.client(envEndpoint.value, appDetails, {
bridgeUser: 'bridgeUser',
userId: 'userId',
});

const spyConfigService = vi.spyOn(envService, 'getVariable').mockReturnValue(envEndpoint.value);
vi.spyOn(SdkManager, 'getAppDetails').mockReturnValue(appDetails);
vi.spyOn(Network, 'client').mockReturnValue(client);

const newClient = SdkManager.instance.getNetwork({
user: 'bridgeUser',
pass: '123',
});

expect(spyConfigService).toHaveBeenCalledWith(envEndpoint.key);
expect(newClient).to.be.deep.equal(client);
});
});
59 changes: 34 additions & 25 deletions src/services/upload.service.test.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
import { vi, describe, it, expect, beforeEach, Mock } from 'vitest';
import { vi, describe, expect, beforeEach, Mock, test } from 'vitest';
import { getCaptchaToken } from '../lib/auth';
import { CreateSendLinksResponse, SendLink, UploadService } from './upload.service';
import { UploadService } from './upload.service';
import { FileWithPath } from 'react-dropzone';
import { SendItemData } from '../models/SendItem';
import axios from 'axios';
import { v4 } from 'uuid';

vi.mock('axios');
import { CreateSendLinksResponse, SendLink } from '@internxt/sdk/dist/send/types';
import { SdkManager } from './sdk-manager.service';

vi.mock('../lib/auth', () => ({
getCaptchaToken: vi.fn().mockResolvedValue('mock-token'),
Expand Down Expand Up @@ -73,18 +72,22 @@ const mockCreateSendLinksResponse: CreateSendLinksResponse = {
expirationAt: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000).toISOString(),
};

beforeEach(() => {
vi.clearAllMocks();
(axios.post as Mock).mockResolvedValue({ data: mockCreateSendLinksResponse });
});

describe('upload.service', () => {
it('When the uploadFiles() is done, then should call storeSendLinks() and getCaptchaToken()', async () => {
beforeEach(() => {
vi.clearAllMocks();
});

test('When the uploadFiles() is done, then should call storeSendLinks() and getCaptchaToken()', async () => {
const uploadFilesSpy = vi.spyOn(UploadService, 'uploadFiles');
const storeSendLinksSpy = vi.spyOn(UploadService, 'storeSendLinks');
(getCaptchaToken as Mock).mockResolvedValue('mock-token');

const axiosPostSpy = vi.spyOn(axios, 'post');
// @ts-expect-error - We only mock the properties we need
const sendSpy = vi.spyOn(SdkManager.instance, 'getSend').mockImplementation(() => {
return {
createSendLink: vi.fn().mockResolvedValue(mockCreateSendLinksResponse),
};
});

uploadFilesSpy.mockResolvedValue([mockSendLink, mockSendLinkFolder]);

Expand Down Expand Up @@ -116,19 +119,22 @@ describe('upload.service', () => {
plainCode: expect.any(String),
}),
);
expect(axiosPostSpy).toHaveBeenCalledTimes(1);
expect(sendSpy).toHaveBeenCalledTimes(1);
expect(uploadFilesSpy.mock.invocationCallOrder[0]).toBeLessThan(
(getCaptchaToken as Mock).mock.invocationCallOrder[0],
);
expect((getCaptchaToken as Mock).mock.invocationCallOrder[0]).toBeLessThan(
axiosPostSpy.mock.invocationCallOrder[0],
);
expect((getCaptchaToken as Mock).mock.invocationCallOrder[0]).toBeLessThan(sendSpy.mock.invocationCallOrder[0]);
expect(result).toContain('/d/');
});

it('When links are sent to be created, the captcha is generated before that', async () => {
test('When links are sent to be created, the captcha is generated before that', async () => {
const storeSendLinksSpy = vi.spyOn(UploadService, 'storeSendLinks');
const axiosPostSpy = vi.spyOn(axios, 'post');
// @ts-expect-error - We only mock the properties we need
const sendSpy = vi.spyOn(SdkManager.instance, 'getSend').mockImplementation(() => {
return {
createSendLink: vi.fn().mockResolvedValue(mockCreateSendLinksResponse),
};
});
(getCaptchaToken as Mock).mockResolvedValue('mock-token');
vi.spyOn(UploadService, 'uploadFiles').mockResolvedValue([mockSendLink]);

Expand All @@ -147,12 +153,10 @@ describe('upload.service', () => {
expect(storeSendLinksSpy.mock.invocationCallOrder[0]).toBeLessThan(
(getCaptchaToken as Mock).mock.invocationCallOrder[0],
);
expect((getCaptchaToken as Mock).mock.invocationCallOrder[0]).toBeLessThan(
axiosPostSpy.mock.invocationCallOrder[0],
);
expect((getCaptchaToken as Mock).mock.invocationCallOrder[0]).toBeLessThan(sendSpy.mock.invocationCallOrder[0]);
});

it('When an error is thrown before getCaptchaToken, then getCaptchaToken is not called', async () => {
test('When an error is thrown before getCaptchaToken, then getCaptchaToken is not called', async () => {
const uploadFilesSpy = vi.spyOn(UploadService, 'uploadFiles').mockImplementation(() => {
throw new Error('Error before captcha');
});
Expand All @@ -174,14 +178,19 @@ describe('upload.service', () => {
expect(getCaptchaToken).not.toHaveBeenCalled();
});

it('When an error is thrown in getCaptchaToken, then storeSendLinks is not called', async () => {
test('When an error is thrown in getCaptchaToken, then storeSendLinks is not called', async () => {
vi.spyOn(UploadService, 'uploadFiles').mockResolvedValue([mockSendLink]);
(getCaptchaToken as Mock).mockRejectedValue(new Error('Captcha error'));
const axiosPostSpy = vi.spyOn(axios, 'post');
// @ts-expect-error - We only mock the properties we need
const sendSpy = vi.spyOn(SdkManager.instance, 'getSend').mockImplementation(() => {
return {
createSendLink: vi.fn().mockResolvedValue(mockCreateSendLinksResponse),
};
});

await expect(UploadService.storeSendLinks).rejects.toThrow('Captcha error');

expect(getCaptchaToken).toHaveBeenCalled();
expect(axiosPostSpy).not.toHaveBeenCalled();
expect(sendSpy).not.toHaveBeenCalled();
});
});
Loading