mirror of
https://github.com/reduxjs/redux-devtools.git
synced 2024-11-22 17:46:56 +03:00
Merge remotedev-server
This commit is contained in:
parent
9de536fae2
commit
4849081263
21
packages/redux-devtools-cli/LICENSE.md
Normal file
21
packages/redux-devtools-cli/LICENSE.md
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2016 Mihail Diordiev
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
119
packages/redux-devtools-cli/README.md
Normal file
119
packages/redux-devtools-cli/README.md
Normal file
|
@ -0,0 +1,119 @@
|
||||||
|
RemoteDev Server
|
||||||
|
================
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
```
|
||||||
|
npm install --save-dev remotedev-server
|
||||||
|
```
|
||||||
|
|
||||||
|
Also [there's a docker image](https://github.com/jhen0409/docker-remotedev-server) you can use.
|
||||||
|
|
||||||
|
### Usage
|
||||||
|
|
||||||
|
##### Add in your app's `package.json`:
|
||||||
|
|
||||||
|
```
|
||||||
|
"scripts": {
|
||||||
|
"remotedev": "remotedev --hostname=localhost --port=8000"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
So, you can start remotedev server by running `npm run remotedev`.
|
||||||
|
|
||||||
|
##### Import in your `server.js` script you use for starting a development server:
|
||||||
|
|
||||||
|
```js
|
||||||
|
var remotedev = require('remotedev-server');
|
||||||
|
remotedev({ hostname: 'localhost', port: 8000 });
|
||||||
|
```
|
||||||
|
|
||||||
|
So, you can start remotedev server together with your dev server.
|
||||||
|
|
||||||
|
##### Install the package globally (not recommended) just run:
|
||||||
|
|
||||||
|
```
|
||||||
|
remotedev --hostname=localhost --port=8000
|
||||||
|
```
|
||||||
|
|
||||||
|
### 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.
|
||||||
|
|
||||||
|
|
||||||
|
#### Available options
|
||||||
|
|
||||||
|
| Console argument | description | default value |
|
||||||
|
| ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------- |
|
||||||
|
| `--hostname` | hostname | localhost |
|
||||||
|
| `--port` | port | 8000 |
|
||||||
|
| `--protocol` | protocol | http |
|
||||||
|
| `--key` | the key file for [running an https server](https://github.com/SocketCluster/socketcluster#using-over-https) (`--protocol` must be set to 'https') | - |
|
||||||
|
| `--cert` | the cert file for [running an https server](https://github.com/SocketCluster/socketcluster#using-over-https) (`--protocol` must be set to 'https') | - |
|
||||||
|
| `--passphrase` | the key passphrase for [running an https server](https://github.com/SocketCluster/socketcluster#using-over-https) (`--protocol` must be set to 'https') | - |
|
||||||
|
| `--dbOptions` | database configuration, can be whether an object or a path (string) to json configuration file (by default it uses our `./defaultDbOptions.json` file. Set `migrate` key to `true` to use our migrations file. [More details bellow](https://github.com/zalmoxisus/remotedev-server#save-reports-and-logs). | - |
|
||||||
|
| `--logLevel` | the socket server log level - 0=none, 1=error, 2=warn, 3=info | 3 |
|
||||||
|
| `--wsEngine` | the socket server web socket engine - ws or uws (sc-uws) | ws |
|
||||||
|
|
||||||
|
### Inject to React Native local server
|
||||||
|
|
||||||
|
##### Add in your React Native app's `package.json`:
|
||||||
|
|
||||||
|
```
|
||||||
|
"scripts": {
|
||||||
|
"remotedev": "remotedev --hostname=localhost --port=8000 --injectserver=reactnative"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
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 (`npm start`).
|
||||||
|
|
||||||
|
##### Revert the injection
|
||||||
|
|
||||||
|
Add in your React Native app's `package.json`:
|
||||||
|
|
||||||
|
```
|
||||||
|
"scripts": {
|
||||||
|
"remotedev-revert": "remotedev --revert=reactnative"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
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:
|
||||||
|
|
||||||
|
```
|
||||||
|
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. Open `http://localhost:8000/graphiql` (assuming you're using `localhost` as host and `8000`) to explore in GraphQL. Reports are posted to `http://localhost:8000/`. See examples in [tests](https://github.com/zalmoxisus/remotedev-server/blob/937cfa1f0ac9dc12ebf7068eeaa8b03022ec33bc/test/integration.spec.js#L110-L165).
|
||||||
|
|
||||||
|
Remotedev server is database agnostic using `knex` schema. By default everything is stored in the memory using sqlite database. See [`defaultDbOptions.json`](https://github.com/zalmoxisus/remotedev-server/blob/master/defaultDbOptions.json) for example of sqlite. You can replace `"connection": { "filename": ":memory:" },` with your file name (instead of `:memory:`) to persist teh database. Here's an example for PostgreSQL:
|
||||||
|
```
|
||||||
|
{
|
||||||
|
"client": "pg",
|
||||||
|
"connection": { "user": "myuser", "password": "mypassword", "database": "mydb" },
|
||||||
|
"debug": false,
|
||||||
|
"migrate": true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Advanced
|
||||||
|
- [Writing your integration for a native application](https://github.com/zalmoxisus/remotedev-server/blob/master/docs/API/Realtime.md)
|
||||||
|
|
||||||
|
### License
|
||||||
|
|
||||||
|
MIT
|
98
packages/redux-devtools-cli/bin/injectServer.js
Normal file
98
packages/redux-devtools-cli/bin/injectServer.js
Normal file
|
@ -0,0 +1,98 @@
|
||||||
|
var fs = require('fs');
|
||||||
|
var path = require('path');
|
||||||
|
var semver = require('semver');
|
||||||
|
|
||||||
|
var name = 'remotedev-server';
|
||||||
|
var startFlag = '/* ' + name + ' start */';
|
||||||
|
var endFlag = '/* ' + name + ' end */';
|
||||||
|
var serverFlags = {
|
||||||
|
'react-native': {
|
||||||
|
'0.0.1': ' _server(argv, config, resolve, reject);',
|
||||||
|
'0.31.0': " runServer(args, config, () => console.log('\\nReact packager ready.\\n'));",
|
||||||
|
'0.44.0-rc.0': ' runServer(args, config, startedCallback, readyCallback);',
|
||||||
|
'0.46.0-rc.0': ' runServer(runServerArgs, configT, startedCallback, readyCallback);',
|
||||||
|
'0.57.0': ' runServer(args, configT);'
|
||||||
|
},
|
||||||
|
'react-native-desktop': {
|
||||||
|
'0.0.1': ' _server(argv, config, resolve, reject);'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
function getModuleVersion(modulePath) {
|
||||||
|
return JSON.parse(
|
||||||
|
fs.readFileSync(
|
||||||
|
path.join(modulePath, 'package.json'),
|
||||||
|
'utf-8'
|
||||||
|
)
|
||||||
|
).version;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getServerFlag(moduleName, version) {
|
||||||
|
var flags = serverFlags[moduleName || 'react-native'];
|
||||||
|
var versions = Object.keys(flags);
|
||||||
|
var flag;
|
||||||
|
for (var i = 0; i < versions.length; i++) {
|
||||||
|
if (semver.gte(version, versions[i])) {
|
||||||
|
flag = flags[versions[i]];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return flag;
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.dir = 'local-cli/server';
|
||||||
|
exports.file = 'server.js';
|
||||||
|
exports.fullPath = path.join(exports.dir, exports.file);
|
||||||
|
|
||||||
|
exports.inject = function(modulePath, options, moduleName) {
|
||||||
|
var filePath = path.join(modulePath, exports.fullPath);
|
||||||
|
if (!fs.existsSync(filePath)) return false;
|
||||||
|
|
||||||
|
var serverFlag = getServerFlag(
|
||||||
|
moduleName,
|
||||||
|
getModuleVersion(modulePath)
|
||||||
|
);
|
||||||
|
var code = [
|
||||||
|
startFlag,
|
||||||
|
' require("' + name + '")(' + JSON.stringify(options) + ')',
|
||||||
|
' .then(_remotedev =>',
|
||||||
|
' _remotedev.on("ready", () => {',
|
||||||
|
' if (!_remotedev.portAlreadyUsed) console.log("-".repeat(80));',
|
||||||
|
' ' + serverFlag,
|
||||||
|
' })',
|
||||||
|
' );',
|
||||||
|
endFlag,
|
||||||
|
].join('\n');
|
||||||
|
|
||||||
|
var serverCode = fs.readFileSync(filePath, 'utf-8');
|
||||||
|
var start = serverCode.indexOf(startFlag); // already injected ?
|
||||||
|
var end = serverCode.indexOf(endFlag) + endFlag.length;
|
||||||
|
if (start === -1) {
|
||||||
|
start = serverCode.indexOf(serverFlag);
|
||||||
|
end = start + serverFlag.length;
|
||||||
|
}
|
||||||
|
fs.writeFileSync(
|
||||||
|
filePath,
|
||||||
|
serverCode.substr(0, start) + code + serverCode.substr(end, serverCode.length)
|
||||||
|
);
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.revert = function(modulePath, moduleName) {
|
||||||
|
var filePath = path.join(modulePath, exports.fullPath);
|
||||||
|
if (!fs.existsSync(filePath)) return false;
|
||||||
|
|
||||||
|
var serverFlag = getServerFlag(
|
||||||
|
moduleName,
|
||||||
|
getModuleVersion(modulePath)
|
||||||
|
);
|
||||||
|
var serverCode = fs.readFileSync(filePath, 'utf-8');
|
||||||
|
var start = serverCode.indexOf(startFlag); // already injected ?
|
||||||
|
var end = serverCode.indexOf(endFlag) + endFlag.length;
|
||||||
|
if (start !== -1) {
|
||||||
|
fs.writeFileSync(
|
||||||
|
filePath,
|
||||||
|
serverCode.substr(0, start) + serverFlag + serverCode.substr(end, serverCode.length)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
};
|
73
packages/redux-devtools-cli/bin/remotedev.js
Executable file
73
packages/redux-devtools-cli/bin/remotedev.js
Executable file
|
@ -0,0 +1,73 @@
|
||||||
|
#! /usr/bin/env node
|
||||||
|
var fs = require('fs');
|
||||||
|
var path = require('path');
|
||||||
|
var argv = require('minimist')(process.argv.slice(2));
|
||||||
|
var chalk = require('chalk');
|
||||||
|
var injectServer = require('./injectServer');
|
||||||
|
var getOptions = require('./../src/options');
|
||||||
|
|
||||||
|
function readFile(filePath) {
|
||||||
|
return fs.readFileSync(path.resolve(process.cwd(), filePath), 'utf-8');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (argv.protocol === 'https') {
|
||||||
|
argv.key = argv.key ? readFile(argv.key) : null;
|
||||||
|
argv.cert = argv.cert ? readFile(argv.cert) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function log(pass, msg) {
|
||||||
|
var prefix = pass ? chalk.green.bgBlack('PASS') : chalk.red.bgBlack('FAIL');
|
||||||
|
var color = pass ? chalk.blue : chalk.red;
|
||||||
|
console.log(prefix, color(msg));
|
||||||
|
}
|
||||||
|
|
||||||
|
function getModuleName(type) {
|
||||||
|
switch (type) {
|
||||||
|
case 'macos':
|
||||||
|
return 'react-native-macos';
|
||||||
|
// react-native-macos is renamed from react-native-desktop
|
||||||
|
case 'desktop':
|
||||||
|
return 'react-native-desktop';
|
||||||
|
case 'reactnative':
|
||||||
|
default:
|
||||||
|
return 'react-native';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getModulePath(moduleName) {
|
||||||
|
return path.join(process.cwd(), 'node_modules', moduleName);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getModule(type) {
|
||||||
|
var moduleName = getModuleName(type);
|
||||||
|
var modulePath = getModulePath(moduleName);
|
||||||
|
if (type === 'desktop' && !fs.existsSync(modulePath)) {
|
||||||
|
moduleName = getModuleName('macos');
|
||||||
|
modulePath = getModulePath(moduleName);
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
name: moduleName,
|
||||||
|
path: modulePath
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (argv.revert) {
|
||||||
|
var module = getModule(argv.revert);
|
||||||
|
var pass = injectServer.revert(module.path, module.name);
|
||||||
|
var msg = 'Revert injection of RemoteDev server from React Native local server';
|
||||||
|
log(pass, msg + (!pass ? ', the file `' + path.join(module.name, injectServer.fullPath) + '` not found.' : '.'));
|
||||||
|
|
||||||
|
process.exit(pass ? 0 : 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (argv.injectserver) {
|
||||||
|
var options = getOptions(argv);
|
||||||
|
var module = getModule(argv.injectserver);
|
||||||
|
var pass = injectServer.inject(module.path, options, module.name);
|
||||||
|
var msg = 'Inject RemoteDev server into React Native local server';
|
||||||
|
log(pass, msg + (pass ? '.' : ', the file `' + path.join(module.name, injectServer.fullPath) + '` not found.'));
|
||||||
|
|
||||||
|
process.exit(pass ? 0 : 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
require('../index')(argv);
|
13
packages/redux-devtools-cli/defaultDbOptions.json
Normal file
13
packages/redux-devtools-cli/defaultDbOptions.json
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
{
|
||||||
|
"client": "sqlite3",
|
||||||
|
"connection": { "filename": ":memory:" },
|
||||||
|
"pool": {
|
||||||
|
"min": 1,
|
||||||
|
"max": 1,
|
||||||
|
"idleTimeoutMillis": 360000000,
|
||||||
|
"disposeTimeout": 360000000
|
||||||
|
},
|
||||||
|
"useNullAsDefault": true,
|
||||||
|
"debug": false,
|
||||||
|
"migrate": true
|
||||||
|
}
|
40
packages/redux-devtools-cli/index.js
Normal file
40
packages/redux-devtools-cli/index.js
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
var getPort = require('getport');
|
||||||
|
var SocketCluster = require('socketcluster');
|
||||||
|
var getOptions = require('./src/options');
|
||||||
|
|
||||||
|
var LOG_LEVEL_NONE = 0;
|
||||||
|
var LOG_LEVEL_ERROR = 1;
|
||||||
|
var LOG_LEVEL_WARN = 2;
|
||||||
|
var LOG_LEVEL_INFO = 3;
|
||||||
|
|
||||||
|
module.exports = function(argv) {
|
||||||
|
var options = Object.assign(getOptions(argv), {
|
||||||
|
workerController: __dirname + '/src/worker.js',
|
||||||
|
allowClientPublish: false
|
||||||
|
});
|
||||||
|
var port = options.port;
|
||||||
|
var logLevel = options.logLevel === undefined ? LOG_LEVEL_INFO : options.logLevel;
|
||||||
|
return new Promise(function(resolve) {
|
||||||
|
// Check port already used
|
||||||
|
getPort(port, function(err, p) {
|
||||||
|
if (err) {
|
||||||
|
if (logLevel >= LOG_LEVEL_ERROR) {
|
||||||
|
console.error(err);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (port !== p) {
|
||||||
|
if (logLevel >= LOG_LEVEL_WARN) {
|
||||||
|
console.log('[RemoteDev] Server port ' + port + ' is already used.');
|
||||||
|
}
|
||||||
|
resolve({ portAlreadyUsed: true, on: function(status, cb) { cb(); } });
|
||||||
|
} else {
|
||||||
|
if (logLevel >= LOG_LEVEL_INFO) {
|
||||||
|
console.log('[RemoteDev] Start server...');
|
||||||
|
console.log('-'.repeat(80) + '\n');
|
||||||
|
}
|
||||||
|
resolve(new SocketCluster(options));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
63
packages/redux-devtools-cli/package.json
Normal file
63
packages/redux-devtools-cli/package.json
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
{
|
||||||
|
"name": "remotedev-server",
|
||||||
|
"version": "0.3.1",
|
||||||
|
"description": "Run the RemoteDev monitor on your local server.",
|
||||||
|
"main": "index.js",
|
||||||
|
"bin": {
|
||||||
|
"remotedev": "bin/remotedev.js"
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"bin",
|
||||||
|
"src",
|
||||||
|
"views",
|
||||||
|
"index.js",
|
||||||
|
"defaultDbOptions.json"
|
||||||
|
],
|
||||||
|
"scripts": {
|
||||||
|
"test": "NODE_ENV=test mocha --recursive",
|
||||||
|
"test:watch": "NODE_ENV=test mocha --recursive --watch",
|
||||||
|
"prepublish": "npm run test"
|
||||||
|
},
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/zalmoxisus/remotedev-server.git"
|
||||||
|
},
|
||||||
|
"keywords": [
|
||||||
|
"devtools",
|
||||||
|
"remotedev"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6.0.0"
|
||||||
|
},
|
||||||
|
"author": "Mihail Diordiev <zalmoxisus@gmail.com> (https://github.com/zalmoxisus)",
|
||||||
|
"license": "MIT",
|
||||||
|
"bugs": {
|
||||||
|
"url": "https://github.com/zalmoxisus/remotedev-server/issues"
|
||||||
|
},
|
||||||
|
"homepage": "https://github.com/zalmoxisus/remotedev-server",
|
||||||
|
"dependencies": {
|
||||||
|
"body-parser": "^1.15.0",
|
||||||
|
"chalk": "^1.1.3",
|
||||||
|
"cors": "^2.7.1",
|
||||||
|
"ejs": "^2.4.1",
|
||||||
|
"express": "^4.13.3",
|
||||||
|
"getport": "^0.1.0",
|
||||||
|
"graphql": "^0.13.0",
|
||||||
|
"graphql-server-express": "^1.4.0",
|
||||||
|
"graphql-tools": "^4.0.3",
|
||||||
|
"knex": "^0.15.2",
|
||||||
|
"lodash": "^4.15.0",
|
||||||
|
"minimist": "^1.2.0",
|
||||||
|
"morgan": "^1.7.0",
|
||||||
|
"semver": "^5.3.0",
|
||||||
|
"socketcluster": "^14.3.3",
|
||||||
|
"sqlite3": "^4.0.4",
|
||||||
|
"uuid": "^3.0.1"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"expect": "^1.20.2",
|
||||||
|
"mocha": "^3.2.0",
|
||||||
|
"socketcluster-client": "^14.0.0",
|
||||||
|
"supertest": "^3.0.0"
|
||||||
|
}
|
||||||
|
}
|
21
packages/redux-devtools-cli/src/api/schema.js
Normal file
21
packages/redux-devtools-cli/src/api/schema.js
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
var makeExecutableSchema = require('graphql-tools').makeExecutableSchema;
|
||||||
|
var requireSchema = require('../utils/requireSchema');
|
||||||
|
var schema = requireSchema('./schema_def.graphql', require);
|
||||||
|
|
||||||
|
var resolvers = {
|
||||||
|
Query: {
|
||||||
|
reports: function report(source, args, context, ast) {
|
||||||
|
return context.store.listAll();
|
||||||
|
},
|
||||||
|
report: function report(source, args, context, ast) {
|
||||||
|
return context.store.get(args.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var executableSchema = makeExecutableSchema({
|
||||||
|
typeDefs: schema,
|
||||||
|
resolvers: resolvers
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = executableSchema;
|
60
packages/redux-devtools-cli/src/api/schema_def.graphql
Normal file
60
packages/redux-devtools-cli/src/api/schema_def.graphql
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
# A list of options for the type of the report
|
||||||
|
enum ReportType {
|
||||||
|
STATE
|
||||||
|
ACTION
|
||||||
|
STATES
|
||||||
|
ACTIONS
|
||||||
|
}
|
||||||
|
|
||||||
|
type Report {
|
||||||
|
# Report ID
|
||||||
|
id: ID!
|
||||||
|
# Type of the report, can be: STATE, ACTION, STATES, ACTIONS
|
||||||
|
type: ReportType,
|
||||||
|
# Briefly what happened
|
||||||
|
title: String,
|
||||||
|
# Details supplied by the user
|
||||||
|
description: String,
|
||||||
|
# The last dispatched action before the report was sent
|
||||||
|
action: String,
|
||||||
|
# Stringified actions or the state or both, which should be loaded the application to reproduce the exact behavior
|
||||||
|
payload: String,
|
||||||
|
# Stringified preloaded state object. Could be the initial state of the app or committed state (after dispatching COMMIT action or reaching maxAge)
|
||||||
|
preloadedState: String,
|
||||||
|
# Screenshot url or blob as a string
|
||||||
|
screenshot: String,
|
||||||
|
# User Agent String
|
||||||
|
userAgent: String,
|
||||||
|
# Application version to group the reports and versioning
|
||||||
|
version: String,
|
||||||
|
# Used to identify the user who sent the report
|
||||||
|
userId: String,
|
||||||
|
# More detailed data about the user, usually it's a stringified object
|
||||||
|
user: String,
|
||||||
|
# Everything else you want to send
|
||||||
|
meta: String,
|
||||||
|
# Error message which invoked sending the report
|
||||||
|
exception: String,
|
||||||
|
# Id to identify the store in case there are multiple stores
|
||||||
|
instanceId: String,
|
||||||
|
# Timestamp when the report was added
|
||||||
|
added: String
|
||||||
|
# Id to identify the application (from apps table)
|
||||||
|
appId: ID
|
||||||
|
}
|
||||||
|
|
||||||
|
# Explore GraphQL query schema
|
||||||
|
type Query {
|
||||||
|
# List all reports
|
||||||
|
reports: [Report]
|
||||||
|
# Get a report by ID
|
||||||
|
report(
|
||||||
|
# Report ID
|
||||||
|
id: ID!
|
||||||
|
): Report
|
||||||
|
}
|
||||||
|
|
||||||
|
schema {
|
||||||
|
query: Query
|
||||||
|
#mutation: Mutation
|
||||||
|
}
|
27
packages/redux-devtools-cli/src/db/connector.js
Normal file
27
packages/redux-devtools-cli/src/db/connector.js
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
var path = require('path');
|
||||||
|
var knexModule = require('knex');
|
||||||
|
|
||||||
|
module.exports = function connector(options) {
|
||||||
|
var dbOptions = options.dbOptions;
|
||||||
|
dbOptions.useNullAsDefault = true;
|
||||||
|
if (!dbOptions.migrate) {
|
||||||
|
return knexModule(dbOptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
dbOptions.migrations = { directory: path.resolve(__dirname, 'migrations') };
|
||||||
|
dbOptions.seeds = { directory: path.resolve(__dirname, 'seeds') };
|
||||||
|
var knex = knexModule(dbOptions);
|
||||||
|
|
||||||
|
knex.migrate.latest()
|
||||||
|
.then(function() {
|
||||||
|
return knex.seed.run();
|
||||||
|
})
|
||||||
|
.then(function() {
|
||||||
|
console.log(' \x1b[0;32m[Done]\x1b[0m Migrations are finished\n');
|
||||||
|
})
|
||||||
|
.catch(function(error) {
|
||||||
|
console.error(error);
|
||||||
|
});
|
||||||
|
|
||||||
|
return knex;
|
||||||
|
};
|
71
packages/redux-devtools-cli/src/db/migrations/index.js
Normal file
71
packages/redux-devtools-cli/src/db/migrations/index.js
Normal file
|
@ -0,0 +1,71 @@
|
||||||
|
exports.up = function(knex, Promise) {
|
||||||
|
return Promise.all([
|
||||||
|
knex.schema.createTable('remotedev_reports', function(table) {
|
||||||
|
table.uuid('id').primary();
|
||||||
|
table.string('type');
|
||||||
|
table.string('title');
|
||||||
|
table.string('description');
|
||||||
|
table.string('action');
|
||||||
|
table.text('payload', 'longtext');
|
||||||
|
table.text('preloadedState', 'longtext');
|
||||||
|
table.text('screenshot', 'longtext');
|
||||||
|
table.string('userAgent');
|
||||||
|
table.string('version');
|
||||||
|
table.string('user');
|
||||||
|
table.string('userId');
|
||||||
|
table.string('instanceId');
|
||||||
|
table.string('meta');
|
||||||
|
table.string('exception');
|
||||||
|
table.timestamp('added').defaultTo(knex.fn.now());
|
||||||
|
table.uuid('appId')
|
||||||
|
.references('id')
|
||||||
|
.inTable('remotedev_apps').onDelete('CASCADE').onUpdate('CASCADE')
|
||||||
|
.defaultTo('78626c31-e16b-4528-b8e5-f81301b627f4');
|
||||||
|
}),
|
||||||
|
knex.schema.createTable('remotedev_payloads', function(table){
|
||||||
|
table.uuid('id').primary();
|
||||||
|
table.text('state');
|
||||||
|
table.text('action');
|
||||||
|
table.timestamp('added').defaultTo(knex.fn.now());
|
||||||
|
table.uuid('reportId')
|
||||||
|
.references('id')
|
||||||
|
.inTable('remotedev_reports').onDelete('CASCADE').onUpdate('CASCADE');
|
||||||
|
}),
|
||||||
|
knex.schema.createTable('remotedev_apps', function(table){
|
||||||
|
table.uuid('id').primary();
|
||||||
|
table.string('title');
|
||||||
|
table.string('description');
|
||||||
|
table.string('url');
|
||||||
|
table.timestamps(false, true);
|
||||||
|
}),
|
||||||
|
knex.schema.createTable('remotedev_users', function(table){
|
||||||
|
table.uuid('id').primary();
|
||||||
|
table.string('name');
|
||||||
|
table.string('login');
|
||||||
|
table.string('email');
|
||||||
|
table.string('avatarUrl');
|
||||||
|
table.string('profileUrl');
|
||||||
|
table.string('oauthId');
|
||||||
|
table.string('oauthType');
|
||||||
|
table.string('token');
|
||||||
|
table.timestamps(false, true);
|
||||||
|
}),
|
||||||
|
knex.schema.createTable('remotedev_users_apps', function(table){
|
||||||
|
table.boolean('readOnly').defaultTo(false);
|
||||||
|
table.uuid('userId');
|
||||||
|
table.uuid('appId');
|
||||||
|
table.primary(['userId', 'appId']);
|
||||||
|
table.foreign('userId')
|
||||||
|
.references('id').inTable('remotedev_users').onDelete('CASCADE').onUpdate('CASCADE');
|
||||||
|
table.foreign('appId')
|
||||||
|
.references('id').inTable('remotedev_apps').onDelete('CASCADE').onUpdate('CASCADE');
|
||||||
|
})
|
||||||
|
])
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.down = function(knex, Promise) {
|
||||||
|
return Promise.all([
|
||||||
|
knex.schema.dropTable('remotedev_reports'),
|
||||||
|
knex.schema.dropTable('remotedev_apps')
|
||||||
|
])
|
||||||
|
};
|
12
packages/redux-devtools-cli/src/db/seeds/index.js
Normal file
12
packages/redux-devtools-cli/src/db/seeds/index.js
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
exports.seed = function(knex, Promise) {
|
||||||
|
return Promise.all([
|
||||||
|
knex('remotedev_apps').del()
|
||||||
|
]).then(function() {
|
||||||
|
return Promise.all([
|
||||||
|
knex('remotedev_apps').insert({
|
||||||
|
id: '78626c31-e16b-4528-b8e5-f81301b627f4',
|
||||||
|
title: 'Default'
|
||||||
|
})
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
};
|
13
packages/redux-devtools-cli/src/middleware/graphiql.js
Normal file
13
packages/redux-devtools-cli/src/middleware/graphiql.js
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
var graphiqlExpress = require('graphql-server-express').graphiqlExpress;
|
||||||
|
|
||||||
|
module.exports = graphiqlExpress({
|
||||||
|
endpointURL: '/graphql',
|
||||||
|
query:
|
||||||
|
'{\n' +
|
||||||
|
' reports {\n' +
|
||||||
|
' id,\n' +
|
||||||
|
' type,\n' +
|
||||||
|
' title\n' +
|
||||||
|
' }\n' +
|
||||||
|
'}'
|
||||||
|
});
|
13
packages/redux-devtools-cli/src/middleware/graphql.js
Normal file
13
packages/redux-devtools-cli/src/middleware/graphql.js
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
var graphqlExpress = require('graphql-server-express').graphqlExpress;
|
||||||
|
var schema = require('../api/schema');
|
||||||
|
|
||||||
|
module.exports = function (store) {
|
||||||
|
return graphqlExpress(function() {
|
||||||
|
return {
|
||||||
|
schema: schema,
|
||||||
|
context: {
|
||||||
|
store: store
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
};
|
26
packages/redux-devtools-cli/src/options.js
Normal file
26
packages/redux-devtools-cli/src/options.js
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
var path = require('path');
|
||||||
|
|
||||||
|
module.exports = function getOptions(argv) {
|
||||||
|
var dbOptions = argv.dbOptions;
|
||||||
|
if (typeof dbOptions === 'string') {
|
||||||
|
dbOptions = require(path.resolve(process.cwd(), argv.dbOptions));
|
||||||
|
} else if (typeof dbOptions === 'undefined') {
|
||||||
|
dbOptions = require('../defaultDbOptions.json');
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
host: argv.hostname || process.env.npm_package_remotedev_hostname || null,
|
||||||
|
port: Number(argv.port || process.env.npm_package_remotedev_port) || 8000,
|
||||||
|
protocol: argv.protocol || process.env.npm_package_remotedev_protocol || 'http',
|
||||||
|
protocolOptions: !(argv.protocol === 'https') ? null : {
|
||||||
|
key: argv.key || process.env.npm_package_remotedev_key || null,
|
||||||
|
cert: argv.cert || process.env.npm_package_remotedev_cert || null,
|
||||||
|
passphrase: argv.passphrase || process.env.npm_package_remotedev_passphrase || null
|
||||||
|
},
|
||||||
|
dbOptions: dbOptions,
|
||||||
|
maxRequestBody: argv.passphrase || '16mb',
|
||||||
|
logHTTPRequests: argv.logHTTPRequests,
|
||||||
|
logLevel: argv.logLevel || 3,
|
||||||
|
wsEngine: argv.wsEngine || process.env.npm_package_remotedev_wsengine || 'ws'
|
||||||
|
};
|
||||||
|
}
|
103
packages/redux-devtools-cli/src/store.js
Normal file
103
packages/redux-devtools-cli/src/store.js
Normal file
|
@ -0,0 +1,103 @@
|
||||||
|
var uuidV4 = require('uuid/v4');
|
||||||
|
var pick = require('lodash/pick');
|
||||||
|
var connector = require('./db/connector');
|
||||||
|
|
||||||
|
var reports = 'remotedev_reports';
|
||||||
|
// var payloads = 'remotedev_payloads';
|
||||||
|
var knex;
|
||||||
|
|
||||||
|
var baseFields = ['id', 'title', 'added'];
|
||||||
|
|
||||||
|
function error(msg) {
|
||||||
|
return new Promise(function(resolve, reject) {
|
||||||
|
return resolve({ error: msg });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function list(query, fields) {
|
||||||
|
var r = knex.select(fields || baseFields).from(reports);
|
||||||
|
if (query) return r.where(query);
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
function listAll(query) {
|
||||||
|
var r = knex.select().from(reports);
|
||||||
|
if (query) return r.where(query);
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
function get(id) {
|
||||||
|
if (!id) return error('No id specified.');
|
||||||
|
|
||||||
|
return knex(reports).where('id', id).first();
|
||||||
|
}
|
||||||
|
|
||||||
|
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 reportId = uuidV4();
|
||||||
|
var report = {
|
||||||
|
id: reportId,
|
||||||
|
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,
|
||||||
|
userAgent: data.userAgent,
|
||||||
|
user: data.user,
|
||||||
|
userId: typeof data.user === 'object' ? data.user.id : data.user,
|
||||||
|
instanceId: data.instanceId,
|
||||||
|
meta: data.meta,
|
||||||
|
exception: composeException(data.exception),
|
||||||
|
added: new Date().toISOString(),
|
||||||
|
};
|
||||||
|
if (data.appId) report.appId = data.appId; // TODO check if the id exists and we have access to link it
|
||||||
|
/*
|
||||||
|
var payload = {
|
||||||
|
id: uuid.v4(),
|
||||||
|
reportId: reportId,
|
||||||
|
state: data.payload
|
||||||
|
};
|
||||||
|
*/
|
||||||
|
|
||||||
|
return knex.insert(report).into(reports)
|
||||||
|
.then(function (){ return byBaseFields(report); })
|
||||||
|
}
|
||||||
|
|
||||||
|
function byBaseFields(data) {
|
||||||
|
return pick(data, baseFields);
|
||||||
|
}
|
||||||
|
|
||||||
|
function createStore(options) {
|
||||||
|
knex = connector(options);
|
||||||
|
|
||||||
|
return {
|
||||||
|
list: list,
|
||||||
|
listAll: listAll,
|
||||||
|
get: get,
|
||||||
|
add: add
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function composeException(exception) {
|
||||||
|
var message = '';
|
||||||
|
|
||||||
|
if (exception) {
|
||||||
|
message = 'Exception thrown: ';
|
||||||
|
if (exception.message)
|
||||||
|
message += exception.message;
|
||||||
|
if (exception.stack)
|
||||||
|
message += '\n' + exception.stack;
|
||||||
|
}
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = createStore;
|
6
packages/redux-devtools-cli/src/utils/requireSchema.js
Normal file
6
packages/redux-devtools-cli/src/utils/requireSchema.js
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
var fs = require('fs');
|
||||||
|
|
||||||
|
module.exports = function(name, require) {
|
||||||
|
return fs.readFileSync(require.resolve(name)).toString();
|
||||||
|
// return GraphQL.buildSchema(schema);
|
||||||
|
};
|
131
packages/redux-devtools-cli/src/worker.js
Normal file
131
packages/redux-devtools-cli/src/worker.js
Normal file
|
@ -0,0 +1,131 @@
|
||||||
|
var SCWorker = require("socketcluster/scworker");
|
||||||
|
var path = require('path');
|
||||||
|
var app = require('express')();
|
||||||
|
var bodyParser = require('body-parser');
|
||||||
|
var cors = require('cors');
|
||||||
|
var morgan = require('morgan');
|
||||||
|
var graphiqlMiddleware = require('./middleware/graphiql');
|
||||||
|
var graphqlMiddleware = require('./middleware/graphql');
|
||||||
|
var createStore = require('./store');
|
||||||
|
|
||||||
|
class Worker extends SCWorker {
|
||||||
|
run() {
|
||||||
|
var httpServer = this.httpServer;
|
||||||
|
var scServer = this.scServer;
|
||||||
|
var options = this.options;
|
||||||
|
var store = createStore(options);
|
||||||
|
var limit = options.maxRequestBody;
|
||||||
|
var logHTTPRequests = options.logHTTPRequests;
|
||||||
|
|
||||||
|
httpServer.on('request', app);
|
||||||
|
|
||||||
|
app.set('view engine', 'ejs');
|
||||||
|
app.set('views', path.resolve(__dirname, '..', 'views'));
|
||||||
|
|
||||||
|
if (logHTTPRequests) {
|
||||||
|
if (typeof logHTTPRequests === 'object') app.use(morgan('combined', logHTTPRequests));
|
||||||
|
else app.use(morgan('combined'));
|
||||||
|
}
|
||||||
|
|
||||||
|
app.use('/graphiql', graphiqlMiddleware);
|
||||||
|
|
||||||
|
app.get('*', function (req, res) {
|
||||||
|
res.render('index', {port: options.port});
|
||||||
|
});
|
||||||
|
|
||||||
|
app.use(cors({methods: 'POST'}));
|
||||||
|
app.use(bodyParser.json({limit: limit}));
|
||||||
|
app.use(bodyParser.urlencoded({limit: limit, extended: false}));
|
||||||
|
|
||||||
|
app.use('/graphql', graphqlMiddleware(store));
|
||||||
|
|
||||||
|
app.post('/', function (req, res) {
|
||||||
|
if (!req.body) return res.status(404).end();
|
||||||
|
switch (req.body.op) {
|
||||||
|
case 'get':
|
||||||
|
store.get(req.body.id).then(function (r) {
|
||||||
|
res.send(r || {});
|
||||||
|
}).catch(function (error) {
|
||||||
|
console.error(error);
|
||||||
|
res.sendStatus(500)
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
case 'list':
|
||||||
|
store.list(req.body.query, req.body.fields).then(function (r) {
|
||||||
|
res.send(r);
|
||||||
|
}).catch(function (error) {
|
||||||
|
console.error(error);
|
||||||
|
res.sendStatus(500)
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
store.add(req.body).then(function (r) {
|
||||||
|
res.send({id: r.id, error: r.error});
|
||||||
|
scServer.exchange.publish('report', {
|
||||||
|
type: 'add', data: r
|
||||||
|
});
|
||||||
|
}).catch(function (error) {
|
||||||
|
console.error(error);
|
||||||
|
res.status(500).send({})
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
scServer.addMiddleware(scServer.MIDDLEWARE_EMIT, function (req, next) {
|
||||||
|
var channel = req.event;
|
||||||
|
var data = req.data;
|
||||||
|
if (channel.substr(0, 3) === 'sc-' || channel === 'respond' || channel === 'log') {
|
||||||
|
scServer.exchange.publish(channel, data);
|
||||||
|
} else if (channel === 'log-noid') {
|
||||||
|
scServer.exchange.publish('log', {id: req.socket.id, data: data});
|
||||||
|
}
|
||||||
|
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});
|
||||||
|
}).catch(function (error) {
|
||||||
|
console.error(error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
scServer.on('connection', function (socket) {
|
||||||
|
var channelToWatch, channelToEmit;
|
||||||
|
socket.on('login', function (credentials, respond) {
|
||||||
|
if (credentials === 'master') {
|
||||||
|
channelToWatch = 'respond';
|
||||||
|
channelToEmit = 'log';
|
||||||
|
} else {
|
||||||
|
channelToWatch = 'log';
|
||||||
|
channelToEmit = 'respond';
|
||||||
|
}
|
||||||
|
this.exchange.subscribe('sc-' + socket.id).watch(function (msg) {
|
||||||
|
socket.emit(channelToWatch, msg);
|
||||||
|
});
|
||||||
|
respond(null, channelToWatch);
|
||||||
|
});
|
||||||
|
socket.on('getReport', function (id, respond) {
|
||||||
|
store.get(id).then(function (data) {
|
||||||
|
respond(null, data);
|
||||||
|
}).catch(function (error) {
|
||||||
|
console.error(error);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
socket.on('disconnect', function () {
|
||||||
|
var channel = this.exchange.channel('sc-' + socket.id);
|
||||||
|
channel.unsubscribe();
|
||||||
|
channel.destroy();
|
||||||
|
scServer.exchange.publish(
|
||||||
|
channelToEmit,
|
||||||
|
{id: socket.id, type: 'DISCONNECTED'}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
new Worker();
|
186
packages/redux-devtools-cli/test/integration.spec.js
Normal file
186
packages/redux-devtools-cli/test/integration.spec.js
Normal file
|
@ -0,0 +1,186 @@
|
||||||
|
var childProcess = require('child_process');
|
||||||
|
var request = require('supertest');
|
||||||
|
var expect = require('expect');
|
||||||
|
var scClient = require('socketcluster-client');
|
||||||
|
var remotedev = require('../');
|
||||||
|
|
||||||
|
describe('Server', function() {
|
||||||
|
var scServer;
|
||||||
|
this.timeout(5000);
|
||||||
|
before(function(done) {
|
||||||
|
scServer = childProcess.fork(__dirname + '/../bin/remotedev.js');
|
||||||
|
setTimeout(done, 2000);
|
||||||
|
});
|
||||||
|
|
||||||
|
after(function() {
|
||||||
|
if (scServer) {
|
||||||
|
scServer.kill();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Express backend', function() {
|
||||||
|
it('loads main page', function() {
|
||||||
|
request('http://localhost:8000')
|
||||||
|
.get('/')
|
||||||
|
.expect('Content-Type', /text\/html/)
|
||||||
|
.expect(200)
|
||||||
|
.then(function(res) {
|
||||||
|
expect(res.text).toMatch(/<title>RemoteDev<\/title>/);
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
it('resolves an inexistent url', function(done) {
|
||||||
|
request('http://localhost:8000/jreerfr/123')
|
||||||
|
.get('/')
|
||||||
|
.expect('Content-Type', /text\/html/)
|
||||||
|
.expect(200, done);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Realtime monitoring', function() {
|
||||||
|
var socket, socket2, channel;
|
||||||
|
before(function() {
|
||||||
|
socket = scClient.connect({ hostname: 'localhost', port: 8000 });
|
||||||
|
socket.connect();
|
||||||
|
socket.on('error', function(error) {
|
||||||
|
console.error('Socket1 error', error);
|
||||||
|
});
|
||||||
|
socket2 = scClient.connect({ hostname: 'localhost', port: 8000 });
|
||||||
|
socket2.connect();
|
||||||
|
socket.on('error', function(error) {
|
||||||
|
console.error('Socket2 error', error);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
after(function() {
|
||||||
|
socket.disconnect();
|
||||||
|
socket2.disconnect();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should connect', function(done) {
|
||||||
|
socket.on('connect', function(status) {
|
||||||
|
expect(status.id).toExist();
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should login', function() {
|
||||||
|
socket.emit('login', 'master', function(error, channelName) {
|
||||||
|
if (error) { console.log(error); return; }
|
||||||
|
expect(channelName).toBe('respond');
|
||||||
|
channel = socket.subscribe(channelName);
|
||||||
|
expect(channel.SUBSCRIBED).toBe('subscribed');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should send message', function(done) {
|
||||||
|
var data = {
|
||||||
|
"type": "ACTION",
|
||||||
|
"payload": {
|
||||||
|
"todos": "do some"
|
||||||
|
},
|
||||||
|
"action": {
|
||||||
|
"timestamp": 1483349708506,
|
||||||
|
"action": {
|
||||||
|
"type": "ADD_TODO",
|
||||||
|
"text": "hggg"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"instanceId": "tAmA7H5fclyWhvizAAAi",
|
||||||
|
"name": "LoggerInstance",
|
||||||
|
"id": "tAmA7H5fclyWhvizAAAi"
|
||||||
|
};
|
||||||
|
|
||||||
|
socket2.emit('login', '', function(error, channelName) {
|
||||||
|
if (error) { console.log(error); return; }
|
||||||
|
expect(channelName).toBe('log');
|
||||||
|
var channel2 = socket2.subscribe(channelName);
|
||||||
|
expect(channel2.SUBSCRIBED).toBe('subscribed');
|
||||||
|
channel2.on('subscribe', function() {
|
||||||
|
channel2.watch(function(message) {
|
||||||
|
expect(message).toEqual(data);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
socket.emit(channelName, data);
|
||||||
|
})
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('REST backend', function() {
|
||||||
|
var id;
|
||||||
|
var report = {
|
||||||
|
type: 'ACTIONS',
|
||||||
|
title: 'Test report',
|
||||||
|
description: 'Test body report',
|
||||||
|
action: 'SOME_FINAL_ACTION',
|
||||||
|
payload: '[{"type":"ADD_TODO","text":"hi"},{"type":"SOME_FINAL_ACTION"}]',
|
||||||
|
preloadedState: '{"todos":[{"text":"Use Redux","completed":false,"id":0}]}',
|
||||||
|
userAgent: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.95 Safari/537.36'
|
||||||
|
};
|
||||||
|
it('should add a report', function() {
|
||||||
|
request('http://localhost:8000')
|
||||||
|
.post('/')
|
||||||
|
.send(report)
|
||||||
|
.set('Accept', 'application/json')
|
||||||
|
.expect('Content-Type', /application\/json/)
|
||||||
|
.expect(200)
|
||||||
|
.then(function(res) {
|
||||||
|
id = res.body.id;
|
||||||
|
expect(id).toExist();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should get the report', function() {
|
||||||
|
request('http://localhost:8000')
|
||||||
|
.post('/')
|
||||||
|
.send({
|
||||||
|
op: 'get',
|
||||||
|
id: id
|
||||||
|
})
|
||||||
|
.set('Accept', 'application/json')
|
||||||
|
.expect('Content-Type', /application\/json/)
|
||||||
|
.expect(200)
|
||||||
|
.then(function(res) {
|
||||||
|
expect(res.body).toInclude(report);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should list reports', function() {
|
||||||
|
request('http://localhost:8000')
|
||||||
|
.post('/')
|
||||||
|
.send({
|
||||||
|
op: 'list'
|
||||||
|
})
|
||||||
|
.set('Accept', 'application/json')
|
||||||
|
.expect('Content-Type', /application\/json/)
|
||||||
|
.expect(200)
|
||||||
|
.then(function(res) {
|
||||||
|
expect(res.body.length).toBe(1);
|
||||||
|
expect(res.body[0].id).toBe(id);
|
||||||
|
expect(res.body[0].title).toBe('Test report');
|
||||||
|
expect(res.body[0].added).toExist();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('GraphQL backend', function() {
|
||||||
|
it('should get the report', function() {
|
||||||
|
request('http://localhost:8000')
|
||||||
|
.post('/graphql')
|
||||||
|
.send({
|
||||||
|
query: '{ reports { id, type, title } }'
|
||||||
|
})
|
||||||
|
.set('Accept', 'application/json')
|
||||||
|
.expect('Content-Type', /application\/json/)
|
||||||
|
.expect(200)
|
||||||
|
.then(function(res) {
|
||||||
|
var reports = res.body.data.reports;
|
||||||
|
expect(reports.length).toBe(1);
|
||||||
|
expect(reports[0].id).toExist();
|
||||||
|
expect(reports[0].title).toBe('Test report');
|
||||||
|
expect(reports[0].type).toBe('ACTIONS');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
46
packages/redux-devtools-cli/views/index.ejs
Normal file
46
packages/redux-devtools-cli/views/index.ejs
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<title>RemoteDev</title>
|
||||||
|
<style>
|
||||||
|
html {
|
||||||
|
min-width: 350px;
|
||||||
|
min-height: 300px;
|
||||||
|
}
|
||||||
|
body {
|
||||||
|
position: fixed;
|
||||||
|
overflow: hidden;
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
#root {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
#root > div {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="root"></div>
|
||||||
|
<script src="//unpkg.com/react@0.14/dist/react.min.js"></script>
|
||||||
|
<script src="//unpkg.com/react-dom@0.14/dist/react-dom.min.js"></script>
|
||||||
|
<script src="//unpkg.com/remotedev-app/dist/remotedev-app.min.js"></script>
|
||||||
|
<script>
|
||||||
|
window.remotedevOptions = {
|
||||||
|
hostname: location.hostname,
|
||||||
|
port: <%= port %>,
|
||||||
|
autoReconnect: true
|
||||||
|
};
|
||||||
|
ReactDOM.render(
|
||||||
|
React.createElement(RemoteDevApp, {
|
||||||
|
socketOptions: window.remotedevOptions
|
||||||
|
}),
|
||||||
|
document.querySelector('#root')
|
||||||
|
);
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
Loading…
Reference in New Issue
Block a user