Merge pull request #20 from zalmoxisus/reports
Receive reports from users and get them replicated right in the extension
This commit is contained in:
commit
f3c050ac8e
34
README.md
34
README.md
@ -1,7 +1,7 @@
|
||||
RemoteDev Server
|
||||
================
|
||||
|
||||
Bridge for connecting [remotedev monitor app](https://github.com/zalmoxisus/remotedev-app) with [Remote Redux DevTools](https://github.com/zalmoxisus/remote-redux-devtools) or [RemoteDev](https://github.com/zalmoxisus/remotedev) using a local server. Running a local server is optional, you may use [remotedev.io](https://remotedev.io) server instead, which is by default.
|
||||
Bridge for communicating with an application remotely via [Redux DevTools extension](https://github.com/zalmoxisus/redux-devtools-extension), [Remote Redux DevTools](https://github.com/zalmoxisus/remote-redux-devtools) or [RemoteDev](https://github.com/zalmoxisus/remotedev). Running your server is optional, you can use [remotedev.io](https://remotedev.io) instead.
|
||||
|
||||
### Installation
|
||||
|
||||
@ -19,7 +19,7 @@ npm install --save-dev remotedev-server
|
||||
}
|
||||
```
|
||||
|
||||
So, you can start local server by running `npm run remotedev`.
|
||||
So, you can start remotedev server by running `npm run remotedev`.
|
||||
|
||||
##### Import in your `server.js` script you use for starting a development server:
|
||||
|
||||
@ -36,7 +36,11 @@ So, you can start remotedev server together with your dev server.
|
||||
remotedev --hostname=localhost --port=8000
|
||||
```
|
||||
|
||||
Change `hostname` and `port` to the values you want.
|
||||
### Connection settings
|
||||
|
||||
Set `hostname` and `port` to the values you want. `hostname` by default is `localhost` and `port` is `8000`.
|
||||
|
||||
To use WSS, set `protocol` argument to `https` and provide `key`, `cert` and `passphrase` arguments.
|
||||
|
||||
### Inject to React Native local server
|
||||
|
||||
@ -50,9 +54,7 @@ Change `hostname` and `port` to the values you want.
|
||||
|
||||
The `injectserver` value can be `reactnative` or `macos` ([react-native-macos](https://github.com/ptmt/react-native-macos)), it used `reactnative` by default.
|
||||
|
||||
Then, we can start React Native server and RemoteDev server with one command:
|
||||
|
||||

|
||||
Then, we can start React Native server and RemoteDev server with one command (`npm start`).
|
||||
|
||||
##### Revert the injection
|
||||
|
||||
@ -68,6 +70,8 @@ Or just run `$(npm bin)/remotedev --revert`.
|
||||
|
||||
### Connect from Android device or emulator
|
||||
|
||||
> Note that if you're using `injectserver` argument explained above, this step is not necessary.
|
||||
|
||||
If you're running an Android 5.0+ device connected via USB or an Android emulator, use [adb command line tool](http://developer.android.com/tools/help/adb.html) to setup port forwarding from the device to your computer:
|
||||
|
||||
```
|
||||
@ -76,6 +80,24 @@ adb reverse tcp:8000 tcp:8000
|
||||
|
||||
If you're still use Android 4.0, you should use `10.0.2.2` (Genymotion: `10.0.3.2`) instead of `localhost` in [remote-redux-devtools](https://github.com/zalmoxisus/remote-redux-devtools#storeconfigurestorejs) or [remotedev](https://github.com/zalmoxisus/remotedev#usage).
|
||||
|
||||
### Save reports and logs
|
||||
|
||||
You can store reports via [`redux-remotedev`](https://github.com/zalmoxisus/redux-remotedev) and get them replicated with [Redux DevTools extension](https://github.com/zalmoxisus/redux-devtools-extension) or [Remote Redux DevTools](https://github.com/zalmoxisus/remote-redux-devtools). You can get action history right in the extension just by clicking the link from a report.
|
||||
|
||||
Remotedev server is database agnostic. By default everything is stored in the memory, but you can persist data by specifying one of the jsData adapters above for `adapter` argument. Also you can add an `dbOptions` argument for database configuration. If not provided the default options will be used (for some adapters, like `sql`, it's required). You have to install the required adapter's npm package.
|
||||
|
||||
| Storage | `adapter` | `dbOptions` argument example (optional) | install |
|
||||
|-----------|-----------|------------------------------------------------------------------------------------------------------------------------|------------------------------------------------------|
|
||||
| Firebase | firebase | `{ basePath: 'https://my-app.firebase.io' }` | `npm install --save js-data-firebase` |
|
||||
| HTTP | http | `{ basePath: 'https://my-rest-server/api' }` | `npm install --save js-data-http` |
|
||||
| LevelUp | levelup | `'./db'` (the levelup "db" object will be available at "adapter.db") | `npm install --save js-data-levelup` |
|
||||
| MongoDB | mongodb | `{ name: 'user', idAttribute: '_id', table: 'users' }` | `npm install --save js-data-mongodb` |
|
||||
| MySQL | sql | `{ client: 'mysql', connection: { host: '123.45.67.890', user: 'ubuntu', password: 'welcome1234', database: 'db1' }` | `npm install --save js-data-sql` |
|
||||
| Postgres | sql | `{ client: 'pg', connection: { host: '123.45.67.890', user: 'ubuntu', password: 'welcome1234', database: 'db1' }` | `npm install --save js-data-sql` |
|
||||
| Redis | redis | See the configurable options for [`node_redis`](https://github.com/NodeRedis/node_redis) | `npm install --save js-data-redis` |
|
||||
| RethinkDB | rethinkdb | `{ host: '123.456.68.987', db: 'my_db' }` | `npm install --save rethinkdbdash js-data-rethinkdb` |
|
||||
| SQLite3 | sql | `{ client: 'sqlite3', connection: { host: '123.45.67.890', user: 'ubuntu', password: 'welcome1234', database: 'db1' }` | `npm install --save js-data-sql` |
|
||||
|
||||
### License
|
||||
|
||||
MIT
|
||||
|
@ -4,7 +4,7 @@ var path = require('path');
|
||||
var argv = require('minimist')(process.argv.slice(2));
|
||||
var chalk = require('chalk');
|
||||
var injectServer = require('./injectServer');
|
||||
var getOptions = require('./../lib/getOptions');
|
||||
var getOptions = require('./../lib/options');
|
||||
|
||||
function readFile(filePath) {
|
||||
return fs.readFileSync(path.resolve(process.cwd(), filePath), 'utf-8');
|
||||
|
@ -1,6 +1,6 @@
|
||||
var assign = require('object-assign');
|
||||
var repeat = require('repeat-string');
|
||||
var getOptions = require('./../lib/getOptions');
|
||||
var getOptions = require('./../lib/options');
|
||||
var getPort = require('getport');
|
||||
|
||||
var LOG_LEVEL_NONE = 0;
|
||||
|
5
lib/adapter.js
Normal file
5
lib/adapter.js
Normal file
@ -0,0 +1,5 @@
|
||||
function getAdapter(adapter) {
|
||||
return require('js-data-' + adapter);
|
||||
}
|
||||
|
||||
module.exports = getAdapter;
|
@ -8,6 +8,8 @@ module.exports = function getOptions(argv) {
|
||||
cert: argv.cert || process.env.npm_package_remotedev_cert || null,
|
||||
passphrase: argv.passphrase || process.env.npm_package_remotedev_passphrase || null
|
||||
},
|
||||
adapter: argv.adapter || process.env.npm_package_remotedev_adapter,
|
||||
dbOptions: argv.dbOptions || process.env.npm_package_remotedev_db,
|
||||
logLevel: argv.logLevel || 3
|
||||
};
|
||||
}
|
117
lib/store.js
Normal file
117
lib/store.js
Normal file
@ -0,0 +1,117 @@
|
||||
var uuid = require('node-uuid');
|
||||
var pick = require('lodash/pick');
|
||||
var JSData = require('js-data');
|
||||
var getAdapter = require('./adapter');
|
||||
|
||||
var store;
|
||||
var adapter;
|
||||
var Report;
|
||||
|
||||
var baseFields = ['id', 'title', 'added'];
|
||||
|
||||
function error(msg) {
|
||||
return new Promise(function(resolve, reject) {
|
||||
return resolve({ error: msg });
|
||||
});
|
||||
}
|
||||
|
||||
function map(data, fields) {
|
||||
if (!fields) return data;
|
||||
return data.map(function(r) {
|
||||
return pick(r, fields);
|
||||
});
|
||||
}
|
||||
|
||||
function listEvery(query) {
|
||||
if (!adapter) {
|
||||
return new Promise(function(resolve) {
|
||||
var report = Report.filter(query);
|
||||
return resolve(report);
|
||||
});
|
||||
}
|
||||
return Report.findAll(query);
|
||||
}
|
||||
|
||||
function list(query, fields) {
|
||||
return new Promise(function(resolve) {
|
||||
listEvery(query).then(function(data) {
|
||||
return resolve(map(data, fields || baseFields));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function get(id) {
|
||||
if (!id) return error('No id specified.');
|
||||
|
||||
if (!adapter) {
|
||||
return new Promise(function(resolve) {
|
||||
var report = Report.get(id);
|
||||
return resolve(report);
|
||||
});
|
||||
}
|
||||
return Report.find(id);
|
||||
}
|
||||
|
||||
function add(data) {
|
||||
if (!data.type || !data.payload) {
|
||||
return error('Required parameters aren\'t specified.');
|
||||
}
|
||||
if (data.type !== 'ACTIONS' && data.type !== 'STATE') {
|
||||
return error('Type ' + data.type + ' is not supported yet.');
|
||||
}
|
||||
|
||||
var obj = {
|
||||
id: uuid.v4(),
|
||||
type: data.type,
|
||||
title: data.title || data.exception && data.exception.message || data.action,
|
||||
description: data.description,
|
||||
action: data.action,
|
||||
payload: data.payload,
|
||||
preloadedState: data.preloadedState,
|
||||
screenshot: data.screenshot,
|
||||
version: data.version,
|
||||
appId: data.appId,
|
||||
userAgent: data.userAgent,
|
||||
user: data.user,
|
||||
userId: typeof data.user === 'object' ? data.user.id : data.user,
|
||||
meta: data.meta,
|
||||
exception: data.exception,
|
||||
added: Date.now()
|
||||
};
|
||||
|
||||
if (!adapter) {
|
||||
return new Promise(function(resolve) {
|
||||
var report = Report.inject(obj);
|
||||
return resolve(report);
|
||||
});
|
||||
}
|
||||
return Report.create(obj);
|
||||
}
|
||||
|
||||
function byBaseFields(data) {
|
||||
return pick(data, baseFields);
|
||||
}
|
||||
|
||||
function createStore(options) {
|
||||
var adapterName = options.adapter;
|
||||
store = new JSData.DS();
|
||||
|
||||
if (adapterName) {
|
||||
var DSAdapter = getAdapter(adapterName);
|
||||
adapter = new DSAdapter(options.dbOptions);
|
||||
store.registerAdapter(adapterName, adapter, { default: true });
|
||||
}
|
||||
|
||||
Report = store.defineResource('report');
|
||||
|
||||
return {
|
||||
list: list,
|
||||
get: get,
|
||||
add: add,
|
||||
selectors: {
|
||||
byBaseFields: byBaseFields
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = createStore;
|
@ -2,10 +2,12 @@ var path = require('path');
|
||||
var app = require('express')();
|
||||
var bodyParser = require('body-parser');
|
||||
var cors = require('cors');
|
||||
var createStore = require('./store');
|
||||
|
||||
module.exports.run = function(worker) {
|
||||
var httpServer = worker.httpServer;
|
||||
var scServer = worker.scServer;
|
||||
var store = createStore(worker.options);
|
||||
|
||||
httpServer.on('request', app);
|
||||
|
||||
@ -18,10 +20,28 @@ module.exports.run = function(worker) {
|
||||
|
||||
app.use(cors({ methods: 'POST' }));
|
||||
app.use(bodyParser.json());
|
||||
app.use(bodyParser.urlencoded({ extended: false }));
|
||||
app.post('/', function(req, res) {
|
||||
if (!req.body) return res.status(404).end();
|
||||
scServer.exchange.publish('log', req.body);
|
||||
res.send('OK');
|
||||
switch(req.body.op) {
|
||||
case 'get':
|
||||
store.get(req.body.id).then(function(r) {
|
||||
res.send(r || {});
|
||||
});
|
||||
break;
|
||||
case 'list':
|
||||
store.list(req.body.query, req.body.fields).then(function(r) {
|
||||
res.send(r);
|
||||
});
|
||||
break;
|
||||
default:
|
||||
store.add(req.body).then(function(r) {
|
||||
res.send({ id: r.id, error: r.error });
|
||||
scServer.exchange.publish('report', {
|
||||
type: 'add', data: store.selectors.byBaseFields(r)
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
scServer.addMiddleware(scServer.MIDDLEWARE_EMIT, function (req, next) {
|
||||
@ -35,6 +55,15 @@ module.exports.run = function(worker) {
|
||||
next();
|
||||
});
|
||||
|
||||
scServer.addMiddleware(scServer.MIDDLEWARE_SUBSCRIBE, function (req, next) {
|
||||
next();
|
||||
if (req.channel === 'report') {
|
||||
store.list().then(function(data) {
|
||||
req.socket.emit(req.channel, { type: 'list', data: data });
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
scServer.on('connection', function(socket) {
|
||||
var channelToWatch, channelToEmit;
|
||||
socket.on('login', function (credentials, respond) {
|
||||
@ -48,6 +77,11 @@ module.exports.run = function(worker) {
|
||||
});
|
||||
respond(null, channelToWatch);
|
||||
});
|
||||
socket.on('getReport', function (id, respond) {
|
||||
store.get(id).then(function(data) {
|
||||
respond(null, data);
|
||||
});
|
||||
});
|
||||
socket.on('disconnect', function() {
|
||||
var channel = worker.exchange.channel('sc-' + socket.id);
|
||||
channel.unsubscribe(); channel.destroy();
|
||||
|
@ -32,7 +32,10 @@
|
||||
"ejs": "^2.4.1",
|
||||
"express": "^4.13.3",
|
||||
"getport": "^0.1.0",
|
||||
"js-data": "^2.9.0",
|
||||
"lodash": "^4.15.0",
|
||||
"minimist": "^1.2.0",
|
||||
"node-uuid": "^1.4.0",
|
||||
"object-assign": "^4.0.0",
|
||||
"repeat-string": "^1.5.4",
|
||||
"semver": "^5.3.0",
|
||||
|
Loading…
x
Reference in New Issue
Block a user