Factor out realDevice [7/n]

Summary: `device.realDevice` was the escape hatch used in Sandy plugins to give access to device specific features like taking screenshots, clearing logs or accessing `adb`. Since in decapitated Flipper that won't be possible anymore (since plugins run in the client but device implementations on the server), all escape hatches have been bridged in this stack, and we can get of the `realDevice` interaction, by explicitly exposing those cases, which makes it type safe as well.

Reviewed By: passy

Differential Revision: D31079509

fbshipit-source-id: c9ec2e044d0dec0ccb1de287cf424907b198f818
This commit is contained in:
Michel Weststrate
2021-09-22 09:01:29 -07:00
committed by Facebook GitHub Bot
parent 11a27f9e1a
commit 3882357579
23 changed files with 106 additions and 93 deletions

View File

@@ -35,14 +35,21 @@ export type LogLevel =
| 'fatal';
export interface Device {
readonly realDevice: any; // TODO: temporarily, clean up T70688226
readonly isArchived: boolean;
readonly isConnected: boolean;
readonly os: DeviceOS;
readonly serial: string;
readonly deviceType: DeviceType;
onLogEntry(cb: DeviceLogListener): () => void;
readonly connected: Atom<boolean>;
executeShell(command: string): Promise<string>;
addLogListener(callback: DeviceLogListener): Symbol;
removeLogListener(id: Symbol): void;
executeShell(command: string): Promise<string>;
forwardPort(local: string, remote: string): Promise<boolean>;
clearLogs(): Promise<void>;
sendMetroCommand(command: string): Promise<void>;
navigateToLocation(location: string): Promise<void>;
screenshot(): Promise<Buffer>;
}
export type DevicePluginPredicate = (device: Device) => boolean;
@@ -59,20 +66,6 @@ export interface DevicePluginClient extends BasePluginClient {
readonly connected: ReadOnlyAtom<boolean>;
}
/**
* Wrapper interface around BaseDevice in Flipper
*/
export interface RealFlipperDevice {
os: DeviceOS;
serial: string;
isArchived: boolean;
connected: Atom<boolean>;
deviceType: DeviceType;
addLogListener(callback: DeviceLogListener): Symbol;
removeLogListener(id: Symbol): void;
executeShell(command: string): Promise<string>;
}
export class SandyDevicePluginInstance extends BasePluginInstance {
static is(thing: any): thing is SandyDevicePluginInstance {
return thing instanceof SandyDevicePluginInstance;
@@ -84,20 +77,20 @@ export class SandyDevicePluginInstance extends BasePluginInstance {
constructor(
flipperLib: FlipperLib,
definition: SandyPluginDefinition,
realDevice: RealFlipperDevice,
device: Device,
pluginKey: string,
initialStates?: Record<string, any>,
) {
super(flipperLib, definition, realDevice, pluginKey, initialStates);
super(flipperLib, definition, device, pluginKey, initialStates);
this.client = {
...this.createBasePluginClient(),
selectPlugin(pluginId: string, deeplink?: unknown) {
flipperLib.selectPlugin(realDevice, null, pluginId, deeplink);
flipperLib.selectPlugin(device, null, pluginId, deeplink);
},
get isConnected() {
return realDevice.connected.get();
return device.connected.get();
},
connected: realDevice.connected,
connected: device.connected,
};
this.initializePlugin(() =>
definition.asDevicePluginModule().devicePlugin(this.client),

View File

@@ -8,7 +8,7 @@
*/
import {Logger} from '../utils/Logger';
import {RealFlipperDevice} from './DevicePlugin';
import {Device} from './DevicePlugin';
import {NormalizedMenuEntry} from './MenuEntry';
import {RealFlipperClient} from './Plugin';
import {Notification} from './Notification';
@@ -24,7 +24,7 @@ export interface FlipperLib {
createPaste(input: string): Promise<string | undefined>;
GK(gatekeeper: string): boolean;
selectPlugin(
device: RealFlipperDevice,
device: Device,
client: RealFlipperClient | null,
pluginId: string,
deeplink: unknown,

View File

@@ -10,7 +10,7 @@
import {SandyPluginDefinition} from './SandyPluginDefinition';
import {BasePluginInstance, BasePluginClient} from './PluginBase';
import {FlipperLib} from './FlipperLib';
import {RealFlipperDevice} from './DevicePlugin';
import {Device} from './DevicePlugin';
import {batched} from '../state/batch';
import {Atom, createState, ReadOnlyAtom} from '../state/atom';
@@ -107,7 +107,7 @@ export interface RealFlipperClient {
device: string;
device_id: string;
};
deviceSync: RealFlipperDevice;
deviceSync: Device;
plugins: Set<string>;
isBackgroundPlugin(pluginId: string): boolean;
initPlugin(pluginId: string): void;

View File

@@ -12,7 +12,7 @@ import {EventEmitter} from 'events';
import {SandyPluginDefinition} from './SandyPluginDefinition';
import {MenuEntry, NormalizedMenuEntry, normalizeMenuEntry} from './MenuEntry';
import {FlipperLib} from './FlipperLib';
import {Device, RealFlipperDevice} from './DevicePlugin';
import {Device, DeviceLogListener} from './DevicePlugin';
import {batched} from '../state/batch';
import {Idler} from '../utils/Idler';
import {Notification} from './Notification';
@@ -78,6 +78,12 @@ export interface BasePluginClient {
*/
addMenuEntry(...entry: MenuEntry[]): void;
/**
* Listener that is triggered if the underlying device emits a log message.
* Listeners established with this mechanism will automatically be cleaned up during destroy
*/
onDeviceLogEntry(cb: DeviceLogListener): () => void;
/**
* Creates a Paste (similar to a Github Gist).
* Facebook only function. Resolves to undefined if creating a paste failed.
@@ -186,7 +192,7 @@ export abstract class BasePluginInstance {
constructor(
flipperLib: FlipperLib,
definition: SandyPluginDefinition,
realDevice: RealFlipperDevice,
device: Device,
pluginKey: string,
initialStates?: Record<string, any>,
) {
@@ -194,32 +200,10 @@ export abstract class BasePluginInstance {
this.definition = definition;
this.initialStates = initialStates;
this.pluginKey = pluginKey;
if (!realDevice) {
if (!device) {
throw new Error('Illegal State: Device has not yet been loaded');
}
this.device = {
realDevice, // TODO: temporarily, clean up T70688226
// N.B. we model OS as string, not as enum, to make custom device types possible in the future
os: realDevice.os,
serial: realDevice.serial,
get isArchived() {
return realDevice.isArchived;
},
get isConnected() {
return realDevice.connected.get();
},
deviceType: realDevice.deviceType,
onLogEntry: (cb) => {
const handle = realDevice.addLogListener(cb);
this.logListeners.push(handle);
return () => {
realDevice.removeLogListener(handle);
};
},
executeShell(command: string): Promise<string> {
return realDevice.executeShell(command);
},
};
this.device = device;
}
protected initializePlugin(factory: () => any) {
@@ -325,6 +309,13 @@ export abstract class BasePluginInstance {
}
}
},
onDeviceLogEntry: (cb: DeviceLogListener): (() => void) => {
const handle = this.device.addLogListener(cb);
this.logListeners.push(handle);
return () => {
this.device.removeLogListener(handle);
};
},
writeTextToClipboard: this.flipperLib.writeTextToClipboard,
createPaste: this.flipperLib.createPaste,
isFB: this.flipperLib.isFB,
@@ -364,7 +355,7 @@ export abstract class BasePluginInstance {
this.assertNotDestroyed();
this.deactivate();
this.logListeners.splice(0).forEach((handle) => {
this.device.realDevice.removeLogListener(handle);
this.device.removeLogListener(handle);
});
this.events.emit('destroy');
this.destroyed = true;