From 3bb555c25c0e0937b71f3eeb5611f088298021e1 Mon Sep 17 00:00:00 2001 From: larryrider Date: Mon, 9 Feb 2026 15:06:05 +0100 Subject: [PATCH 1/3] refactor: migrate to SdkManager for network interactions and clean up unused imports --- package.json | 2 +- src/network/download.ts | 24 +++------- src/network/upload.ts | 23 +++------- src/services/download.service.ts | 25 ++--------- src/services/sdk-manager.service.ts | 44 ++++++++++++++++++ src/services/sdkmanager.service.test.ts | 52 ++++++++++++++++++++++ src/services/upload.service.test.ts | 59 ++++++++++++++----------- src/services/upload.service.ts | 52 +++------------------- src/views/DownloadView.tsx | 3 +- yarn.lock | 8 ++-- 10 files changed, 160 insertions(+), 132 deletions(-) create mode 100644 src/services/sdk-manager.service.ts create mode 100644 src/services/sdkmanager.service.test.ts diff --git a/package.json b/package.json index cd9ac5b..43ab106 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,7 @@ "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", diff --git a/src/network/download.ts b/src/network/download.ts index 9199f37..5556e62 100644 --- a/src/network/download.ts +++ b/src/network/download.ts @@ -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 }; @@ -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, }); diff --git a/src/network/upload.ts b/src/network/upload.ts index 41a5f5e..623d3f3 100644 --- a/src/network/upload.ts +++ b/src/network/upload.ts @@ -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; @@ -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 diff --git a/src/services/download.service.ts b/src/services/download.service.ts index 9c84c5d..20ce321 100644 --- a/src/services/download.service.ts +++ b/src/services/download.service.ts @@ -1,4 +1,3 @@ -import axios from 'axios'; import fileDownload from 'js-file-download'; import { NetworkService, ProgressOptions } from './network.service'; @@ -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 @@ -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 { - const res = await axios.get(envService.getVariable('sendApiUrl') + '/links/' + linkId); - - return res.data; + return SdkManager.instance.getSend().getSendLink(linkId); } diff --git a/src/services/sdk-manager.service.ts b/src/services/sdk-manager.service.ts new file mode 100644 index 0000000..0ace10d --- /dev/null +++ b/src/services/sdk-manager.service.ts @@ -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) => { + const appDetails = SdkManager.getAppDetails(); + + return Send.client(envService.getVariable('sendApiUrl'), { + ...appDetails, + ...(customHeaders ?? {}), + }); + }; +} diff --git a/src/services/sdkmanager.service.test.ts b/src/services/sdkmanager.service.test.ts new file mode 100644 index 0000000..d2d4f45 --- /dev/null +++ b/src/services/sdkmanager.service.test.ts @@ -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); + }); +}); diff --git a/src/services/upload.service.test.ts b/src/services/upload.service.test.ts index 333f196..ae4770f 100644 --- a/src/services/upload.service.test.ts +++ b/src/services/upload.service.test.ts @@ -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'), @@ -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]); @@ -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]); @@ -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'); }); @@ -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(); }); }); diff --git a/src/services/upload.service.ts b/src/services/upload.service.ts index 55214e8..874302f 100644 --- a/src/services/upload.service.ts +++ b/src/services/upload.service.ts @@ -1,14 +1,14 @@ import { randomBytes } from 'crypto'; import { queue, QueueObject } from 'async'; import { generateMnemonic } from 'bip39'; -import axios from 'axios'; import { aes, stringUtils } from '@internxt/lib'; import { MAX_ITEMS_PER_LINK, MAX_BYTES_PER_SEND } from '../constants'; import { NetworkService } from './network.service'; import { SendItemData } from '../models/SendItem'; import { getCaptchaToken } from '../lib/auth'; -import envService from './env.service'; +import { CreateSendLinksPayload, SendLink } from '@internxt/sdk/dist/send/types'; +import { SdkManager } from './sdk-manager.service'; interface FileWithNetworkId extends File { networkId: string; @@ -44,13 +44,11 @@ const originUrl = window.origin; export class UploadService { static async storeSendLinks(payload: CreateSendLinksPayload) { const token = await getCaptchaToken(); - const res = await axios.post(envService.getVariable('sendApiUrl') + '/links', payload, { - headers: { - 'x-internxt-captcha': token, - }, - }); + const customHeaders = { + 'x-internxt-captcha': token, + }; - return res.data; + return SdkManager.instance.getSend(customHeaders).createSendLink(payload); } static async uploadFilesAndGetLink( @@ -257,41 +255,3 @@ class UploadManager { } } } - -/** - * TODO: SDK - */ -export interface SendLink { - id: string; - name: string; - type: 'file' | 'folder'; - size: number; - networkId: string; - encryptionKey: string; - parent_folder: string | null; -} - -export interface CreateSendLinksPayload { - sender?: string; - receivers?: string[]; - code: string; - title?: string; - subject?: string; - items: SendLink[]; - mnemonic: string; -} - -export interface CreateSendLinksResponse { - id: string; - title: string; - subject: string; - code: string; - sender: string; - receivers: string[]; - views: number; - userId: number; - items: SendLink[]; - createdAt: string; - updatedAt: string; - expirationAt: string; -} diff --git a/src/views/DownloadView.tsx b/src/views/DownloadView.tsx index cf4f33a..3452454 100644 --- a/src/views/DownloadView.tsx +++ b/src/views/DownloadView.tsx @@ -9,13 +9,14 @@ import FancySpinner from '../components/FancySpinner'; import Spinner from '../components/Spinner'; import ItemsList from '../components/ItemList'; import Layout from '../Layout'; -import { DownloadService, getSendLink, GetSendLinkResponse } from '../services/download.service'; +import { DownloadService, getSendLink } from '../services/download.service'; import { SendItemData } from '../models/SendItem'; import { getAllItemsList } from '../services/items.service'; import { ProgressOptions } from '../services/network.service'; import SendBanner from '../components/SendBanner'; import moment from 'moment'; import { stringUtils } from '@internxt/lib'; +import { GetSendLinkResponse } from '@internxt/sdk/dist/send/types'; export default function DownloadView() { const [state, setState] = useState< diff --git a/yarn.lock b/yarn.lock index 9a01dd2..ec94712 100644 --- a/yarn.lock +++ b/yarn.lock @@ -888,10 +888,10 @@ version "1.0.2" resolved "https://codeload.github.com/internxt/prettier-config/tar.gz/9fa74e9a2805e1538b50c3809324f1c9d0f3e4f9" -"@internxt/sdk@1.12.0": - version "1.12.0" - resolved "https://registry.yarnpkg.com/@internxt/sdk/-/sdk-1.12.0.tgz#cf9c2f0ca8864a688e4c161f470e171997bff7bb" - integrity sha512-QrjH2yJP7MjxAVvkOe6quqX7RYzC6e3M0XcXralJEFybDpimJBJbvRTPUe7+9XRQ6gHdmYi1u3ySDVoZyZpkug== +"@internxt/sdk@1.12.5": + version "1.12.5" + resolved "https://registry.yarnpkg.com/@internxt/sdk/-/sdk-1.12.5.tgz#a0ce80771fa20459392f8d49b72012880abbaa45" + integrity sha512-65zx7xRsBitEpuUAu0V1wh2WjrbGU0DK9ovYdSHFeVnMaIERVRzojtsnVpB8CECEtCnniJcGStjaCUxuaugepQ== dependencies: axios "1.13.2" uuid "11.1.0" From 780359bbd9c9d5d62cf12e0a67b3f46ff4920e4b Mon Sep 17 00:00:00 2001 From: larryrider Date: Mon, 9 Feb 2026 15:12:13 +0100 Subject: [PATCH 2/3] fix: rename script "pretty" to "format" in package.json --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 43ab106..42def21 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ "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" }, From 80f247ca11784fa83882e4c52bd58f8e529e297a Mon Sep 17 00:00:00 2001 From: larryrider Date: Mon, 9 Feb 2026 15:17:48 +0100 Subject: [PATCH 3/3] fix: update conditional operator for customHeaders in getSend method --- src/services/sdk-manager.service.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/services/sdk-manager.service.ts b/src/services/sdk-manager.service.ts index 0ace10d..2853c16 100644 --- a/src/services/sdk-manager.service.ts +++ b/src/services/sdk-manager.service.ts @@ -38,7 +38,7 @@ export class SdkManager { return Send.client(envService.getVariable('sendApiUrl'), { ...appDetails, - ...(customHeaders ?? {}), + ...(customHeaders ? { customHeaders } : {}), }); }; }