fbshipit-source-id: c71048dfea2a03cf83650b55aa9d1e463251920c
This commit is contained in:
@@ -58,4 +58,4 @@ matrix:
|
|||||||
|
|
||||||
script:
|
script:
|
||||||
- cd iOS/Sample
|
- cd iOS/Sample
|
||||||
- xcodebuild clean build -workspace Sample.xcworkspace -scheme Pods-Sample
|
- xcodebuild clean build -workspace Sample.xcworkspace -scheme Pods-Sample -sdk iphonesimulator11.2
|
||||||
|
|||||||
@@ -63,23 +63,17 @@ public class MyApplication extends Application {
|
|||||||
To integrate with an iOS app, you can use [CocoaPods](https://cocoapods.org). Add the mobile Sonar SDK and its dependencies to your `Podfile`:
|
To integrate with an iOS app, you can use [CocoaPods](https://cocoapods.org). Add the mobile Sonar SDK and its dependencies to your `Podfile`:
|
||||||
|
|
||||||
```ruby
|
```ruby
|
||||||
platform :ios, '8.0'
|
project 'MyApp.xcodeproj'
|
||||||
swift_version = '4.1'
|
source 'https://github.com/facebook/Sonar.git'
|
||||||
|
source 'https://github.com/CocoaPods/Specs'
|
||||||
|
# Uncomment the next line to define a global platform for your project
|
||||||
|
swift_version = "4.1"
|
||||||
|
|
||||||
target 'MyApp' do
|
target 'MyApp' do
|
||||||
|
|
||||||
pod 'RSocket', :podspec => 'https://raw.githubusercontent.com/facebook/Sonar/master/iOS/third-party-podspecs/RSocket.podspec'
|
pod 'SonarKit', '~>0.0.1'
|
||||||
pod 'DoubleConversion', :podspec => 'https://raw.githubusercontent.com/facebook/Sonar/master/iOS/third-party-podspecs/DoubleConversion.podspec'
|
|
||||||
pod 'glog', :podspec => 'https://raw.githubusercontent.com/facebook/Sonar/master/iOS/third-party-podspecs/glog.podspec'
|
|
||||||
pod 'Folly', :podspec => 'https://raw.githubusercontent.com/facebook/Sonar/master/iOS/third-party-podspecs/Folly.podspec'
|
|
||||||
pod 'PeerTalk', :podspec => 'https://raw.githubusercontent.com/facebook/Sonar/master/iOS/third-party-podspecs/PeerTalk.podspec'
|
|
||||||
pod 'Yoga','~>1.8.1', :modular_headers => true
|
|
||||||
pod 'Sonar', :podspec => 'https://raw.githubusercontent.com/facebook/Sonar/master/xplat/Sonar/Sonar.podspec'
|
|
||||||
pod 'SonarKit', :podspec => 'https://raw.githubusercontent.com/facebook/Sonar/master/iOS/SonarKit.podspec'
|
|
||||||
pod 'SonarKit/SonarKitLayoutComponentKitSupport', :podspec => 'https://raw.githubusercontent.com/facebook/Sonar/master/iOS/SonarKit.podspec'
|
|
||||||
pod 'SonarKit/SKIOSNetworkPlugin', :podspec => 'https://raw.githubusercontent.com/facebook/Sonar/master/iOS/SonarKit.podspec'
|
|
||||||
pod 'ComponentKit', :podspec => 'https://raw.githubusercontent.com/facebook/Sonar/master/iOS/third-party-podspecs/ComponentKit.podspec'
|
|
||||||
post_install do |installer|
|
post_install do |installer|
|
||||||
|
|
||||||
installer.pods_project.targets.each do |target|
|
installer.pods_project.targets.each do |target|
|
||||||
if ['YogaKit'].include? target.name
|
if ['YogaKit'].include? target.name
|
||||||
target.build_configurations.each do |config|
|
target.build_configurations.each do |config|
|
||||||
@@ -112,7 +106,7 @@ and install the dependencies by running `pod install`. When you open the Xcode w
|
|||||||
```
|
```
|
||||||
<div class='warning'>
|
<div class='warning'>
|
||||||
|
|
||||||
* We haven't released the dependency to CocoaPods, because we weren't able to successfully validate the podspec of SonarKit. You could help us out by fixing this [issue](https://github.com/facebook/Sonar/issues/11) by submitting a PR to the repo.
|
* We haven't released the dependency to CocoaPods yet, here is the [issue](https://github.com/facebook/Sonar/issues/132) by which you can track.
|
||||||
* If you do not use CocoaPods as a dependency management tool then currently there is no way to integrate SonarKit other than manually including all the dependencies and building it.
|
* If you do not use CocoaPods as a dependency management tool then currently there is no way to integrate SonarKit other than manually including all the dependencies and building it.
|
||||||
* For Android, Sonar works with both emulators and physical devices connected through USB. However on iOS, we don't yet support physical devices.
|
* For Android, Sonar works with both emulators and physical devices connected through USB. However on iOS, we don't yet support physical devices.
|
||||||
* Also Sonar doesn't work with swift projects as its written in C++ and had C++ dependencies. But we are working on supporting sonar for swift projects. You can find this issue [here](https://github.com/facebook/Sonar/issues/13)
|
* Also Sonar doesn't work with swift projects as its written in C++ and had C++ dependencies. But we are working on supporting sonar for swift projects. You can find this issue [here](https://github.com/facebook/Sonar/issues/13)
|
||||||
|
|||||||
214
src/App.js
214
src/App.js
@@ -4,185 +4,41 @@
|
|||||||
* LICENSE file in the root directory of this source tree.
|
* LICENSE file in the root directory of this source tree.
|
||||||
* @format
|
* @format
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
import {FlexColumn, FlexRow} from 'sonar';
|
import {FlexColumn, FlexRow} from 'sonar';
|
||||||
import {connect} from 'react-redux';
|
import {connect} from 'react-redux';
|
||||||
import {toggleBugDialogVisible} from './reducers/application.js';
|
import {toggleBugDialogVisible} from './reducers/application.js';
|
||||||
import WelcomeScreen from './chrome/WelcomeScreen.js';
|
import WelcomeScreen from './chrome/WelcomeScreen.js';
|
||||||
import SonarTitleBar from './chrome/SonarTitleBar.js';
|
import SonarTitleBar from './chrome/SonarTitleBar.js';
|
||||||
import BaseDevice from './devices/BaseDevice.js';
|
|
||||||
import MainSidebar from './chrome/MainSidebar.js';
|
import MainSidebar from './chrome/MainSidebar.js';
|
||||||
import {SonarBasePlugin} from './plugin.js';
|
|
||||||
import Server from './server.js';
|
|
||||||
import Client from './Client.js';
|
|
||||||
import React from 'react';
|
|
||||||
import BugReporter from './fb-stubs/BugReporter.js';
|
|
||||||
import BugReporterDialog from './chrome/BugReporterDialog.js';
|
import BugReporterDialog from './chrome/BugReporterDialog.js';
|
||||||
import ErrorBar from './chrome/ErrorBar.js';
|
import ErrorBar from './chrome/ErrorBar.js';
|
||||||
import Logger from './fb-stubs/Logger.js';
|
|
||||||
import PluginContainer from './PluginContainer.js';
|
import PluginContainer from './PluginContainer.js';
|
||||||
import PluginManager from './chrome/PluginManager.js';
|
import PluginManager from './chrome/PluginManager.js';
|
||||||
const electron = require('electron');
|
|
||||||
const yargs = require('yargs');
|
|
||||||
|
|
||||||
export type {Client};
|
import type Logger from './fb-stubs/Logger.js';
|
||||||
|
import type BugReporter from './fb-stubs/BugReporter.js';
|
||||||
export type StatePluginInfo = {
|
|
||||||
plugin: ?SonarBasePlugin<>,
|
|
||||||
state: Object,
|
|
||||||
};
|
|
||||||
|
|
||||||
export type StateClientPlugins = {
|
|
||||||
[pluginKey: string]: StatePluginInfo,
|
|
||||||
};
|
|
||||||
|
|
||||||
export type StatePlugins = {
|
|
||||||
[appKey: string]: StateClientPlugins,
|
|
||||||
};
|
|
||||||
|
|
||||||
export type State = {
|
|
||||||
activePluginKey: ?string,
|
|
||||||
activeAppKey: ?string,
|
|
||||||
plugins: StatePlugins,
|
|
||||||
error: ?string,
|
|
||||||
};
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
devices: Array<BaseDevice>,
|
logger: Logger,
|
||||||
|
bugReporter: BugReporter,
|
||||||
leftSidebarVisible: boolean,
|
leftSidebarVisible: boolean,
|
||||||
bugDialogVisible: boolean,
|
bugDialogVisible: boolean,
|
||||||
pluginManagerVisible: boolean,
|
pluginManagerVisible: boolean,
|
||||||
selectedDeviceIndex: number,
|
selectedDeviceIndex: number,
|
||||||
selectedApp: ?string,
|
error: ?string,
|
||||||
toggleBugDialogVisible: (visible?: boolean) => void,
|
toggleBugDialogVisible: (visible?: boolean) => void,
|
||||||
};
|
};
|
||||||
|
|
||||||
export class App extends React.Component<Props, State> {
|
export class App extends React.Component<Props> {
|
||||||
constructor() {
|
constructor(props: Props) {
|
||||||
performance.mark('init');
|
performance.mark('init');
|
||||||
super();
|
super(props);
|
||||||
this.initTracking();
|
|
||||||
|
|
||||||
setupEnvironment();
|
|
||||||
this.logger = new Logger();
|
|
||||||
replaceGlobalConsole(this.logger);
|
|
||||||
this.server = this.initServer();
|
|
||||||
|
|
||||||
this.state = {
|
|
||||||
activeAppKey: null,
|
|
||||||
activePluginKey: null,
|
|
||||||
error: null,
|
|
||||||
devices: {},
|
|
||||||
plugins: {},
|
|
||||||
};
|
|
||||||
|
|
||||||
this.bugReporter = new BugReporter(this.logger);
|
|
||||||
this.commandLineArgs = yargs.parse(electron.remote.process.argv);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
server: Server;
|
|
||||||
bugReporter: BugReporter;
|
|
||||||
logger: Logger;
|
|
||||||
commandLineArgs: Object;
|
|
||||||
_hasActivatedPreferredPlugin: boolean = false;
|
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
this.logger.trackTimeSince('init');
|
this.props.logger.trackTimeSince('init');
|
||||||
|
|
||||||
// close socket before reloading
|
|
||||||
window.addEventListener('beforeunload', () => {
|
|
||||||
this.server.close();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
toJSON() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
initServer(): Server {
|
|
||||||
const server = new Server(this);
|
|
||||||
server.addListener('new-client', client => {
|
|
||||||
client.addListener('close', () => {
|
|
||||||
this.setState(state => {
|
|
||||||
this.forceUpdate();
|
|
||||||
// TODO:
|
|
||||||
//reducers.TeardownClient(this, state, {appKey: client.id}),
|
|
||||||
});
|
|
||||||
if (this.state.activeAppKey === client.id) {
|
|
||||||
this.forceUpdate();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
client.addListener('plugins-change', () => {
|
|
||||||
this.forceUpdate();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
server.addListener('clients-change', () => {
|
|
||||||
this.forceUpdate();
|
|
||||||
});
|
|
||||||
|
|
||||||
server.addListener('error', err => {
|
|
||||||
if (err.code === 'EADDRINUSE') {
|
|
||||||
this.setState({
|
|
||||||
error:
|
|
||||||
"Couldn't start websocket server. " +
|
|
||||||
'Looks like you have multiple copies of Sonar running.',
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
// unknown error
|
|
||||||
this.setState({
|
|
||||||
error: err.message,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return server;
|
|
||||||
}
|
|
||||||
|
|
||||||
initTracking = () => {
|
|
||||||
electron.ipcRenderer.on('trackUsage', () => {
|
|
||||||
// check if there's a plugin currently active
|
|
||||||
const {activeAppKey, activePluginKey} = this.state;
|
|
||||||
if (activeAppKey == null || activePluginKey == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// app plugins
|
|
||||||
const client = this.getClient(activeAppKey);
|
|
||||||
if (client) {
|
|
||||||
this.logger.track('usage', 'ping', {
|
|
||||||
app: client.query.app,
|
|
||||||
device: client.query.device,
|
|
||||||
os: client.query.os,
|
|
||||||
plugin: activePluginKey,
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// device plugins
|
|
||||||
const device: ?BaseDevice = this.getDevice(activeAppKey);
|
|
||||||
if (device) {
|
|
||||||
this.logger.track('usage', 'ping', {
|
|
||||||
os: device.os,
|
|
||||||
plugin: activePluginKey,
|
|
||||||
device: device.title,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
getDevice = (id: string): ?BaseDevice =>
|
|
||||||
this.props.devices.find((device: BaseDevice) => device.serial === id);
|
|
||||||
|
|
||||||
getClient(appKey: ?string): ?Client {
|
|
||||||
if (appKey == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const info = this.server.connections.get(appKey);
|
|
||||||
if (info != null) {
|
|
||||||
return info.client;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
@@ -191,30 +47,21 @@ export class App extends React.Component<Props, State> {
|
|||||||
<SonarTitleBar />
|
<SonarTitleBar />
|
||||||
{this.props.bugDialogVisible && (
|
{this.props.bugDialogVisible && (
|
||||||
<BugReporterDialog
|
<BugReporterDialog
|
||||||
bugReporter={this.bugReporter}
|
bugReporter={this.props.bugReporter}
|
||||||
close={() => this.props.toggleBugDialogVisible(false)}
|
close={() => this.props.toggleBugDialogVisible(false)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{this.props.selectedDeviceIndex > -1 ? (
|
{this.props.selectedDeviceIndex > -1 ? (
|
||||||
<FlexRow fill={true}>
|
<FlexRow fill={true}>
|
||||||
{this.props.leftSidebarVisible && (
|
{this.props.leftSidebarVisible && <MainSidebar />}
|
||||||
<MainSidebar
|
<PluginContainer logger={this.props.logger} />
|
||||||
clients={Array.from(this.server.connections.values()).map(
|
|
||||||
({client}) => client,
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
<PluginContainer
|
|
||||||
logger={this.logger}
|
|
||||||
client={this.getClient(this.props.selectedApp)}
|
|
||||||
/>
|
|
||||||
</FlexRow>
|
</FlexRow>
|
||||||
) : this.props.pluginManagerVisible ? (
|
) : this.props.pluginManagerVisible ? (
|
||||||
<PluginManager />
|
<PluginManager />
|
||||||
) : (
|
) : (
|
||||||
<WelcomeScreen />
|
<WelcomeScreen />
|
||||||
)}
|
)}
|
||||||
<ErrorBar text={this.state.error} />
|
<ErrorBar text={this.props.error} />
|
||||||
</FlexColumn>
|
</FlexColumn>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -223,39 +70,14 @@ export class App extends React.Component<Props, State> {
|
|||||||
export default connect(
|
export default connect(
|
||||||
({
|
({
|
||||||
application: {pluginManagerVisible, bugDialogVisible, leftSidebarVisible},
|
application: {pluginManagerVisible, bugDialogVisible, leftSidebarVisible},
|
||||||
connections: {devices, selectedDeviceIndex, selectedApp},
|
connections: {selectedDeviceIndex},
|
||||||
|
server: {error},
|
||||||
}) => ({
|
}) => ({
|
||||||
pluginManagerVisible,
|
pluginManagerVisible,
|
||||||
bugDialogVisible,
|
bugDialogVisible,
|
||||||
leftSidebarVisible,
|
leftSidebarVisible,
|
||||||
devices,
|
|
||||||
selectedDeviceIndex,
|
selectedDeviceIndex,
|
||||||
selectedApp,
|
error,
|
||||||
}),
|
}),
|
||||||
{toggleBugDialogVisible},
|
{toggleBugDialogVisible},
|
||||||
)(App);
|
)(App);
|
||||||
|
|
||||||
function replaceGlobalConsole(logger: Logger) {
|
|
||||||
const loggerMethods = {
|
|
||||||
log: logger.info,
|
|
||||||
warn: logger.warn,
|
|
||||||
error: logger.error,
|
|
||||||
};
|
|
||||||
const consoleHandler = {
|
|
||||||
get: function(obj, prop) {
|
|
||||||
return prop in loggerMethods
|
|
||||||
? args => {
|
|
||||||
obj[prop] && obj[prop](args);
|
|
||||||
return loggerMethods[prop].bind(logger)(args);
|
|
||||||
}
|
|
||||||
: obj[prop];
|
|
||||||
},
|
|
||||||
};
|
|
||||||
window.console = new Proxy(console, consoleHandler);
|
|
||||||
}
|
|
||||||
|
|
||||||
function setupEnvironment() {
|
|
||||||
if (!process.env.ANDROID_HOME) {
|
|
||||||
process.env.ANDROID_HOME = '/opt/android_sdk';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -7,7 +7,8 @@
|
|||||||
|
|
||||||
import type {SonarPlugin} from './plugin.js';
|
import type {SonarPlugin} from './plugin.js';
|
||||||
import type {App} from './App.js';
|
import type {App} from './App.js';
|
||||||
import type BaseDevice from './devices/BaseDevice.js';
|
import type Logger from './fb-stubs/Logger.js';
|
||||||
|
|
||||||
import plugins from './plugins/index.js';
|
import plugins from './plugins/index.js';
|
||||||
import {ReactiveSocket, PartialResponder} from 'rsocket-core';
|
import {ReactiveSocket, PartialResponder} from 'rsocket-core';
|
||||||
|
|
||||||
@@ -26,7 +27,12 @@ export type ClientQuery = {|
|
|||||||
type RequestMetadata = {method: string, id: number, params: ?Object};
|
type RequestMetadata = {method: string, id: number, params: ?Object};
|
||||||
|
|
||||||
export default class Client extends EventEmitter {
|
export default class Client extends EventEmitter {
|
||||||
constructor(app: App, id: string, query: ClientQuery, conn: ReactiveSocket) {
|
constructor(
|
||||||
|
id: string,
|
||||||
|
query: ClientQuery,
|
||||||
|
conn: ReactiveSocket,
|
||||||
|
logger: Logger,
|
||||||
|
) {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
this.connected = true;
|
this.connected = true;
|
||||||
@@ -35,7 +41,7 @@ export default class Client extends EventEmitter {
|
|||||||
this.id = id;
|
this.id = id;
|
||||||
this.query = query;
|
this.query = query;
|
||||||
this.messageIdCounter = 0;
|
this.messageIdCounter = 0;
|
||||||
this.app = app;
|
this.logger = logger;
|
||||||
|
|
||||||
this.broadcastCallbacks = new Map();
|
this.broadcastCallbacks = new Map();
|
||||||
this.requestCallbacks = new Map();
|
this.requestCallbacks = new Map();
|
||||||
@@ -82,16 +88,6 @@ export default class Client extends EventEmitter {
|
|||||||
|},
|
|},
|
||||||
>;
|
>;
|
||||||
|
|
||||||
getDevice(): ?BaseDevice {
|
|
||||||
const {device_id} = this.query;
|
|
||||||
|
|
||||||
if (device_id == null) {
|
|
||||||
return null;
|
|
||||||
} else {
|
|
||||||
return this.app.getDevice(device_id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
supportsPlugin(Plugin: Class<SonarPlugin<>>): boolean {
|
supportsPlugin(Plugin: Class<SonarPlugin<>>): boolean {
|
||||||
return this.plugins.includes(Plugin.id);
|
return this.plugins.includes(Plugin.id);
|
||||||
}
|
}
|
||||||
@@ -193,7 +189,7 @@ export default class Client extends EventEmitter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
toJSON() {
|
toJSON() {
|
||||||
return null;
|
return `<Client#${this.id}>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
subscribe(
|
subscribe(
|
||||||
@@ -257,7 +253,7 @@ export default class Client extends EventEmitter {
|
|||||||
finishTimingRequestResponse(data: RequestMetadata) {
|
finishTimingRequestResponse(data: RequestMetadata) {
|
||||||
const mark = this.getPerformanceMark(data);
|
const mark = this.getPerformanceMark(data);
|
||||||
const logEventName = this.getLogEventName(data);
|
const logEventName = this.getLogEventName(data);
|
||||||
this.app.logger.trackTimeSince(mark, logEventName);
|
this.logger.trackTimeSince(mark, logEventName);
|
||||||
}
|
}
|
||||||
|
|
||||||
getPerformanceMark(data: RequestMetadata): string {
|
getPerformanceMark(data: RequestMetadata): string {
|
||||||
|
|||||||
@@ -36,8 +36,9 @@ type Props = {
|
|||||||
logger: LogManager,
|
logger: LogManager,
|
||||||
selectedDeviceIndex: number,
|
selectedDeviceIndex: number,
|
||||||
selectedPlugin: ?string,
|
selectedPlugin: ?string,
|
||||||
|
selectedApp: ?string,
|
||||||
pluginStates: Object,
|
pluginStates: Object,
|
||||||
client: ?Client,
|
clients: Array<Client>,
|
||||||
devices: Array<BaseDevice>,
|
devices: Array<BaseDevice>,
|
||||||
setPluginState: (payload: {
|
setPluginState: (payload: {
|
||||||
pluginKey: string,
|
pluginKey: string,
|
||||||
@@ -101,13 +102,15 @@ class PluginContainer extends Component<Props, State> {
|
|||||||
if (activePlugin) {
|
if (activePlugin) {
|
||||||
pluginKey = `${device.serial}#${activePlugin.id}`;
|
pluginKey = `${device.serial}#${activePlugin.id}`;
|
||||||
} else {
|
} else {
|
||||||
|
target = props.clients.find(
|
||||||
|
(client: Client) => client.id === props.selectedApp,
|
||||||
|
);
|
||||||
activePlugin = plugins.find(
|
activePlugin = plugins.find(
|
||||||
(p: Class<SonarPlugin<>>) => p.id === props.selectedPlugin,
|
(p: Class<SonarPlugin<>>) => p.id === props.selectedPlugin,
|
||||||
);
|
);
|
||||||
if (!activePlugin || !props.client) {
|
if (!activePlugin || !target) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
target = props.client;
|
|
||||||
pluginKey = `${target.id}#${activePlugin.id}`;
|
pluginKey = `${target.id}#${activePlugin.id}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -161,13 +164,16 @@ class PluginContainer extends Component<Props, State> {
|
|||||||
export default connect(
|
export default connect(
|
||||||
({
|
({
|
||||||
application: {rightSidebarVisible, rightSidebarAvailable},
|
application: {rightSidebarVisible, rightSidebarAvailable},
|
||||||
connections: {selectedPlugin, devices, selectedDeviceIndex},
|
connections: {selectedPlugin, devices, selectedDeviceIndex, selectedApp},
|
||||||
pluginStates,
|
pluginStates,
|
||||||
|
server: {clients},
|
||||||
}) => ({
|
}) => ({
|
||||||
selectedPlugin,
|
selectedPlugin,
|
||||||
devices,
|
devices,
|
||||||
selectedDeviceIndex,
|
selectedDeviceIndex,
|
||||||
pluginStates,
|
pluginStates,
|
||||||
|
selectedApp,
|
||||||
|
clients,
|
||||||
}),
|
}),
|
||||||
{
|
{
|
||||||
setPluginState,
|
setPluginState,
|
||||||
|
|||||||
@@ -221,11 +221,13 @@ class MainSidebar extends Component<MainSidebarProps> {
|
|||||||
export default connect(
|
export default connect(
|
||||||
({
|
({
|
||||||
connections: {devices, selectedDeviceIndex, selectedPlugin, selectedApp},
|
connections: {devices, selectedDeviceIndex, selectedPlugin, selectedApp},
|
||||||
|
server: {clients},
|
||||||
}) => ({
|
}) => ({
|
||||||
devices,
|
devices,
|
||||||
selectedDeviceIndex,
|
selectedDeviceIndex,
|
||||||
selectedPlugin,
|
selectedPlugin,
|
||||||
selectedApp,
|
selectedApp,
|
||||||
|
clients,
|
||||||
}),
|
}),
|
||||||
{
|
{
|
||||||
selectPlugin,
|
selectPlugin,
|
||||||
|
|||||||
@@ -554,6 +554,7 @@ export default class LogTable extends SonarDevicePlugin<LogsState> {
|
|||||||
defaultFilters={DEFAULT_FILTERS}
|
defaultFilters={DEFAULT_FILTERS}
|
||||||
zebra={false}
|
zebra={false}
|
||||||
actions={<Button onClick={this.clearLogs}>Clear Logs</Button>}
|
actions={<Button onClick={this.clearLogs}>Clear Logs</Button>}
|
||||||
|
stickyBottom={true}
|
||||||
/>
|
/>
|
||||||
</LogTable.ContextMenu>
|
</LogTable.ContextMenu>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import AndroidDevice from '../devices/AndroidDevice';
|
|||||||
import child_process from 'child_process';
|
import child_process from 'child_process';
|
||||||
import type {Store} from '../reducers/index.js';
|
import type {Store} from '../reducers/index.js';
|
||||||
import type BaseDevice from '../devices/BaseDevice';
|
import type BaseDevice from '../devices/BaseDevice';
|
||||||
|
import type Logger from '../fb-stubs/Logger.js';
|
||||||
const adb = require('adbkit-fb');
|
const adb = require('adbkit-fb');
|
||||||
|
|
||||||
function createDecive(client, device): Promise<AndroidDevice> {
|
function createDecive(client, device): Promise<AndroidDevice> {
|
||||||
@@ -47,7 +48,7 @@ function getRunningEmulatorName(id: string): Promise<?string> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export default (store: Store) => {
|
export default (store: Store, logger: Logger) => {
|
||||||
const client = adb.createClient();
|
const client = adb.createClient();
|
||||||
|
|
||||||
// get emulators
|
// get emulators
|
||||||
|
|||||||
@@ -7,8 +7,9 @@
|
|||||||
|
|
||||||
import {remote} from 'electron';
|
import {remote} from 'electron';
|
||||||
import type {Store} from '../reducers/index.js';
|
import type {Store} from '../reducers/index.js';
|
||||||
|
import type Logger from '../fb-stubs/Logger.js';
|
||||||
|
|
||||||
export default (store: Store) => {
|
export default (store: Store, logger: Logger) => {
|
||||||
const currentWindow = remote.getCurrentWindow();
|
const currentWindow = remote.getCurrentWindow();
|
||||||
currentWindow.on('focus', () =>
|
currentWindow.on('focus', () =>
|
||||||
store.dispatch({
|
store.dispatch({
|
||||||
|
|||||||
@@ -7,6 +7,8 @@
|
|||||||
|
|
||||||
import type {ChildProcess} from 'child_process';
|
import type {ChildProcess} from 'child_process';
|
||||||
import type {Store} from '../reducers/index.js';
|
import type {Store} from '../reducers/index.js';
|
||||||
|
import type Logger from '../fb-stubs/Logger.js';
|
||||||
|
|
||||||
import child_process from 'child_process';
|
import child_process from 'child_process';
|
||||||
import IOSDevice from '../devices/IOSDevice';
|
import IOSDevice from '../devices/IOSDevice';
|
||||||
|
|
||||||
@@ -49,7 +51,7 @@ function querySimulatorDevices(): Promise<IOSDeviceMap> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export default (store: Store) => {
|
export default (store: Store, logger: Logger) => {
|
||||||
// monitoring iOS devices only available on MacOS.
|
// monitoring iOS devices only available on MacOS.
|
||||||
if (process.platform !== 'darwin') {
|
if (process.platform !== 'darwin') {
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -8,7 +8,13 @@
|
|||||||
import androidDevice from './androidDevice';
|
import androidDevice from './androidDevice';
|
||||||
import iOSDevice from './iOSDevice';
|
import iOSDevice from './iOSDevice';
|
||||||
import application from './application';
|
import application from './application';
|
||||||
|
import tracking from './tracking';
|
||||||
|
import server from './server';
|
||||||
|
|
||||||
|
import type Logger from '../fb-stubs/Logger.js';
|
||||||
import type {Store} from '../reducers/index.js';
|
import type {Store} from '../reducers/index.js';
|
||||||
|
|
||||||
export default (store: Store) =>
|
export default (store: Store, logger: Logger) =>
|
||||||
[application, androidDevice, iOSDevice].forEach(fn => fn(store));
|
[application, androidDevice, iOSDevice, tracking, server].forEach(fn =>
|
||||||
|
fn(store, logger),
|
||||||
|
);
|
||||||
|
|||||||
44
src/dispatcher/server.js
Normal file
44
src/dispatcher/server.js
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
/**
|
||||||
|
* Copyright 2018-present Facebook.
|
||||||
|
* This source code is licensed under the MIT license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
* @format
|
||||||
|
*/
|
||||||
|
|
||||||
|
import Server from '../server.js';
|
||||||
|
|
||||||
|
import type {Store} from '../reducers/index.js';
|
||||||
|
import type Logger from '../fb-stubs/Logger.js';
|
||||||
|
|
||||||
|
export default (store: Store, logger: Logger) => {
|
||||||
|
const server = new Server(logger);
|
||||||
|
server.addListener('new-client', (client: Client) => {
|
||||||
|
store.dispatch({
|
||||||
|
type: 'NEW_CLIENT',
|
||||||
|
payload: client,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
server.addListener('removed-client', (id: string) => {
|
||||||
|
store.dispatch({
|
||||||
|
type: 'CLIENT_REMOVED',
|
||||||
|
payload: id,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
server.addListener('error', err => {
|
||||||
|
const payload: string =
|
||||||
|
err.code === 'EADDRINUSE'
|
||||||
|
? "Couldn't start websocket server. Looks like you have multiple copies of Sonar running."
|
||||||
|
: err.message || 'Unknown error';
|
||||||
|
|
||||||
|
store.dispatch({
|
||||||
|
type: 'SERVER_ERROR',
|
||||||
|
payload,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
window.addEventListener('beforeunload', () => {
|
||||||
|
server.close();
|
||||||
|
});
|
||||||
|
};
|
||||||
44
src/dispatcher/tracking.js
Normal file
44
src/dispatcher/tracking.js
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
/**
|
||||||
|
* Copyright 2018-present Facebook.
|
||||||
|
* This source code is licensed under the MIT license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
* @format
|
||||||
|
*/
|
||||||
|
|
||||||
|
import {ipcRenderer} from 'electron';
|
||||||
|
|
||||||
|
import type BaseDevice from '../devices/BaseDevice.js';
|
||||||
|
import type {Store} from '../reducers/index.js';
|
||||||
|
import type Logger from '../fb-stubs/Logger.js';
|
||||||
|
|
||||||
|
export default (store: Store, logger: Logger) => {
|
||||||
|
ipcRenderer.on('trackUsage', () => {
|
||||||
|
const {
|
||||||
|
devices,
|
||||||
|
selectedDeviceIndex,
|
||||||
|
selectedPlugin,
|
||||||
|
selectedApp,
|
||||||
|
} = store.getState().connections;
|
||||||
|
|
||||||
|
const device: ?BaseDevice =
|
||||||
|
selectedDeviceIndex > -1 ? devices[selectedDeviceIndex] : null;
|
||||||
|
console.log(1, 2, 3);
|
||||||
|
if (!device || !selectedPlugin) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (selectedApp) {
|
||||||
|
logger.track('usage', 'ping', {
|
||||||
|
app: selectedApp,
|
||||||
|
device,
|
||||||
|
os: device.os,
|
||||||
|
plugin: selectedPlugin,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
logger.track('usage', 'ping', {
|
||||||
|
os: device.os,
|
||||||
|
plugin: selectedPlugin,
|
||||||
|
device: device.title,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
@@ -10,7 +10,9 @@ import ReactDOM from 'react-dom';
|
|||||||
import {ContextMenuProvider} from 'sonar';
|
import {ContextMenuProvider} from 'sonar';
|
||||||
import {precachedIcons} from './utils/icons.js';
|
import {precachedIcons} from './utils/icons.js';
|
||||||
import GK from './fb-stubs/GK.js';
|
import GK from './fb-stubs/GK.js';
|
||||||
|
import Logger from './fb-stubs/Logger.js';
|
||||||
import App from './App.js';
|
import App from './App.js';
|
||||||
|
import BugReporter from './fb-stubs/BugReporter.js';
|
||||||
import {createStore} from 'redux';
|
import {createStore} from 'redux';
|
||||||
import reducers from './reducers/index.js';
|
import reducers from './reducers/index.js';
|
||||||
import dispatcher from './dispatcher/index.js';
|
import dispatcher from './dispatcher/index.js';
|
||||||
@@ -22,15 +24,16 @@ const store = createStore(
|
|||||||
window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__(),
|
window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__(),
|
||||||
);
|
);
|
||||||
|
|
||||||
dispatcher(store);
|
const logger = new Logger();
|
||||||
|
const bugReporter = new BugReporter(logger);
|
||||||
|
dispatcher(store, logger);
|
||||||
GK.init();
|
GK.init();
|
||||||
setupMenuBar();
|
setupMenuBar();
|
||||||
|
|
||||||
const AppFrame = () => (
|
const AppFrame = () => (
|
||||||
<ContextMenuProvider>
|
<ContextMenuProvider>
|
||||||
<Provider store={store}>
|
<Provider store={store}>
|
||||||
<App />
|
<App logger={logger} bugReporter={bugReporter} />
|
||||||
</Provider>
|
</Provider>
|
||||||
</ContextMenuProvider>
|
</ContextMenuProvider>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
import {combineReducers} from 'redux';
|
import {combineReducers} from 'redux';
|
||||||
import application from './application.js';
|
import application from './application.js';
|
||||||
import connections from './connections.js';
|
import connections from './connections.js';
|
||||||
|
import server from './server.js';
|
||||||
import pluginStates from './pluginStates.js';
|
import pluginStates from './pluginStates.js';
|
||||||
import type {
|
import type {
|
||||||
State as ApplicationState,
|
State as ApplicationState,
|
||||||
@@ -21,6 +22,7 @@ import type {
|
|||||||
State as PluginsState,
|
State as PluginsState,
|
||||||
Action as PluginsAction,
|
Action as PluginsAction,
|
||||||
} from './pluginStates.js';
|
} from './pluginStates.js';
|
||||||
|
import type {State as ServerState, Action as ServerAction} from './server.js';
|
||||||
import type {Store as ReduxStore} from 'redux';
|
import type {Store as ReduxStore} from 'redux';
|
||||||
|
|
||||||
export type Store = ReduxStore<
|
export type Store = ReduxStore<
|
||||||
@@ -28,8 +30,14 @@ export type Store = ReduxStore<
|
|||||||
application: ApplicationState,
|
application: ApplicationState,
|
||||||
connections: DevicesState,
|
connections: DevicesState,
|
||||||
pluginStates: PluginsState,
|
pluginStates: PluginsState,
|
||||||
|
server: ServerState,
|
||||||
},
|
},
|
||||||
ApplicationAction | DevicesAction | PluginsAction,
|
ApplicationAction | DevicesAction | PluginsAction | ServerAction,
|
||||||
>;
|
>;
|
||||||
|
|
||||||
export default combineReducers({application, connections, pluginStates});
|
export default combineReducers({
|
||||||
|
application,
|
||||||
|
connections,
|
||||||
|
pluginStates,
|
||||||
|
server,
|
||||||
|
});
|
||||||
|
|||||||
54
src/reducers/server.js
Normal file
54
src/reducers/server.js
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
/**
|
||||||
|
* Copyright 2018-present Facebook.
|
||||||
|
* This source code is licensed under the MIT license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
* @format
|
||||||
|
*/
|
||||||
|
|
||||||
|
export type State = {
|
||||||
|
error: ?string,
|
||||||
|
clients: Array<Client>,
|
||||||
|
};
|
||||||
|
|
||||||
|
export type Action =
|
||||||
|
| {
|
||||||
|
type: 'SERVER_ERROR',
|
||||||
|
payload: ?string,
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
type: 'NEW_CLIENT',
|
||||||
|
payload: Client,
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
type: 'CLIENT_REMOVED',
|
||||||
|
payload: string,
|
||||||
|
};
|
||||||
|
|
||||||
|
const INITIAL_STATE: State = {
|
||||||
|
error: null,
|
||||||
|
clients: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function reducer(
|
||||||
|
state: State = INITIAL_STATE,
|
||||||
|
action: Action,
|
||||||
|
): State {
|
||||||
|
if (action.type === 'NEW_CLIENT') {
|
||||||
|
const {payload} = action;
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
clients: state.clients.concat(payload),
|
||||||
|
};
|
||||||
|
} else if (action.type === 'CLIENT_REMOVED') {
|
||||||
|
const {payload} = action;
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
clients: state.clients.filter((client: Client) => client.id !== payload),
|
||||||
|
};
|
||||||
|
} else if (action.type === 'SERVER_ERROR') {
|
||||||
|
const {payload} = action;
|
||||||
|
return {...state, error: payload};
|
||||||
|
} else {
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,7 +5,6 @@
|
|||||||
* @format
|
* @format
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type {App} from './App.js';
|
|
||||||
import type {SecureServerConfig} from './utils/CertificateProvider';
|
import type {SecureServerConfig} from './utils/CertificateProvider';
|
||||||
import type Logger from './fb-stubs/Logger';
|
import type Logger from './fb-stubs/Logger';
|
||||||
import type {ClientQuery} from './Client.js';
|
import type {ClientQuery} from './Client.js';
|
||||||
@@ -40,14 +39,14 @@ export default class Server extends EventEmitter {
|
|||||||
insecureServer: RSocketServer;
|
insecureServer: RSocketServer;
|
||||||
certificateProvider: CertificateProvider;
|
certificateProvider: CertificateProvider;
|
||||||
connectionTracker: ConnectionTracker;
|
connectionTracker: ConnectionTracker;
|
||||||
app: App;
|
logger: Logger;
|
||||||
|
|
||||||
constructor(app: App) {
|
constructor(logger: Logger) {
|
||||||
super();
|
super();
|
||||||
this.app = app;
|
this.logger = logger;
|
||||||
this.connections = new Map();
|
this.connections = new Map();
|
||||||
this.certificateProvider = new CertificateProvider(this, app.logger);
|
this.certificateProvider = new CertificateProvider(this, logger);
|
||||||
this.connectionTracker = new ConnectionTracker(app.logger);
|
this.connectionTracker = new ConnectionTracker(logger);
|
||||||
this.init();
|
this.init();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -186,7 +185,7 @@ export default class Server extends EventEmitter {
|
|||||||
const id = `${query.app}-${query.os}-${query.device}`;
|
const id = `${query.app}-${query.os}-${query.device}`;
|
||||||
console.warn(`Device connected: ${id}`, 'connection');
|
console.warn(`Device connected: ${id}`, 'connection');
|
||||||
|
|
||||||
const client = new Client(this.app, id, query, conn);
|
const client = new Client(id, query, conn, this.logger);
|
||||||
|
|
||||||
const info = {
|
const info = {
|
||||||
client,
|
client,
|
||||||
@@ -235,6 +234,7 @@ export default class Server extends EventEmitter {
|
|||||||
info.client.emit('close');
|
info.client.emit('close');
|
||||||
this.connections.delete(id);
|
this.connections.delete(id);
|
||||||
this.emit('clients-change');
|
this.emit('clients-change');
|
||||||
|
this.emit('removed-client', id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,10 @@ const fs = require('fs');
|
|||||||
const compilePlugins = require('./compilePlugins.js');
|
const compilePlugins = require('./compilePlugins.js');
|
||||||
const os = require('os');
|
const os = require('os');
|
||||||
|
|
||||||
|
if (!process.env.ANDROID_HOME) {
|
||||||
|
process.env.ANDROID_HOME = '/opt/android_sdk';
|
||||||
|
}
|
||||||
|
|
||||||
// ensure .sonar folder and config exist
|
// ensure .sonar folder and config exist
|
||||||
const sonarDir = path.join(os.homedir(), '.sonar');
|
const sonarDir = path.join(os.homedir(), '.sonar');
|
||||||
if (!fs.existsSync(sonarDir)) {
|
if (!fs.existsSync(sonarDir)) {
|
||||||
|
|||||||
Reference in New Issue
Block a user