Expose current connection status to Sandy plugins
Summary: Introduced `isConnected` flag on device and plugin client to reflect whether a connection is still available for the plugins, or that they have been disconnected. Potentially we could expose the (readonly) `connected` state atom for this as well, or an `onDisconnect` event for device pugins, to create a responsive UI, but there might be no need for that, in which case this suffices. Reviewed By: nikoant Differential Revision: D26249346 fbshipit-source-id: b8486713fdf2fcd520488ce54f771bd038fd13f8
This commit is contained in:
committed by
Facebook GitHub Bot
parent
7e1bf0f58b
commit
bb529411b5
@@ -29,6 +29,9 @@ test('Devices can disconnect', async () => {
|
|||||||
return {
|
return {
|
||||||
counter,
|
counter,
|
||||||
destroy,
|
destroy,
|
||||||
|
get isConnected() {
|
||||||
|
return client.device.isConnected;
|
||||||
|
},
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
supportsDevice() {
|
supportsDevice() {
|
||||||
@@ -42,6 +45,9 @@ test('Devices can disconnect', async () => {
|
|||||||
const {device} = await createMockFlipperWithPlugin(deviceplugin);
|
const {device} = await createMockFlipperWithPlugin(deviceplugin);
|
||||||
|
|
||||||
device.sandyPluginStates.get(deviceplugin.id)!.instanceApi.counter.set(1);
|
device.sandyPluginStates.get(deviceplugin.id)!.instanceApi.counter.set(1);
|
||||||
|
expect(
|
||||||
|
device.sandyPluginStates.get(deviceplugin.id)!.instanceApi.isConnected,
|
||||||
|
).toBe(true);
|
||||||
|
|
||||||
expect(device.isArchived).toBe(false);
|
expect(device.isArchived).toBe(false);
|
||||||
|
|
||||||
@@ -49,6 +55,7 @@ test('Devices can disconnect', async () => {
|
|||||||
|
|
||||||
expect(device.isArchived).toBe(true);
|
expect(device.isArchived).toBe(true);
|
||||||
const instance = device.sandyPluginStates.get(deviceplugin.id)!;
|
const instance = device.sandyPluginStates.get(deviceplugin.id)!;
|
||||||
|
expect(instance.instanceApi.isConnected).toBe(false);
|
||||||
expect(instance).toBeTruthy();
|
expect(instance).toBeTruthy();
|
||||||
expect(instance.instanceApi.counter.get()).toBe(1); // state preserved
|
expect(instance.instanceApi.counter.get()).toBe(1); // state preserved
|
||||||
expect(instance.instanceApi.destroy).toBeCalledTimes(0);
|
expect(instance.instanceApi.destroy).toBeCalledTimes(0);
|
||||||
@@ -126,6 +133,9 @@ test('clients can disconnect but preserve state', async () => {
|
|||||||
disconnect,
|
disconnect,
|
||||||
counter,
|
counter,
|
||||||
destroy,
|
destroy,
|
||||||
|
get isConnected() {
|
||||||
|
return client.isConnected;
|
||||||
|
},
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
Component() {
|
Component() {
|
||||||
@@ -142,6 +152,7 @@ test('clients can disconnect but preserve state', async () => {
|
|||||||
expect(instance.instanceApi.destroy).toBeCalledTimes(0);
|
expect(instance.instanceApi.destroy).toBeCalledTimes(0);
|
||||||
expect(instance.instanceApi.connect).toBeCalledTimes(1);
|
expect(instance.instanceApi.connect).toBeCalledTimes(1);
|
||||||
expect(instance.instanceApi.disconnect).toBeCalledTimes(0);
|
expect(instance.instanceApi.disconnect).toBeCalledTimes(0);
|
||||||
|
expect(instance.instanceApi.isConnected).toBe(true);
|
||||||
expect(client.connected.get()).toBe(true);
|
expect(client.connected.get()).toBe(true);
|
||||||
|
|
||||||
client.disconnect();
|
client.disconnect();
|
||||||
@@ -150,6 +161,7 @@ test('clients can disconnect but preserve state', async () => {
|
|||||||
instance = client.sandyPluginStates.get(plugin.id)!;
|
instance = client.sandyPluginStates.get(plugin.id)!;
|
||||||
expect(instance).toBeTruthy();
|
expect(instance).toBeTruthy();
|
||||||
expect(instance.instanceApi.counter.get()).toBe(1); // state preserved
|
expect(instance.instanceApi.counter.get()).toBe(1); // state preserved
|
||||||
|
expect(instance.instanceApi.isConnected).toBe(false);
|
||||||
expect(instance.instanceApi.destroy).toBeCalledTimes(0);
|
expect(instance.instanceApi.destroy).toBeCalledTimes(0);
|
||||||
expect(instance.instanceApi.connect).toBeCalledTimes(1);
|
expect(instance.instanceApi.connect).toBeCalledTimes(1);
|
||||||
expect(instance.instanceApi.disconnect).toBeCalledTimes(1);
|
expect(instance.instanceApi.disconnect).toBeCalledTimes(1);
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ export type LogLevel =
|
|||||||
export interface Device {
|
export interface Device {
|
||||||
readonly realDevice: any; // TODO: temporarily, clean up T70688226
|
readonly realDevice: any; // TODO: temporarily, clean up T70688226
|
||||||
readonly isArchived: boolean;
|
readonly isArchived: boolean;
|
||||||
|
readonly isConnected: boolean;
|
||||||
readonly os: string;
|
readonly os: string;
|
||||||
readonly deviceType: DeviceType;
|
readonly deviceType: DeviceType;
|
||||||
onLogEntry(cb: DeviceLogListener): () => void;
|
onLogEntry(cb: DeviceLogListener): () => void;
|
||||||
@@ -79,7 +80,7 @@ export class SandyDevicePluginInstance extends BasePluginInstance {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** client that is bound to this instance */
|
/** client that is bound to this instance */
|
||||||
client: DevicePluginClient;
|
readonly client: DevicePluginClient;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
flipperLib: FlipperLib,
|
flipperLib: FlipperLib,
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import {BasePluginInstance, BasePluginClient} from './PluginBase';
|
|||||||
import {FlipperLib} from './FlipperLib';
|
import {FlipperLib} from './FlipperLib';
|
||||||
import {RealFlipperDevice} from './DevicePlugin';
|
import {RealFlipperDevice} from './DevicePlugin';
|
||||||
import {batched} from '../state/batch';
|
import {batched} from '../state/batch';
|
||||||
|
import {Atom, createState} from '../state/atom';
|
||||||
|
|
||||||
type EventsContract = Record<string, any>;
|
type EventsContract = Record<string, any>;
|
||||||
type MethodsContract = Record<string, (params: any) => Promise<any>>;
|
type MethodsContract = Record<string, (params: any) => Promise<any>>;
|
||||||
@@ -38,6 +39,8 @@ export interface PluginClient<
|
|||||||
*/
|
*/
|
||||||
readonly appName: string;
|
readonly appName: string;
|
||||||
|
|
||||||
|
readonly isConnected: boolean;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* the onConnect event is fired whenever the plugin is connected to it's counter part on the device.
|
* the onConnect event is fired whenever the plugin is connected to it's counter part on the device.
|
||||||
* For most plugins this event is fired if the user selects the plugin,
|
* For most plugins this event is fired if the user selects the plugin,
|
||||||
@@ -101,6 +104,7 @@ export interface PluginClient<
|
|||||||
*/
|
*/
|
||||||
export interface RealFlipperClient {
|
export interface RealFlipperClient {
|
||||||
id: string;
|
id: string;
|
||||||
|
connected: Atom<boolean>;
|
||||||
query: {
|
query: {
|
||||||
app: string;
|
app: string;
|
||||||
os: string;
|
os: string;
|
||||||
@@ -134,11 +138,11 @@ export class SandyPluginInstance extends BasePluginInstance {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** base client provided by Flipper */
|
/** base client provided by Flipper */
|
||||||
realClient: RealFlipperClient;
|
readonly realClient: RealFlipperClient;
|
||||||
/** client that is bound to this instance */
|
/** client that is bound to this instance */
|
||||||
client: PluginClient<any, any>;
|
readonly client: PluginClient<any, any>;
|
||||||
/** connection alive? */
|
/** connection alive? */
|
||||||
connected = false;
|
readonly connected = createState(false);
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
flipperLib: FlipperLib,
|
flipperLib: FlipperLib,
|
||||||
@@ -149,6 +153,7 @@ export class SandyPluginInstance extends BasePluginInstance {
|
|||||||
super(flipperLib, definition, realClient.deviceSync, initialStates);
|
super(flipperLib, definition, realClient.deviceSync, initialStates);
|
||||||
this.realClient = realClient;
|
this.realClient = realClient;
|
||||||
this.definition = definition;
|
this.definition = definition;
|
||||||
|
const self = this;
|
||||||
this.client = {
|
this.client = {
|
||||||
...this.createBasePluginClient(),
|
...this.createBasePluginClient(),
|
||||||
get appId() {
|
get appId() {
|
||||||
@@ -157,6 +162,9 @@ export class SandyPluginInstance extends BasePluginInstance {
|
|||||||
get appName() {
|
get appName() {
|
||||||
return realClient.query.app;
|
return realClient.query.app;
|
||||||
},
|
},
|
||||||
|
get isConnected() {
|
||||||
|
return self.connected.get();
|
||||||
|
},
|
||||||
onConnect: (cb) => {
|
onConnect: (cb) => {
|
||||||
this.events.on('connect', batched(cb));
|
this.events.on('connect', batched(cb));
|
||||||
},
|
},
|
||||||
@@ -212,7 +220,10 @@ export class SandyPluginInstance extends BasePluginInstance {
|
|||||||
activate() {
|
activate() {
|
||||||
super.activate();
|
super.activate();
|
||||||
const pluginId = this.definition.id;
|
const pluginId = this.definition.id;
|
||||||
if (!this.connected && !this.realClient.isBackgroundPlugin(pluginId)) {
|
if (
|
||||||
|
!this.connected.get() &&
|
||||||
|
!this.realClient.isBackgroundPlugin(pluginId)
|
||||||
|
) {
|
||||||
this.realClient.initPlugin(pluginId); // will call connect() if needed
|
this.realClient.initPlugin(pluginId); // will call connect() if needed
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -221,29 +232,29 @@ export class SandyPluginInstance extends BasePluginInstance {
|
|||||||
deactivate() {
|
deactivate() {
|
||||||
super.deactivate();
|
super.deactivate();
|
||||||
const pluginId = this.definition.id;
|
const pluginId = this.definition.id;
|
||||||
if (this.connected && !this.realClient.isBackgroundPlugin(pluginId)) {
|
if (this.connected.get() && !this.realClient.isBackgroundPlugin(pluginId)) {
|
||||||
this.realClient.deinitPlugin(pluginId);
|
this.realClient.deinitPlugin(pluginId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
connect() {
|
connect() {
|
||||||
this.assertNotDestroyed();
|
this.assertNotDestroyed();
|
||||||
if (!this.connected) {
|
if (!this.connected.get()) {
|
||||||
this.connected = true;
|
this.connected.set(true);
|
||||||
this.events.emit('connect');
|
this.events.emit('connect');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
disconnect() {
|
disconnect() {
|
||||||
this.assertNotDestroyed();
|
this.assertNotDestroyed();
|
||||||
if (this.connected) {
|
if (this.connected.get()) {
|
||||||
this.connected = false;
|
this.connected.set(false);
|
||||||
this.events.emit('disconnect');
|
this.events.emit('disconnect');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
destroy() {
|
destroy() {
|
||||||
if (this.connected) {
|
if (this.connected.get()) {
|
||||||
this.realClient.deinitPlugin(this.definition.id);
|
this.realClient.deinitPlugin(this.definition.id);
|
||||||
}
|
}
|
||||||
super.destroy();
|
super.destroy();
|
||||||
@@ -265,7 +276,7 @@ export class SandyPluginInstance extends BasePluginInstance {
|
|||||||
|
|
||||||
private assertConnected() {
|
private assertConnected() {
|
||||||
this.assertNotDestroyed();
|
this.assertNotDestroyed();
|
||||||
if (!this.connected) {
|
if (!this.connected.get()) {
|
||||||
throw new Error('Plugin is not connected');
|
throw new Error('Plugin is not connected');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -90,23 +90,23 @@ export function getCurrentPluginInstance(): typeof currentPluginInstance {
|
|||||||
|
|
||||||
export abstract class BasePluginInstance {
|
export abstract class BasePluginInstance {
|
||||||
/** generally available Flipper APIs */
|
/** generally available Flipper APIs */
|
||||||
flipperLib: FlipperLib;
|
readonly flipperLib: FlipperLib;
|
||||||
/** the original plugin definition */
|
/** the original plugin definition */
|
||||||
definition: SandyPluginDefinition;
|
definition: SandyPluginDefinition;
|
||||||
/** the plugin instance api as used inside components and such */
|
/** the plugin instance api as used inside components and such */
|
||||||
instanceApi: any;
|
instanceApi: any;
|
||||||
/** the device owning this plugin */
|
/** the device owning this plugin */
|
||||||
device: Device;
|
readonly device: Device;
|
||||||
|
|
||||||
activated = false;
|
activated = false;
|
||||||
destroyed = false;
|
destroyed = false;
|
||||||
events = new EventEmitter();
|
readonly events = new EventEmitter();
|
||||||
|
|
||||||
// temporarily field that is used during deserialization
|
// temporarily field that is used during deserialization
|
||||||
initialStates?: Record<string, any>;
|
initialStates?: Record<string, any>;
|
||||||
|
|
||||||
// all the atoms that should be serialized when making an export / import
|
// all the atoms that should be serialized when making an export / import
|
||||||
rootStates: Record<string, Atom<any>> = {};
|
readonly rootStates: Record<string, Atom<any>> = {};
|
||||||
// last seen deeplink
|
// last seen deeplink
|
||||||
lastDeeplink?: any;
|
lastDeeplink?: any;
|
||||||
// export handler
|
// export handler
|
||||||
@@ -135,6 +135,10 @@ export abstract class BasePluginInstance {
|
|||||||
get isArchived() {
|
get isArchived() {
|
||||||
return realDevice.isArchived;
|
return realDevice.isArchived;
|
||||||
},
|
},
|
||||||
|
get isConnected() {
|
||||||
|
// for now same as isArchived, in the future we might distinguish between archived/imported and disconnected/offline devices
|
||||||
|
return !realDevice.isArchived;
|
||||||
|
},
|
||||||
deviceType: realDevice.deviceType,
|
deviceType: realDevice.deviceType,
|
||||||
|
|
||||||
onLogEntry(cb) {
|
onLogEntry(cb) {
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ import {BasePluginInstance} from '../plugin/PluginBase';
|
|||||||
import {FlipperLib} from '../plugin/FlipperLib';
|
import {FlipperLib} from '../plugin/FlipperLib';
|
||||||
import {stubLogger} from '../utils/Logger';
|
import {stubLogger} from '../utils/Logger';
|
||||||
import {Idler} from '../utils/Idler';
|
import {Idler} from '../utils/Idler';
|
||||||
|
import {createState} from '../state/atom';
|
||||||
|
|
||||||
type Renderer = RenderResult<typeof queries>;
|
type Renderer = RenderResult<typeof queries>;
|
||||||
|
|
||||||
@@ -207,10 +208,13 @@ export function startPlugin<Module extends FlipperPluginModule<any>>(
|
|||||||
isBackgroundPlugin(_pluginId: string) {
|
isBackgroundPlugin(_pluginId: string) {
|
||||||
return !!options?.isBackgroundPlugin;
|
return !!options?.isBackgroundPlugin;
|
||||||
},
|
},
|
||||||
|
connected: createState(false),
|
||||||
initPlugin() {
|
initPlugin() {
|
||||||
|
this.connected.set(true);
|
||||||
pluginInstance.connect();
|
pluginInstance.connect();
|
||||||
},
|
},
|
||||||
deinitPlugin() {
|
deinitPlugin() {
|
||||||
|
this.connected.set(false);
|
||||||
pluginInstance.disconnect();
|
pluginInstance.disconnect();
|
||||||
},
|
},
|
||||||
call(
|
call(
|
||||||
|
|||||||
@@ -51,6 +51,13 @@ The name of the application, for example 'Facebook', 'Instagram' or 'Slack'.
|
|||||||
|
|
||||||
A string that uniquely identifies the current application, is based on a combination of the application name and device serial on which the application is running.
|
A string that uniquely identifies the current application, is based on a combination of the application name and device serial on which the application is running.
|
||||||
|
|
||||||
|
#### `isConnected`
|
||||||
|
|
||||||
|
Returns whether there is currently an active connection. This is true if:
|
||||||
|
1. The device is still connected
|
||||||
|
2. The client is still connected
|
||||||
|
3. The plugin is currently selected by the user _or_ the plugin is running in the background.
|
||||||
|
|
||||||
### Events listeners
|
### Events listeners
|
||||||
|
|
||||||
#### `onMessage`
|
#### `onMessage`
|
||||||
@@ -182,6 +189,9 @@ Usage: `client.send(method: string, params: object): Promise<object>`
|
|||||||
|
|
||||||
If the plugin is connected, `send` can be used to invoke a [method](create-plugin#[background-plugins#using-flipperconnection) on the client implementation of the plugin.
|
If the plugin is connected, `send` can be used to invoke a [method](create-plugin#[background-plugins#using-flipperconnection) on the client implementation of the plugin.
|
||||||
|
|
||||||
|
Note that if `client.isConnected` returns `false`, calling `client.send` will throw an exception. This is the case if for example the connection with the device or application was lost.
|
||||||
|
Generally one should guard `client.send` calls with a check to `client.isConnected`.
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
@@ -362,6 +372,10 @@ A `string` that describes whether the device is a physical device or an emulator
|
|||||||
|
|
||||||
This `boolean` flag is `true` if the current device is coming from an import Flipper snapshot, and not an actually connected device.
|
This `boolean` flag is `true` if the current device is coming from an import Flipper snapshot, and not an actually connected device.
|
||||||
|
|
||||||
|
#### isConnected
|
||||||
|
|
||||||
|
This `boolean` flag is `true` if the connection to the device is still alive.
|
||||||
|
|
||||||
### Events
|
### Events
|
||||||
|
|
||||||
#### `onLogEntry`
|
#### `onLogEntry`
|
||||||
|
|||||||
Reference in New Issue
Block a user