Refactored extension to avoid eval() and instead use executeScript and message communication
This commit is contained in:
parent
ca9ec58244
commit
632b248dc5
6 changed files with 308 additions and 46 deletions
|
@ -1,8 +1,10 @@
|
||||||
import utils from './utils';
|
import utils from './utils';
|
||||||
|
import axios from 'axios';
|
||||||
|
import Mercury from '@postlight/mercury-parser';
|
||||||
|
|
||||||
global.browser = require('webextension-polyfill');
|
global.browser = require('webextension-polyfill');
|
||||||
|
|
||||||
const menu = {
|
const app = {
|
||||||
hosts: {},
|
hosts: {},
|
||||||
actions: {},
|
actions: {},
|
||||||
scripts: {},
|
scripts: {},
|
||||||
|
@ -76,6 +78,112 @@ const menu = {
|
||||||
await utils.methods.runScript(this.scripts[action].script, this.hosts[host], tab, target);
|
await utils.methods.runScript(this.scripts[action].script, this.hosts[host], tab, target);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
browser.runtime.onConnect.addListener(port => {
|
||||||
|
switch (port.name) {
|
||||||
|
case 'action':
|
||||||
|
port.onMessage.addListener(async message => {
|
||||||
|
switch (message.type) {
|
||||||
|
case 'run':
|
||||||
|
const ret = await utils.methods.run(message.action, message.host);
|
||||||
|
port.postMessage(ret);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'url':
|
||||||
|
port.onMessage.addListener(async message => {
|
||||||
|
const tab = await utils.methods.getCurrentTab();
|
||||||
|
switch (message.type) {
|
||||||
|
case 'get':
|
||||||
|
port.postMessage(tab.url);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'set':
|
||||||
|
await browser.tabs.sendMessage(tab.id, { type: 'setURL', url: message.url }, {});
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'open':
|
||||||
|
await browser.tabs.create({
|
||||||
|
url: message.url,
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'dom':
|
||||||
|
port.onMessage.addListener(async message => {
|
||||||
|
const tab = await utils.methods.getCurrentTab();
|
||||||
|
switch (message.type) {
|
||||||
|
case 'get':
|
||||||
|
const dom = await browser.tabs.sendMessage(tab.id, { type: 'getDOM' }, {});
|
||||||
|
port.postMessage(dom);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'set':
|
||||||
|
await browser.tabs.sendMessage(tab.id, { type: 'setDOM', html: message.html }, {});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'notify':
|
||||||
|
port.onMessage.addListener(async message => {
|
||||||
|
switch (message.type) {
|
||||||
|
case 'run':
|
||||||
|
utils.methods.notify(message.message, message.title, message.error);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'axios':
|
||||||
|
port.onMessage.addListener(async message => {
|
||||||
|
const method = axios[message.type.toLowerCase()];
|
||||||
|
const response = await method(message.url, ...message.args);
|
||||||
|
port.postMessage({
|
||||||
|
config: {
|
||||||
|
data: response.config.data,
|
||||||
|
headers: response.config.headers,
|
||||||
|
maxContentLength: response.config.maxContentLength,
|
||||||
|
method: response.config.method,
|
||||||
|
timeout: response.config.timeout,
|
||||||
|
url: response.config.url,
|
||||||
|
xsrfCookieName: response.config.xsrfCookieName,
|
||||||
|
xsrfHeaderName: response.config.xsrfHeaderName,
|
||||||
|
},
|
||||||
|
headers: response.headers,
|
||||||
|
data: response.data,
|
||||||
|
status: response.status,
|
||||||
|
statusText: response.statusText,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'mercury':
|
||||||
|
port.onMessage.addListener(async message => {
|
||||||
|
switch (message.type) {
|
||||||
|
case 'parse':
|
||||||
|
const response = await Mercury.parse(message.url, {
|
||||||
|
contentType: 'html',
|
||||||
|
html: message.html,
|
||||||
|
});
|
||||||
|
|
||||||
|
port.postMessage(response);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
async create() {
|
async create() {
|
||||||
|
@ -84,8 +192,9 @@ const menu = {
|
||||||
};
|
};
|
||||||
|
|
||||||
const onCreate = () => {
|
const onCreate = () => {
|
||||||
// noinspection JSIgnoredPromiseFromCall
|
app.create().then(() => {
|
||||||
menu.create();
|
console.debug('Extension context created');
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
onCreate();
|
onCreate();
|
||||||
|
|
|
@ -4,23 +4,24 @@ const context = {
|
||||||
targetElement: null,
|
targetElement: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
browser.runtime.onMessage.addListener((message, sender, sendResponse) => {
|
browser.runtime.onMessage.addListener(async message => {
|
||||||
switch (message.type) {
|
switch (message.type) {
|
||||||
case 'getURL':
|
case 'getURL':
|
||||||
sendResponse(window.location.href);
|
return Promise.resolve(window.location.href);
|
||||||
|
|
||||||
|
case 'setURL':
|
||||||
|
window.location.href = message.url;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'getDOM':
|
case 'getDOM':
|
||||||
sendResponse(document.getElementsByTagName('html')[0].outerHTML);
|
return Promise.resolve(document.getElementsByTagName('html')[0].outerHTML);
|
||||||
break;
|
|
||||||
|
|
||||||
case 'setDOM':
|
case 'setDOM':
|
||||||
document.getElementsByTagName('html')[0].innerHTML = message.html;
|
document.documentElement.innerHTML = message.html;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'getTargetElement':
|
case 'getTargetElement':
|
||||||
sendResponse(context.targetElement ? context.targetElement.outerHTML : null);
|
return Promise.resolve(context.targetElement ? context.targetElement.outerHTML : null);
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,6 @@
|
||||||
"name": "platypush",
|
"name": "platypush",
|
||||||
"description": "Web extension for interacting with Platypush instances via browser and creating custom browser actions",
|
"description": "Web extension for interacting with Platypush instances via browser and creating custom browser actions",
|
||||||
"version": "0.1.4",
|
"version": "0.1.4",
|
||||||
"content_security_policy": "script-src 'self' 'unsafe-eval'; object-src 'self'",
|
|
||||||
"manifest_version": 2,
|
"manifest_version": 2,
|
||||||
"icons": {
|
"icons": {
|
||||||
"16": "icons/icon-16.png",
|
"16": "icons/icon-16.png",
|
||||||
|
|
|
@ -157,9 +157,9 @@ export default {
|
||||||
selectedScript: Object,
|
selectedScript: Object,
|
||||||
scriptTemplate: {
|
scriptTemplate: {
|
||||||
type: String,
|
type: String,
|
||||||
default: `async (app, host, browser, tab, target, ...args) => {
|
default: `async (app, args) => {
|
||||||
// Run some action on the host
|
// Run some action on the host
|
||||||
const status = await app.run({ name: 'music.mpd.pause' }, host);
|
const status = await app.run({ name: 'music.mpd.pause' }, args.host);
|
||||||
|
|
||||||
// Send notifications to the browser
|
// Send notifications to the browser
|
||||||
app.notify(status.state, 'Music status changed');
|
app.notify(status.state, 'Music status changed');
|
||||||
|
@ -457,7 +457,7 @@ export default {
|
||||||
this.saveParams.name = action.displayName;
|
this.saveParams.name = action.displayName;
|
||||||
this.saveParams.color = action.color;
|
this.saveParams.color = action.color;
|
||||||
this.saveParams.iconClass = action.iconClass;
|
this.saveParams.iconClass = action.iconClass;
|
||||||
this.selectedCategories = action.categories;
|
this.selectedCategories = action.categories.map(cat => (typeof cat === 'string' ? { text: cat } : cat));
|
||||||
this.selectedHosts = action.hosts;
|
this.selectedHosts = action.hosts;
|
||||||
|
|
||||||
if (this.selectedAction) {
|
if (this.selectedAction) {
|
||||||
|
|
126
src/script.js
Normal file
126
src/script.js
Normal file
|
@ -0,0 +1,126 @@
|
||||||
|
export default {
|
||||||
|
api: `{
|
||||||
|
run: (action, host) => {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
const port = browser.runtime.connect({ name: 'action' });
|
||||||
|
port.onMessage.addListener(msg => {
|
||||||
|
resolve(msg);
|
||||||
|
});
|
||||||
|
|
||||||
|
port.postMessage({
|
||||||
|
type: 'run',
|
||||||
|
host: host,
|
||||||
|
action: action,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
getURL: () => {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
const port = browser.runtime.connect({ name: 'url' });
|
||||||
|
port.onMessage.addListener(url => {
|
||||||
|
resolve(url);
|
||||||
|
});
|
||||||
|
|
||||||
|
port.postMessage({
|
||||||
|
type: 'get',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
setURL: (url) => {
|
||||||
|
const port = browser.runtime.connect({ name: 'url' });
|
||||||
|
port.postMessage({
|
||||||
|
type: 'set',
|
||||||
|
url: url,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
openTab: (url) => {
|
||||||
|
const port = browser.runtime.connect({ name: 'url' });
|
||||||
|
port.postMessage({
|
||||||
|
type: 'open',
|
||||||
|
url: url,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
axios: ['get', 'post', 'put', 'delete', 'head', 'options', 'patch'].reduce((api, method) => {
|
||||||
|
api[method] = (url, ...args) => {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
const port = browser.runtime.connect({ name: 'axios' });
|
||||||
|
port.onMessage.addListener(response => {
|
||||||
|
resolve(response);
|
||||||
|
});
|
||||||
|
|
||||||
|
port.postMessage({
|
||||||
|
type: method,
|
||||||
|
url: url,
|
||||||
|
args: args,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return api;
|
||||||
|
}, {}),
|
||||||
|
|
||||||
|
mercury: {
|
||||||
|
parse: (url, html) => {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
const port = browser.runtime.connect({ name: 'mercury' });
|
||||||
|
port.onMessage.addListener(response => {
|
||||||
|
resolve(response);
|
||||||
|
});
|
||||||
|
|
||||||
|
port.postMessage({
|
||||||
|
type: 'parse',
|
||||||
|
url: url,
|
||||||
|
html: html,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
getDOM: () => {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
const port = browser.runtime.connect({ name: 'dom' });
|
||||||
|
port.onMessage.addListener(dom => {
|
||||||
|
dom = (new DOMParser()).parseFromString(dom, 'text/html');
|
||||||
|
resolve(dom);
|
||||||
|
});
|
||||||
|
|
||||||
|
port.postMessage({
|
||||||
|
type: 'get',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
setDOM: (html) => {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
const port = browser.runtime.connect({ name: 'dom' });
|
||||||
|
port.postMessage({
|
||||||
|
type: 'set',
|
||||||
|
html: html,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
HTML2DOM: (html, isRoot = false) => {
|
||||||
|
const dom = new DOMParser().parseFromString(html, 'text/html').documentElement;
|
||||||
|
if (isRoot)
|
||||||
|
return dom;
|
||||||
|
return dom.getElementsByTagName('body')[0].firstChild;
|
||||||
|
},
|
||||||
|
|
||||||
|
notify: (msg, title = 'platypush', error = false) => {
|
||||||
|
const port = browser.runtime.connect({ name: 'notify' });
|
||||||
|
port.postMessage({
|
||||||
|
type: 'run',
|
||||||
|
message: msg,
|
||||||
|
title: title,
|
||||||
|
error: error,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
}`,
|
||||||
|
};
|
||||||
|
|
||||||
|
// vim:sw=2:ts=2:et:
|
91
src/utils.js
91
src/utils.js
|
@ -1,6 +1,6 @@
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import Mercury from '@postlight/mercury-parser';
|
|
||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
|
import _script from './script';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
data() {
|
data() {
|
||||||
|
@ -10,8 +10,28 @@ export default {
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
notify(message, title = 'platypush') {
|
async notify(message, title = 'platypush', error = false) {
|
||||||
browser.notifications.create({
|
let msg = '';
|
||||||
|
if (title && title.length) {
|
||||||
|
msg = `${title}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (message && message.length) {
|
||||||
|
if (msg.length > 0) {
|
||||||
|
msg += ': ';
|
||||||
|
}
|
||||||
|
msg += message;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (msg.length) {
|
||||||
|
if (error) {
|
||||||
|
console.error(msg);
|
||||||
|
} else {
|
||||||
|
console.log(msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await browser.notifications.create({
|
||||||
type: 'basic',
|
type: 'basic',
|
||||||
title: title,
|
title: title,
|
||||||
message: message,
|
message: message,
|
||||||
|
@ -25,7 +45,7 @@ export default {
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!tabs.length) {
|
if (!tabs.length) {
|
||||||
this.notify('', 'No active tab');
|
await this.notify('No active tab', '', true);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -78,7 +98,7 @@ export default {
|
||||||
}, {});
|
}, {});
|
||||||
} else {
|
} else {
|
||||||
args = Object.entries(args)
|
args = Object.entries(args)
|
||||||
.filter(([name, value]) => value != null && value.length)
|
.filter(([, value]) => value != null && value.length)
|
||||||
.reduce((obj, [name, value]) => {
|
.reduce((obj, [name, value]) => {
|
||||||
obj[name] = value;
|
obj[name] = value;
|
||||||
return obj;
|
return obj;
|
||||||
|
@ -86,8 +106,8 @@ export default {
|
||||||
}
|
}
|
||||||
|
|
||||||
Object.keys(args).forEach(name => {
|
Object.keys(args).forEach(name => {
|
||||||
|
// URL wildcard
|
||||||
if (args[name] === '$URL$') {
|
if (args[name] === '$URL$') {
|
||||||
// URL wildcard
|
|
||||||
if (!currentURL) {
|
if (!currentURL) {
|
||||||
console.warn('Unable to get the current URL');
|
console.warn('Unable to get the current URL');
|
||||||
} else {
|
} else {
|
||||||
|
@ -115,28 +135,46 @@ export default {
|
||||||
|
|
||||||
const errors = msg.data.response.errors;
|
const errors = msg.data.response.errors;
|
||||||
if (errors && errors.length) {
|
if (errors && errors.length) {
|
||||||
throw new Error(errors[0]);
|
// noinspection ExceptionCaughtLocallyJS
|
||||||
|
throw errors[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
return msg.data.response.output;
|
return msg.data.response.output;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
this.notify(e.toString(), 'Request error');
|
await this.notify(e.toString(), 'Request error');
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
prepareScript(script, host, tab, target, ...args) {
|
||||||
|
args = JSON.stringify({
|
||||||
|
host: host,
|
||||||
|
tabId: tab ? tab.id : null,
|
||||||
|
target: typeof target === 'object' ? target.outerHTML : target,
|
||||||
|
...args,
|
||||||
|
});
|
||||||
|
|
||||||
|
return `(${script})(${_script.api}, ${args})`;
|
||||||
|
},
|
||||||
|
|
||||||
async runScript(script, host, tab, target, ...args) {
|
async runScript(script, host, tab, target, ...args) {
|
||||||
this.loading = true;
|
this.loading = true;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (typeof script === 'string') {
|
if (!tab) {
|
||||||
/* eslint no-eval: "off" */
|
tab = await this.getCurrentTab();
|
||||||
script = eval(this.script);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return await script(this, host, browser, tab, target, ...args);
|
if (!tab) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const code = this.prepareScript(script, host, tab, target, ...args);
|
||||||
|
return await browser.tabs.executeScript(tab.id, {
|
||||||
|
code: code,
|
||||||
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
this.notify(e.message, 'Script error');
|
await this.notify(e.message, 'Script error', true);
|
||||||
throw e;
|
throw e;
|
||||||
} finally {
|
} finally {
|
||||||
this.loading = false;
|
this.loading = false;
|
||||||
|
@ -182,7 +220,7 @@ export default {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
async getScripts(parse = true) {
|
async getScripts() {
|
||||||
this.loading = true;
|
this.loading = true;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
@ -192,10 +230,6 @@ export default {
|
||||||
}
|
}
|
||||||
|
|
||||||
return Object.entries(JSON.parse(response.scripts)).reduce((obj, [name, info]) => {
|
return Object.entries(JSON.parse(response.scripts)).reduce((obj, [name, info]) => {
|
||||||
if (parse && typeof info.script === 'string') {
|
|
||||||
info.script = eval(info.script);
|
|
||||||
}
|
|
||||||
|
|
||||||
obj[name] = info;
|
obj[name] = info;
|
||||||
return obj;
|
return obj;
|
||||||
}, {});
|
}, {});
|
||||||
|
@ -224,7 +258,7 @@ export default {
|
||||||
|
|
||||||
actions[action.displayName] = action;
|
actions[action.displayName] = action;
|
||||||
await this.saveActions(actions);
|
await this.saveActions(actions);
|
||||||
this.notify('You can find this action under the Local Actions menu', 'Action saved');
|
await this.notify('You can find this action under the Local Actions menu', 'Action saved');
|
||||||
},
|
},
|
||||||
|
|
||||||
async saveScripts(scripts) {
|
async saveScripts(scripts) {
|
||||||
|
@ -242,7 +276,7 @@ export default {
|
||||||
|
|
||||||
await browser.storage.local.set({ scripts: JSON.stringify(scripts) });
|
await browser.storage.local.set({ scripts: JSON.stringify(scripts) });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
this.notify(e.message, 'Error on script save');
|
await this.notify(e.message, 'Error on script save');
|
||||||
} finally {
|
} finally {
|
||||||
this.loading = false;
|
this.loading = false;
|
||||||
}
|
}
|
||||||
|
@ -258,7 +292,7 @@ export default {
|
||||||
|
|
||||||
scripts[script.displayName] = script;
|
scripts[script.displayName] = script;
|
||||||
await this.saveScripts(scripts);
|
await this.saveScripts(scripts);
|
||||||
this.notify('You can find this script under the Local Actions menu', 'Script saved');
|
await this.notify('You can find this script under the Local Actions menu', 'Script saved');
|
||||||
},
|
},
|
||||||
|
|
||||||
async loadConfig() {
|
async loadConfig() {
|
||||||
|
@ -293,7 +327,7 @@ export default {
|
||||||
if (typeof host === 'string') {
|
if (typeof host === 'string') {
|
||||||
const hosts = await this.getHosts();
|
const hosts = await this.getHosts();
|
||||||
if (!(host in hosts)) {
|
if (!(host in hosts)) {
|
||||||
this.notify(host, 'No such Platypush host');
|
await this.notify(host, 'No such Platypush host');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -325,7 +359,7 @@ export default {
|
||||||
host
|
host
|
||||||
);
|
);
|
||||||
|
|
||||||
this.notify(`Configugration successfully backed up to ${host.name}`, 'Backup successful');
|
await this.notify(`Configuration successfully backed up to ${host.name}`, 'Backup successful');
|
||||||
} finally {
|
} finally {
|
||||||
this.loading = false;
|
this.loading = false;
|
||||||
}
|
}
|
||||||
|
@ -335,7 +369,7 @@ export default {
|
||||||
if (typeof host === 'string') {
|
if (typeof host === 'string') {
|
||||||
const hosts = await this.getHosts();
|
const hosts = await this.getHosts();
|
||||||
if (!(host in hosts)) {
|
if (!(host in hosts)) {
|
||||||
this.notify(host, 'No such Platypush host');
|
await this.notify(host, 'No such Platypush host');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -347,15 +381,13 @@ export default {
|
||||||
const filename = `${basedir}/config.json`;
|
const filename = `${basedir}/config.json`;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const config = await this.run(
|
return await this.run(
|
||||||
{
|
{
|
||||||
name: 'file.read',
|
name: 'file.read',
|
||||||
args: { file: filename },
|
args: { file: filename },
|
||||||
},
|
},
|
||||||
host
|
host
|
||||||
);
|
);
|
||||||
|
|
||||||
return config;
|
|
||||||
} finally {
|
} finally {
|
||||||
this.loading = false;
|
this.loading = false;
|
||||||
}
|
}
|
||||||
|
@ -395,11 +427,6 @@ export default {
|
||||||
return form.name.value.length && form.address.value.length && this.isPortValid(form.port.value) && this.isPortValid(form.websocketPort.value);
|
return form.name.value.length && form.address.value.length && this.isPortValid(form.port.value) && this.isPortValid(form.websocketPort.value);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
created() {
|
|
||||||
this.$axios = axios;
|
|
||||||
this.$mercury = Mercury;
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const bus = new Vue();
|
export const bus = new Vue();
|
||||||
|
|
Loading…
Reference in a new issue