Skip to content

Commit 5edf2c9

Browse files
committed
Merge branch 'beta'
2 parents d9eb26b + 58ade78 commit 5edf2c9

38 files changed

+4149
-4259
lines changed

.eslintrc.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
module.exports = {
2+
parser: "babel-eslint",
23
// extends: ['eslint-config-google'].map(require.resolve),
34
extends: ['@advanced-rest-client/eslint-config'].map(require.resolve),
45
rules: {

.travis.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@ cache:
2121
- "$HOME/.cache/electron-builder"
2222
before_cache:
2323
- rm -rf $HOME/.cache/electron-builder/wine
24+
branches:
25+
except:
26+
- "/^\\d+\\.\\d+\\.\\d+$/"
2427
install:
2528
- npm install
2629
script: "./test/travis-build.sh"

app.js

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
// Scrips are moved to scripts/renderer/preload.js so node integration can be disabled
22
// in the application window.
3+
4+
/* eslint-disable no-console */
5+
36
/**
47
* Class responsible for initializing the main ARC elements
58
* and setup base options.
@@ -94,6 +97,7 @@ class ArcInit {
9497
ipc.on('app-navigate', this._appNavHandler.bind(this));
9598
ipc.on('popup-app-menu-opened', this._popupMenuOpened.bind(this));
9699
ipc.on('popup-app-menu-closed', this._popupMenuClosed.bind(this));
100+
ipc.on('system-theme-changed', this._systemThemeChangeHandler.bind(this));
97101
}
98102
/**
99103
* Requests initial state information from the main process for current
@@ -145,9 +149,9 @@ class ArcInit {
145149
throw e;
146150
}
147151
if (this.initConfig.darkMode) {
148-
cnf.theme = 'advanced-rest-client/arc-electron-dark-theme';
152+
cnf.theme = '@advanced-rest-client/arc-electron-dark-theme';
149153
}
150-
if (cnf.theme === 'advanced-rest-client/arc-electron-anypoint-theme') {
154+
if (cnf.theme === '@advanced-rest-client/arc-electron-anypoint-theme') {
151155
const app = this.app;
152156
app.compatibility = true;
153157
}
@@ -177,6 +181,7 @@ class ArcInit {
177181
if (this.created) {
178182
return Promise.resolve();
179183
}
184+
/* eslint-disable-next-line import/no-unresolved */
180185
await import('web-module://src/arc-electron.js');
181186
const app = document.createElement('arc-electron');
182187
app.id = 'app';
@@ -237,7 +242,7 @@ class ArcInit {
237242
*/
238243
commandHandler(e, action, ...args) {
239244
// console.info('Renderer command handled: ', action);
240-
const app = this.app;
245+
const { app } = this;
241246
switch (action) {
242247
case 'show-settings': app.openSettings(); break;
243248
case 'about': app.openAbout(); break;
@@ -262,6 +267,7 @@ class ArcInit {
262267
case 'open-onboarding': app.openOnboarding(); break;
263268
case 'open-workspace-details': app.openWorkspaceDetails(); break;
264269
case 'export-workspace': this.exportWorkspace(); break;
270+
case 'open-client-certificates': app.openClientCertificates(); break;
265271
default:
266272
console.warn('Unknown command', action, args);
267273
}
@@ -504,6 +510,26 @@ class ArcInit {
504510
const workspace = this.app.workspace.serializeWorkspace();
505511
return await this.fs.exportFileData(workspace, 'application/json', 'arc-workspace.arc');
506512
}
513+
/**
514+
* Handler for system theme change event dispatche in the IO thread.
515+
* Updates theme depending on current setting.
516+
*
517+
* @param {Event} e
518+
* @param {Boolean} isDarkMode true when Electron detected dark mode
519+
* @return {Promise}
520+
*/
521+
async _systemThemeChangeHandler(e, isDarkMode) {
522+
const theme = isDarkMode ?
523+
'@advanced-rest-client/arc-electron-dark-theme' :
524+
'@advanced-rest-client/arc-electron-default-theme';
525+
const app = this.app;
526+
app.compatibility = false;
527+
try {
528+
await this.themeManager.loadTheme(theme);
529+
} catch (e) {
530+
console.error(e);
531+
}
532+
}
507533
}
508534

509535
const initScript = new ArcInit();

dev-api/api-es.js

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import express from 'express';
2+
import session from 'express-session';
3+
import apiRouter from './routes.js';
4+
5+
/* eslint-disable no-console */
6+
7+
const app = express();
8+
export default app;
9+
10+
app.disable('etag');
11+
app.disable('x-powered-by');
12+
app.set('trust proxy', true);
13+
14+
const sessionConfig = {
15+
resave: false,
16+
saveUninitialized: false,
17+
secret: 'dev-secret',
18+
signed: true
19+
};
20+
21+
app.use(session(sessionConfig));
22+
app.use('/v1', apiRouter);
23+
24+
const portStr = process.argv.slice(2).find((arg) => arg.indexOf('--PORT') === 0);
25+
let port;
26+
if (!isNaN(portStr)) {
27+
port = Number(portStr);
28+
} else {
29+
port = 8080;
30+
}
31+
32+
// Basic 404 handler
33+
app.use((req, res) => {
34+
res.status(404).send('Not Found');
35+
});
36+
37+
// Basic error handler
38+
app.use((err, req, res) => {
39+
console.error(err.response);
40+
res.status(500).send({
41+
error: true,
42+
message: err.response || 'Something is wrong...'
43+
});
44+
});
45+
46+
const server = app.listen(port, () => {
47+
const port = server.address().port;
48+
console.info(`App listening on port ${port}`);
49+
});

dev-api/api.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
require = require('esm')(module);
2+
module.exports = require('./api-es.js');

dev-api/auth-basic.route.js

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import express from 'express';
2+
import { BaseApi } from './base-api.js';
3+
4+
const router = express.Router();
5+
export default router;
6+
7+
class AuthBaiscRoute extends BaseApi {
8+
sendUnauthorized(res) {
9+
res.status(401);
10+
res.set('WWW-Authenticate', 'Basic realm="This resource is protected"');
11+
res.send('Auth required');
12+
}
13+
14+
requireAuthorized(req, res) {
15+
const auth = req.headers['authorization'];
16+
if (!auth) {
17+
this.sendUnauthorized(res);
18+
return;
19+
}
20+
const { username, password } = req.params;
21+
try {
22+
const parsed = auth.replace(/basic| /ig).trim();
23+
const buff = new Buffer(parsed, 'base64');
24+
const str = buff.toString('ascii');
25+
const [aUname, aPasswd] = str.split(':');
26+
if (username !== aUname || password !== aPasswd) {
27+
this.sendUnauthorized(res);
28+
return;
29+
}
30+
} catch (e) {
31+
this.sendUnauthorized(res);
32+
return;
33+
}
34+
this.printDefaultResponse(req, res);
35+
}
36+
}
37+
38+
const api = new AuthBaiscRoute();
39+
api.setCors(router);
40+
api.wrapApi(router, [
41+
['/:username/:password', 'requireAuthorized'],
42+
]);

dev-api/base-api.js

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
import cors from 'cors';
2+
3+
export class BaseApi {
4+
constructor() {
5+
this._processCors = this._processCors.bind(this);
6+
}
7+
/**
8+
* Sets CORS on all routes for `OPTIONS` HTTP method.
9+
* @param {Object} router Express app.
10+
*/
11+
setCors(router) {
12+
router.options('*', cors(this._processCors));
13+
}
14+
/**
15+
* Shorthand function to register a route on this class.
16+
* @param {Object} router Express app.
17+
* @param {Array<Array<String>>} routes List of routes. Each route is an array
18+
* where:
19+
* - index `0` is the API route, eg, `/api/models/:modelId`
20+
* - index `1` is the function name to call
21+
* - index `2` is optional and describes HTTP method. Defaults to 'get'.
22+
* It must be lowercase.
23+
*/
24+
wrapApi(router, routes) {
25+
for (let i = 0, len = routes.length; i < len; i++) {
26+
const route = routes[i];
27+
const method = route[2] || 'get';
28+
const clb = this[route[1]].bind(this);
29+
router[method](route[0], cors(this._processCors), clb);
30+
}
31+
}
32+
/**
33+
* Sends error to the client in a standarized way.
34+
* @param {Object} res HTTP response object
35+
* @param {String} message Error message to send.
36+
* @param {?Number} status HTTP status code, default to 400.
37+
*/
38+
sendError(res, message, status) {
39+
res.status(status || 400).send({
40+
error: true,
41+
message
42+
});
43+
}
44+
45+
_processCors(req, callback) {
46+
const whitelist = [];
47+
const origin = req.header('Origin');
48+
let corsOptions;
49+
if (!origin) {
50+
corsOptions = { origin: false };
51+
} else if (origin.indexOf('http://localhost:') === 0 || origin.indexOf('http://127.0.0.1:') === 0) {
52+
corsOptions = { origin: true };
53+
} else if (whitelist.indexOf(origin) !== -1) {
54+
corsOptions = { origin: true };
55+
}
56+
if (corsOptions) {
57+
corsOptions.credentials = true;
58+
corsOptions.allowedHeaders = ['Content-Type', 'Authorization'];
59+
corsOptions.origin = origin;
60+
}
61+
callback(null, corsOptions);
62+
}
63+
/**
64+
* Creates a default response message and writes it to the response.
65+
* @param {Object} req
66+
* @param {Object} res
67+
*/
68+
printDefaultResponse(req, res) {
69+
const result = {
70+
header: req.headers,
71+
url: req.url
72+
};
73+
if (req.body) {
74+
result.body = req.body;
75+
}
76+
res.send(result);
77+
}
78+
}

dev-api/routes.js

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import express from 'express';
2+
import statusCodesRoute from './status-codes.route.js';
3+
import authBasicRoute from './auth-basic.route.js';
4+
5+
/* eslint-disable no-console */
6+
7+
const router = express.Router();
8+
export default router;
9+
10+
// Test scenarios for status codes
11+
router.use('/status', statusCodesRoute);
12+
router.use('/auth/basic', authBasicRoute);
13+
14+
// Errors
15+
router.use((req, res) => {
16+
const message = `Route ${req.url} not found`;
17+
console.warn(message);
18+
res.status(404).send({
19+
error: true,
20+
message
21+
});
22+
});
23+
24+
router.use((err, req, res) => {
25+
console.error(err);
26+
res.send({
27+
error: true,
28+
message: 'There was an error. That is all we can share.'
29+
});
30+
});

dev-api/status-codes.route.js

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import express from 'express';
2+
import bodyParser from 'body-parser';
3+
import { BaseApi } from './base-api.js';
4+
5+
const router = express.Router();
6+
export default router;
7+
router.use(bodyParser.json());
8+
9+
class StatusCodesRoute extends BaseApi {
10+
returnStatus(req, res) {
11+
const { status } = req.params;
12+
let parsedStatus = Number(status);
13+
if (parsedStatus !== parsedStatus) {
14+
parsedStatus = 200;
15+
}
16+
res.status(parsedStatus);
17+
this.printDefaultResponse(req, res);
18+
res.end();
19+
}
20+
21+
returnMessage(req, res) {
22+
const { msg } = req.params;
23+
const message = decodeURIComponent(msg.replace(/\+/g, ' '));
24+
res.statusMessage = message;
25+
res.status(200);
26+
this.printDefaultResponse(req, res);
27+
res.end();
28+
}
29+
30+
returnEmptyMessage(req, res) {
31+
res.writeHead(200, '', { 'Content-Type': 'text/plain' });
32+
res.end();
33+
}
34+
}
35+
36+
const api = new StatusCodesRoute();
37+
api.setCors(router);
38+
api.wrapApi(router, [
39+
['/code/:status', 'returnStatus'],
40+
['/message/empty', 'returnEmptyMessage'],
41+
['/message/:msg', 'returnMessage'],
42+
]);

menus/darwin.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -265,6 +265,10 @@
265265
"label": "Cookie manager",
266266
"command": "application:open-cookie-manager"
267267
},
268+
{
269+
"label": "Client certificates",
270+
"command": "application:open-client-certificates"
271+
},
268272
{
269273
"type": "separator"
270274
},

0 commit comments

Comments
 (0)