diff --git a/package-lock.json b/package-lock.json index 6768e055b..264a8fe59 100644 --- a/package-lock.json +++ b/package-lock.json @@ -23,6 +23,8 @@ "@sentry/electron": "^6.5.0", "@types/unzipper": "^0.10.11", "@types/yargs": "^17.0.33", + "@uppy/core": "^5.1.1", + "@uppy/tus": "^5.0.2", "@vscode/sudo-prompt": "^9.3.1", "@wordpress/compose": "^7.26.0", "@wordpress/dataviews": "^10.2.0", @@ -9601,6 +9603,12 @@ "node": ">= 10" } }, + "node_modules/@transloadit/prettier-bytes": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@transloadit/prettier-bytes/-/prettier-bytes-0.3.5.tgz", + "integrity": "sha512-xF4A3d/ZyX2LJWeQZREZQw+qFX4TGQ8bGVP97OLRt6sPO6T0TNHBFTuRHOJh7RNmYOBmQ9MHxpolD9bXihpuVA==", + "license": "MIT" + }, "node_modules/@tsconfig/node10": { "version": "1.0.9", "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz", @@ -10104,6 +10112,12 @@ "@types/node": "*" } }, + "node_modules/@types/retry": { + "version": "0.12.2", + "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.2.tgz", + "integrity": "sha512-XISRgDJ2Tc5q4TRqvgJtzsRkFYNJzZrhTdtMoGVBttwzzQJkPnS3WWTFc7kuDRoPtPakl+T+OfdEUjYJj7Jbow==", + "license": "MIT" + }, "node_modules/@types/semver": { "version": "7.7.0", "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.7.0.tgz", @@ -10749,6 +10763,131 @@ "win32" ] }, + "node_modules/@uppy/companion-client": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/@uppy/companion-client/-/companion-client-5.1.1.tgz", + "integrity": "sha512-DzrOWTbIZHvtgAFXBMYHk2wD27NjpBSVhY2tEiEIUhPd2CxbFRZjHM/N3HOt3VwZEAP471QWFLlJRWPcIY3A2Q==", + "license": "MIT", + "dependencies": { + "@uppy/utils": "^7.1.1", + "namespace-emitter": "^2.0.1", + "p-retry": "^6.1.0" + }, + "peerDependencies": { + "@uppy/core": "^5.1.1" + } + }, + "node_modules/@uppy/core": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/@uppy/core/-/core-5.1.1.tgz", + "integrity": "sha512-a0EDB+KBENB1+jkeY3oeZLMJfLhMSMsA1EfeAr6XUtKIN2uu2YHFhut5psQlYfLNOL7qtRWmG0jAa03ew1TvEw==", + "license": "MIT", + "dependencies": { + "@transloadit/prettier-bytes": "^0.3.4", + "@uppy/store-default": "^5.0.0", + "@uppy/utils": "^7.1.1", + "lodash": "^4.17.21", + "mime-match": "^1.0.2", + "namespace-emitter": "^2.0.1", + "nanoid": "^5.0.9", + "preact": "^10.5.13" + } + }, + "node_modules/@uppy/core/node_modules/nanoid": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-5.1.6.tgz", + "integrity": "sha512-c7+7RQ+dMB5dPwwCp4ee1/iV/q2P6aK1mTZcfr1BTuVlyW9hJYiMPybJCcnBlQtuSmTIWNeazm/zqNoZSSElBg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.js" + }, + "engines": { + "node": "^18 || >=20" + } + }, + "node_modules/@uppy/store-default": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@uppy/store-default/-/store-default-5.0.0.tgz", + "integrity": "sha512-hQtCSQ1yGiaval/wVYUWquYGDJ+bpQ7e4FhUUAsRQz1x1K+o7NBtjfp63O9I4Ks1WRoKunpkarZ+as09l02cPw==", + "license": "MIT" + }, + "node_modules/@uppy/tus": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@uppy/tus/-/tus-5.0.2.tgz", + "integrity": "sha512-sW1EEaCJJqyzzRm05XTmJCRl7gYBThWMXa6X6SmXUgNQo6WnKlyQ/6pVB7dypsVW8u2uzWpWfyUMfUNRFKleNw==", + "license": "MIT", + "dependencies": { + "@uppy/companion-client": "^5.1.1", + "@uppy/utils": "^7.1.1", + "tus-js-client": "^4.2.3" + }, + "peerDependencies": { + "@uppy/core": "^5.1.1" + } + }, + "node_modules/@uppy/tus/node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@uppy/tus/node_modules/js-base64": { + "version": "3.7.8", + "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-3.7.8.tgz", + "integrity": "sha512-hNngCeKxIUQiEUN3GPJOkz4wF/YvdUdbNL9hsBcMQTkKzboD7T/q3OYOuuPZLUE6dBxSGpwhk5mwuDud7JVAow==", + "license": "BSD-3-Clause" + }, + "node_modules/@uppy/tus/node_modules/proper-lockfile": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/proper-lockfile/-/proper-lockfile-4.1.2.tgz", + "integrity": "sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA==", + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "retry": "^0.12.0", + "signal-exit": "^3.0.2" + } + }, + "node_modules/@uppy/tus/node_modules/tus-js-client": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/tus-js-client/-/tus-js-client-4.3.1.tgz", + "integrity": "sha512-ZLeYmjrkaU1fUsKbIi8JML52uAocjEZtBx4DKjRrqzrZa0O4MYwT6db+oqePlspV+FxXJAyFBc/L5gwUi2OFsg==", + "license": "MIT", + "dependencies": { + "buffer-from": "^1.1.2", + "combine-errors": "^3.0.3", + "is-stream": "^2.0.0", + "js-base64": "^3.7.2", + "lodash.throttle": "^4.1.1", + "proper-lockfile": "^4.1.2", + "url-parse": "^1.5.7" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@uppy/utils": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/@uppy/utils/-/utils-7.1.3.tgz", + "integrity": "sha512-075LfJXzPaolhAUnxhKUfLpzdlPXHX991PYFlooMYEiA7EvDfoW8EO88jDE2G/wLGUGAYLOIhOE9g/m4gW9pkg==", + "license": "MIT", + "dependencies": { + "lodash": "^4.17.21", + "preact": "^10.5.13" + } + }, "node_modules/@use-gesture/core": { "version": "10.3.1", "resolved": "https://registry.npmjs.org/@use-gesture/core/-/core-10.3.1.tgz", @@ -18383,6 +18522,18 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-network-error": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/is-network-error/-/is-network-error-1.3.0.tgz", + "integrity": "sha512-6oIwpsgRfnDiyEDLMay/GqCl3HoAtH5+RUKW29gYkL0QA+ipzpDLA16yQs7/RHCSu+BwgbJaOUqa4A99qNVQVw==", + "license": "MIT", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", @@ -22174,6 +22325,15 @@ "node": ">= 0.6" } }, + "node_modules/mime-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/mime-match/-/mime-match-1.0.2.tgz", + "integrity": "sha512-VXp/ugGDVh3eCLOBCiHZMYWQaTNUHv2IJrut+yXA6+JbLPXHglHwfS/5A5L0ll+jkCY7fIzRJcH6OIunF+c6Cg==", + "license": "ISC", + "dependencies": { + "wildcard": "^1.1.0" + } + }, "node_modules/mime-types": { "version": "2.1.35", "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", @@ -22475,6 +22635,12 @@ "thenify-all": "^1.0.0" } }, + "node_modules/namespace-emitter": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/namespace-emitter/-/namespace-emitter-2.0.1.tgz", + "integrity": "sha512-N/sMKHniSDJBjfrkbS/tpkPj4RAbvW3mr8UAzvlMHyun93XEm83IAvhWtJVHo+RHn/oO8Job5YN4b+wRjSVp5g==", + "license": "MIT" + }, "node_modules/nan": { "version": "2.22.0", "resolved": "https://registry.npmjs.org/nan/-/nan-2.22.0.tgz", @@ -23159,6 +23325,32 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/p-retry": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-6.2.1.tgz", + "integrity": "sha512-hEt02O4hUct5wtwg4H4KcWgDdm+l1bOaEy/hWzd8xtXB9BqxTWBBhb+2ImAtH4Cv4rPjV76xN3Zumqk3k3AhhQ==", + "license": "MIT", + "dependencies": { + "@types/retry": "0.12.2", + "is-network-error": "^1.0.0", + "retry": "^0.13.1" + }, + "engines": { + "node": ">=16.17" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-retry/node_modules/retry": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", + "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, "node_modules/p-try": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", @@ -23884,6 +24076,16 @@ "node": "^12.20.0 || >=14" } }, + "node_modules/preact": { + "version": "10.28.0", + "resolved": "https://registry.npmjs.org/preact/-/preact-10.28.0.tgz", + "integrity": "sha512-rytDAoiXr3+t6OIP3WGlDd0ouCUG1iCWzkcY3++Nreuoi17y6T5i/zRhe6uYfoVcxq6YU+sBtJouuRDsq8vvqA==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/preact" + } + }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -24946,7 +25148,6 @@ "version": "0.12.0", "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", - "dev": true, "license": "MIT", "engines": { "node": ">= 4" @@ -28543,6 +28744,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/wildcard": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-1.1.2.tgz", + "integrity": "sha512-DXukZJxpHA8LuotRwL0pP1+rS6CS7FF2qStDDE1C7DDg2rLud2PXRMuEDYIPhgEezwnlHNL4c+N6MfMTjCGTng==", + "license": "MIT" + }, "node_modules/winreg": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/winreg/-/winreg-1.2.4.tgz", diff --git a/package.json b/package.json index 6ba1bc24a..a81a7f310 100644 --- a/package.json +++ b/package.json @@ -117,6 +117,8 @@ "@sentry/electron": "^6.5.0", "@types/unzipper": "^0.10.11", "@types/yargs": "^17.0.33", + "@uppy/core": "^5.1.1", + "@uppy/tus": "^5.0.2", "@vscode/sudo-prompt": "^9.3.1", "@wordpress/compose": "^7.26.0", "@wordpress/dataviews": "^10.2.0", diff --git a/src/constants.ts b/src/constants.ts index 7e0ec5e4e..7828b645e 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -109,3 +109,9 @@ export const IPC_VOID_HANDLERS = < const >[ // What's New export const FORCE_WHATS_NEW_WHEN_PATCH_CHANGED = true; + +// TUS upload constants +export const TUS_UPLOAD_ENDPOINT_BASE = + 'https://public-api.wordpress.com/rest/v1.1/studio-file-uploads'; +export const TUS_UPLOAD_CHUNK_SIZE = 500000; +export const TUS_UPLOAD_RETRY_DELAYS = [ 0, 1000, 3000, 5000, 10000 ]; diff --git a/src/hooks/sync-sites/use-sync-push.ts b/src/hooks/sync-sites/use-sync-push.ts index 3e0bc2cfb..5e5622993 100644 --- a/src/hooks/sync-sites/use-sync-push.ts +++ b/src/hooks/sync-sites/use-sync-push.ts @@ -1,8 +1,17 @@ import * as Sentry from '@sentry/electron/renderer'; +// @ts-expect-error - Uppy types require newer moduleResolution, but types exist at runtime +import Uppy from '@uppy/core'; +// @ts-expect-error - Uppy types require newer moduleResolution, but types exist at runtime +import Tus from '@uppy/tus'; import { sprintf } from '@wordpress/i18n'; import { useI18n } from '@wordpress/react-i18n'; import { useCallback, useEffect, useMemo } from 'react'; -import { SYNC_PUSH_SIZE_LIMIT_BYTES } from 'src/constants'; +import { + SYNC_PUSH_SIZE_LIMIT_BYTES, + TUS_UPLOAD_CHUNK_SIZE, + TUS_UPLOAD_ENDPOINT_BASE, + TUS_UPLOAD_RETRY_DELAYS, +} from 'src/constants'; import { ClearState, generateStateId, @@ -234,6 +243,117 @@ export function useSyncPush( { [ __ ] ); + /** + * Converts a file path to a File object for use with TUS + * Reads the file via IPC and creates a File object + */ + const getFileFromPath = useCallback( async ( filePath: string ): Promise< File > => { + // Read file content via IPC + const arrayBuffer = await getIpcApi().readFileAsArrayBuffer( filePath ); + const fileName = filePath.split( /[/\\]/ ).pop() || 'archive.tar.gz'; + const blob = new Blob( [ arrayBuffer ], { type: 'application/gzip' } ); + return new File( [ blob ], fileName, { type: 'application/gzip' } ); + }, [] ); + + /** + * Uploads a file using Uppy with TUS and Uppy (resumable uploads) + */ + const uploadArchive = useCallback( + async ( + archivePath: string, + remoteSiteId: number, + onUploadProgress?: ( percentage: number ) => void + ): Promise< number > => { + // Get authentication token + const token = await getIpcApi().getAuthenticationToken(); + if ( ! token?.accessToken ) { + throw new Error( 'No authentication token found' ); + } + + // Read file content via IPC and create File object + const file = await getFileFromPath( archivePath ); + + return new Promise< number >( ( resolve, reject ) => { + // Create Uppy instance + const uppy = new Uppy( { + debug: true, + autoProceed: true, + allowMultiple: false, + restrictions: { + maxNumberOfFiles: 1, + maxFileSize: SYNC_PUSH_SIZE_LIMIT_BYTES, + }, + } ); + + // Configure TUS plugin + uppy.use( Tus, { + endpoint: `${ TUS_UPLOAD_ENDPOINT_BASE }/${ remoteSiteId }`, + chunkSize: TUS_UPLOAD_CHUNK_SIZE, + retryDelays: TUS_UPLOAD_RETRY_DELAYS, + removeFingerprintOnSuccess: true, + headers: { + Authorization: `Bearer ${ token.accessToken }`, + }, + onBeforeRequest: ( req: Tus.HttpRequest ) => { + // make ALL requests be either POST or GET to honor the public-api.wordpress.com "contract". + const method = req._method; + if ( [ 'HEAD', 'OPTIONS' ].indexOf( method ) >= 0 ) { + req._method = 'GET'; + req.setHeader( 'X-HTTP-Method-Override', method ); + } + + if ( [ 'DELETE', 'PUT', 'PATCH' ].indexOf( method ) >= 0 ) { + req._method = 'POST'; + req.setHeader( 'X-HTTP-Method-Override', method ); + } + + req._xhr.open( req._method, req._url, true ); + + Object.keys( req._headers ).map( ( headerName ) => { + req.setHeader( headerName, req._headers[ headerName ] ); + } ); + + return req; + }, + } ); + + uppy.on( 'upload-error', ( file: Uppy.UppyFile, error: Error ) => { + reject( error ); + } ); + + uppy.on( 'upload-progress', ( file: Uppy.UppyFile, progress: Uppy.ProgressEvent ) => { + const percentage = ( progress.bytesUploaded / progress.bytesTotal ) * 100; + if ( onUploadProgress ) { + onUploadProgress( percentage ); + } + } ); + + uppy.on( 'upload-success', ( file: Uppy.UppyFile, response: Uppy.UploadResponse ) => { + const xhr = response.body.xhr as XMLHttpRequest; + const attachmentId = xhr.getResponseHeader( 'x-videopress-upload-media-id' ); + if ( attachmentId ) { + resolve( parseInt( attachmentId ) ); + } else { + reject( new Error( 'The upload failed.' ) ); + } + } ); + + uppy.on( 'complete', ( result: Uppy.UppyFile ) => { + console.log( 'Upload complete:', result ); + } ); + + // Add file to Uppy and start upload + uppy.addFile( { + source: 'Local', + name: file.name, + type: file.type, + data: file, + } ); + } ); + }, + [ getFileFromPath ] + ); + const pushSite = useCallback< PushSite >( async ( connectedSite, selectedSite, options ) => { if ( ! client ) { @@ -307,18 +427,45 @@ export function useSyncPush( { } ); try { - const response = await getIpcApi().pushArchive( - remoteSiteId, - archivePath, - options?.optionsToSync, - options?.specificSelectionPaths - ); + // Upload file using TUS (resumable uploads) + const attachmentId = await uploadArchive( archivePath, remoteSiteId, ( percentage ) => { + console.log( 'Upload progress:', percentage ); + } ); + const stateAfterUpload = getPushState( selectedSite.id, remoteSiteId ); if ( isKeyCancelled( stateAfterUpload?.status.key ) ) { return; } + const formData: [ string, unknown, Record< string, string >? ][] = [ + [ 'import_attachment_id', attachmentId ], + ]; + + if ( options?.specificSelectionPaths && options?.specificSelectionPaths.length > 0 ) { + const joinedPaths = options?.specificSelectionPaths.join( ',' ); + formData.push( [ 'list_sync_items', joinedPaths ] ); + } + + if ( options?.optionsToSync ) { + formData.push( [ 'options', options?.optionsToSync.join( ',' ) ] ); + } + + // Uses the import/initiate endpoint to initiate the import. + const response = await client.req.post< { + success: boolean; + } >( { + path: `/sites/${ remoteSiteId }/studio-app/sync/import/initiate`, + apiNamespace: 'wpcom/v2', + formData, + } ); + + const stateAfterImport = getPushState( selectedSite.id, remoteSiteId ); + + if ( isKeyCancelled( stateAfterImport?.status.key ) ) { + return; + } + if ( response.success ) { updatePushState( selectedSite.id, remoteSiteId, { status: pushStatesProgressInfo.creatingRemoteBackup, @@ -348,6 +495,7 @@ export function useSyncPush( { updatePushState, getErrorFromResponse, isKeyCancelled, + uploadArchive, ] ); diff --git a/src/ipc-handlers.ts b/src/ipc-handlers.ts index 45ab79d0c..4e037c573 100644 --- a/src/ipc-handlers.ts +++ b/src/ipc-handlers.ts @@ -101,7 +101,7 @@ export { downloadSyncBackup, exportSiteForPush, getConnectedWpcomSites, - pushArchive, + readFileAsArrayBuffer, removeExportedSiteTmpFile, removeSyncBackup, updateConnectedWpcomSites, diff --git a/src/modules/sync/lib/ipc-handlers.ts b/src/modules/sync/lib/ipc-handlers.ts index 3a93cdb26..9d206a177 100644 --- a/src/modules/sync/lib/ipc-handlers.ts +++ b/src/modules/sync/lib/ipc-handlers.ts @@ -15,10 +15,7 @@ import { download } from 'src/lib/download'; import { getSyncBackupTempPath } from 'src/lib/get-sync-backup-temp-path'; import { exportBackup } from 'src/lib/import-export/export/export-manager'; import { ExportOptions } from 'src/lib/import-export/export/types'; -import { getAuthenticationToken } from 'src/lib/oauth'; import { keepSqliteIntegrationUpdated } from 'src/lib/sqlite-versions'; -import wpcomFactory from 'src/lib/wpcom-factory'; -import wpcomXhrRequest from 'src/lib/wpcom-xhr-request-factory'; import { SiteServer } from 'src/site-server'; import { loadUserData, lockAppdata, saveUserData, unlockAppdata } from 'src/storage/user-data'; import { SyncOption } from 'src/types'; @@ -145,57 +142,12 @@ export function removeExportedSiteTmpFile( event: IpcMainInvokeEvent, path: stri } } -export async function pushArchive( +export async function readFileAsArrayBuffer( event: IpcMainInvokeEvent, - remoteSiteId: number, - archivePath: string, - optionsToSync?: string[], - specificSelectionPaths?: string[] -): Promise< { success: boolean; error?: string } > { - const token = await getAuthenticationToken(); - - if ( ! token?.accessToken ) { - throw new Error( 'No token found' ); - } - - const wpcom = wpcomFactory( token.accessToken, wpcomXhrRequest ); - const formData: [ string, unknown, Record< string, string >? ][] = [ - [ - 'import', - fs.createReadStream( archivePath ), - { - filename: 'loca-env-site-1.tar.gz', - contentType: 'application/gzip', - }, - ], - ]; - - if ( specificSelectionPaths && specificSelectionPaths.length > 0 ) { - const joinedPaths = specificSelectionPaths.join( ',' ); - formData.push( [ 'list_sync_items', joinedPaths ] ); - } - - if ( optionsToSync ) { - formData.push( [ 'options', optionsToSync.join( ',' ) ] ); - } - - try { - await wpcom.req.post( { - path: `/sites/${ remoteSiteId }/studio-app/sync/import`, - apiNamespace: 'wpcom/v2', - formData, - } ); - - return { success: true }; - } catch ( error ) { - const parseResult = z.object( { error: z.string() } ).safeParse( error ); - - if ( parseResult.success ) { - return { success: false, error: parseResult.data.error }; - } - - return { success: false, error: 'Unknown error' }; - } + filePath: string +): Promise< ArrayBuffer > { + const buffer = await fsPromises.readFile( filePath ); + return buffer.buffer.slice( buffer.byteOffset, buffer.byteOffset + buffer.byteLength ); } export async function downloadSyncBackup( diff --git a/src/preload.ts b/src/preload.ts index 1a8fb2bb4..9976f68c7 100644 --- a/src/preload.ts +++ b/src/preload.ts @@ -23,14 +23,7 @@ function ipcRendererSend< T extends keyof IpcHandlers >( const api: IpcApi = { exportSiteForPush: ( id, operationId, configuration ) => ipcRendererInvoke( 'exportSiteForPush', id, operationId, configuration ), - pushArchive: ( remoteSiteId, archivePath, optionsToSync, specificSelectionPaths ) => - ipcRendererInvoke( - 'pushArchive', - remoteSiteId, - archivePath, - optionsToSync, - specificSelectionPaths - ), + readFileAsArrayBuffer: ( filePath ) => ipcRendererInvoke( 'readFileAsArrayBuffer', filePath ), deleteSite: ( id, deleteFiles ) => ipcRendererInvoke( 'deleteSite', id, deleteFiles ), createSite: ( path, config ) => ipcRendererInvoke( 'createSite', path, config ), updateSite: ( updatedSite ) => ipcRendererInvoke( 'updateSite', updatedSite ),