Summary: Plugin metadata format extended to include type of each plugin (client / device) and list of supported devices (android/ios/..., emulator/physical, etc). This will allow to detect plugins supported by device even if they are not installed and only available on Marketplace. Reviewed By: mweststrate Differential Revision: D26073531 fbshipit-source-id: e331f1be1af1046cd4220a286a1d52378c26cc53
217 lines
5.1 KiB
TypeScript
217 lines
5.1 KiB
TypeScript
/**
|
|
* Copyright (c) Facebook, Inc. and its affiliates.
|
|
*
|
|
* This source code is licensed under the MIT license found in the
|
|
* LICENSE file in the root directory of this source tree.
|
|
*
|
|
* @format
|
|
*/
|
|
|
|
import stream from 'stream';
|
|
import {
|
|
DeviceLogEntry,
|
|
_SandyDevicePluginInstance,
|
|
_SandyPluginDefinition,
|
|
DeviceType,
|
|
DeviceLogListener,
|
|
} from 'flipper-plugin';
|
|
import type {DevicePluginDefinition, DevicePluginMap} from '../plugin';
|
|
import {getFlipperLibImplementation} from '../utils/flipperLibImplementation';
|
|
import {DeviceSpec, OS as PluginOS} from 'flipper-plugin-lib';
|
|
|
|
export type DeviceShell = {
|
|
stdout: stream.Readable;
|
|
stderr: stream.Readable;
|
|
stdin: stream.Writable;
|
|
};
|
|
|
|
export type OS = PluginOS | 'Windows' | 'MacOS' | 'JSWebApp';
|
|
|
|
export type DeviceExport = {
|
|
os: OS;
|
|
title: string;
|
|
deviceType: DeviceType;
|
|
serial: string;
|
|
logs: Array<DeviceLogEntry>;
|
|
};
|
|
|
|
export default class BaseDevice {
|
|
constructor(
|
|
serial: string,
|
|
deviceType: DeviceType,
|
|
title: string,
|
|
os: OS,
|
|
specs: DeviceSpec[] = [],
|
|
) {
|
|
this.serial = serial;
|
|
this.title = title;
|
|
this.deviceType = deviceType;
|
|
this.os = os;
|
|
this.specs = specs;
|
|
}
|
|
|
|
// operating system of this device
|
|
os: OS;
|
|
|
|
// human readable name for this device
|
|
title: string;
|
|
|
|
// type of this device
|
|
deviceType: DeviceType;
|
|
|
|
// serial number for this device
|
|
serial: string;
|
|
|
|
// additional device specs used for plugin compatibility checks
|
|
specs: DeviceSpec[];
|
|
|
|
// possible src of icon to display next to the device title
|
|
icon: string | null | undefined;
|
|
|
|
logListeners: Map<Symbol, DeviceLogListener> = new Map();
|
|
logEntries: Array<DeviceLogEntry> = [];
|
|
isArchived: boolean = false;
|
|
// if imported, stores the original source location
|
|
source = '';
|
|
|
|
// sorted list of supported device plugins
|
|
devicePlugins: string[] = [];
|
|
|
|
sandyPluginStates: Map<string, _SandyDevicePluginInstance> = new Map<
|
|
string,
|
|
_SandyDevicePluginInstance
|
|
>();
|
|
|
|
supportsOS(os: OS) {
|
|
return os.toLowerCase() === this.os.toLowerCase();
|
|
}
|
|
|
|
displayTitle(): string {
|
|
return this.title;
|
|
}
|
|
|
|
toJSON(): DeviceExport {
|
|
return {
|
|
os: this.os,
|
|
title: this.title,
|
|
deviceType: this.deviceType,
|
|
serial: this.serial,
|
|
logs: this.getLogs(),
|
|
};
|
|
}
|
|
|
|
teardown() {
|
|
for (const instance of this.sandyPluginStates.values()) {
|
|
instance.destroy();
|
|
}
|
|
}
|
|
|
|
addLogListener(callback: DeviceLogListener): Symbol {
|
|
const id = Symbol();
|
|
this.logListeners.set(id, callback);
|
|
return id;
|
|
}
|
|
|
|
_notifyLogListeners(entry: DeviceLogEntry) {
|
|
if (this.logListeners.size > 0) {
|
|
this.logListeners.forEach((listener) => {
|
|
// prevent breaking other listeners, if one listener doesn't work.
|
|
try {
|
|
listener(entry);
|
|
} catch (e) {
|
|
console.error(`Log listener exception:`, e);
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
addLogEntry(entry: DeviceLogEntry) {
|
|
this.logEntries.push(entry);
|
|
this._notifyLogListeners(entry);
|
|
}
|
|
|
|
// TODO: remove getLogs T70688226
|
|
getLogs(startDate: Date | null = null) {
|
|
return startDate != null
|
|
? this.logEntries.filter((log) => {
|
|
return log.date > startDate;
|
|
})
|
|
: this.logEntries;
|
|
}
|
|
|
|
clearLogs(): Promise<void> {
|
|
// Only for device types that allow clearing.
|
|
this.logEntries = [];
|
|
return Promise.resolve();
|
|
}
|
|
|
|
removeLogListener(id: Symbol) {
|
|
this.logListeners.delete(id);
|
|
}
|
|
|
|
navigateToLocation(_location: string) {
|
|
throw new Error('unimplemented');
|
|
}
|
|
|
|
archive(): any | null | undefined {
|
|
return null;
|
|
}
|
|
|
|
screenshot(): Promise<Buffer> {
|
|
return Promise.reject(
|
|
new Error('No screenshot support for current device'),
|
|
);
|
|
}
|
|
|
|
async screenCaptureAvailable(): Promise<boolean> {
|
|
return false;
|
|
}
|
|
|
|
async startScreenCapture(_destination: string): Promise<void> {
|
|
throw new Error('startScreenCapture not implemented on BaseDevice ');
|
|
}
|
|
|
|
async stopScreenCapture(): Promise<string | null> {
|
|
return null;
|
|
}
|
|
|
|
loadDevicePlugins(devicePlugins?: DevicePluginMap) {
|
|
if (!devicePlugins) {
|
|
return;
|
|
}
|
|
const plugins = Array.from(devicePlugins.values());
|
|
for (const plugin of plugins) {
|
|
this.loadDevicePlugin(plugin);
|
|
}
|
|
}
|
|
|
|
loadDevicePlugin(plugin: DevicePluginDefinition) {
|
|
if (plugin instanceof _SandyPluginDefinition) {
|
|
if (plugin.asDevicePluginModule().supportsDevice(this as any)) {
|
|
this.devicePlugins.push(plugin.id);
|
|
this.sandyPluginStates.set(
|
|
plugin.id,
|
|
new _SandyDevicePluginInstance(
|
|
getFlipperLibImplementation(),
|
|
plugin,
|
|
this,
|
|
),
|
|
); // TODO T70582933: pass initial state if applicable
|
|
}
|
|
} else {
|
|
if (plugin.supportsDevice(this)) {
|
|
this.devicePlugins.push(plugin.id);
|
|
}
|
|
}
|
|
}
|
|
|
|
unloadDevicePlugin(pluginId: string) {
|
|
const instance = this.sandyPluginStates.get(pluginId);
|
|
if (instance) {
|
|
instance.destroy();
|
|
this.sandyPluginStates.delete(pluginId);
|
|
}
|
|
this.devicePlugins.splice(this.devicePlugins.indexOf(pluginId), 1);
|
|
}
|
|
}
|