diff --git a/desktop/flipper-common/src/server-types.tsx b/desktop/flipper-common/src/server-types.tsx index d15384066..633a26752 100644 --- a/desktop/flipper-common/src/server-types.tsx +++ b/desktop/flipper-common/src/server-types.tsx @@ -163,6 +163,22 @@ export type FlipperServerCommands = { ) => Promise; 'node-api-fs-stat': (path: string) => Promise; 'node-api-fs-readlink': (path: string) => Promise; + 'node-api-fs-readfile': ( + path: string, + options?: {encoding?: BufferEncoding}, + ) => Promise; + 'node-api-fs-readfile-binary': ( + path: string, + ) => Promise; + 'node-api-fs-writefile': ( + path: string, + contents: string, + options?: {encoding?: BufferEncoding}, + ) => Promise; + 'node-api-fs-writefile-binary': ( + path: string, + base64contents: string, + ) => Promise; /** * @throws ExecError */ diff --git a/desktop/flipper-plugin/src/plugin/FlipperLib.tsx b/desktop/flipper-plugin/src/plugin/FlipperLib.tsx index 57eefc4e0..e1fee4975 100644 --- a/desktop/flipper-plugin/src/plugin/FlipperLib.tsx +++ b/desktop/flipper-plugin/src/plugin/FlipperLib.tsx @@ -66,6 +66,17 @@ export type RemoteServerContext = { copyFile(src: string, dest: string, flags?: number): Promise; stat(path: string): Promise; readlink(path: string): Promise; + readFile( + path: string, + options?: {encoding?: BufferEncoding}, + ): Promise; + readFileBinary(path: string): Promise; // No Buffer, which is not a browser type + writeFile( + path: string, + contents: string, + options?: {encoding?: BufferEncoding}, + ): Promise; + writeFileBinary(path: string, contents: Uint8Array): Promise; }; downloadFile( url: string, diff --git a/desktop/flipper-plugin/src/test-utils/test-utils.tsx b/desktop/flipper-plugin/src/test-utils/test-utils.tsx index 5f33d7225..fba257db7 100644 --- a/desktop/flipper-plugin/src/test-utils/test-utils.tsx +++ b/desktop/flipper-plugin/src/test-utils/test-utils.tsx @@ -415,6 +415,10 @@ export function createMockFlipperLib(options?: StartPluginOptions): FlipperLib { constants: fsConstants, stat: jest.fn(), readlink: jest.fn(), + readFile: jest.fn(), + readFileBinary: jest.fn(), + writeFile: jest.fn(), + writeFileBinary: jest.fn(), }, downloadFile: jest.fn(), }, diff --git a/desktop/flipper-server-core/src/FlipperServerImpl.tsx b/desktop/flipper-server-core/src/FlipperServerImpl.tsx index b5cb2a7be..e6404bb50 100644 --- a/desktop/flipper-server-core/src/FlipperServerImpl.tsx +++ b/desktop/flipper-server-core/src/FlipperServerImpl.tsx @@ -48,8 +48,10 @@ import {commandDownloadFileStartFactory} from './commands/DownloadFile'; import {promises} from 'fs'; // Electron 11 runs on Node 12 which does not support fs.promises.rm import rm from 'rimraf'; +import assert from 'assert'; -const {access, copyFile, mkdir, unlink, stat, readlink} = promises; +const {access, copyFile, mkdir, unlink, stat, readlink, readFile, writeFile} = + promises; export const SERVICE_FLIPPER = 'flipper.oAuthToken'; @@ -258,6 +260,22 @@ export class FlipperServerImpl implements FlipperServer { }; }, 'node-api-fs-readlink': readlink, + 'node-api-fs-readfile': async (path, options) => { + const contents = await readFile(path, options ?? 'utf8'); + assert( + typeof contents === 'string', + `File ${path} was not read with a string encoding`, + ); + return contents; + }, + 'node-api-fs-readfile-binary': async (path) => { + const contents = await readFile(path); + return Base64.fromUint8Array(contents); + }, + 'node-api-fs-writefile': (path, contents, options) => + writeFile(path, contents, options ?? 'utf8'), + 'node-api-fs-writefile-binary': (path, base64contents) => + writeFile(path, Base64.toUint8Array(base64contents), 'binary'), // TODO: Do we need API to cancel an active download? 'download-file-start': commandDownloadFileStartFactory( this.emit.bind(this), diff --git a/desktop/flipper-ui-core/src/utils/flipperLibImplementation/index.tsx b/desktop/flipper-ui-core/src/utils/flipperLibImplementation/index.tsx index ad529ca0d..7463071b5 100644 --- a/desktop/flipper-ui-core/src/utils/flipperLibImplementation/index.tsx +++ b/desktop/flipper-ui-core/src/utils/flipperLibImplementation/index.tsx @@ -29,6 +29,7 @@ import {DetailSidebarImpl} from '../../sandy-chrome/DetailSidebarImpl'; import {RenderHost} from '../../RenderHost'; import {setMenuEntries} from '../../reducers/connections'; import {downloadFileFactory} from './downloadFile'; +import {Base64} from 'js-base64'; export function initializeFlipperLibImplementation( renderHost: RenderHost, @@ -115,6 +116,30 @@ export function initializeFlipperLibImplementation( renderHost.flipperServer.exec('node-api-fs-stat', path), readlink: async (path: string) => renderHost.flipperServer.exec('node-api-fs-readlink', path), + readFile: (path, options) => + renderHost.flipperServer.exec('node-api-fs-readfile', path, options), + readFileBinary: async (path) => + Base64.toUint8Array( + await renderHost.flipperServer.exec( + 'node-api-fs-readfile-binary', + path, + ), + ), + writeFile: (path, contents, options) => + renderHost.flipperServer.exec( + 'node-api-fs-writefile', + path, + contents, + options, + ), + writeFileBinary: async (path, contents) => { + const base64contents = Base64.fromUint8Array(contents); + return await renderHost.flipperServer.exec( + 'node-api-fs-writefile-binary', + path, + base64contents, + ); + }, }, downloadFile: downloadFileFactory(renderHost), },