Skip to content

Commit dc52e23

Browse files
committed
Change socket implementation for servers
1 parent e0fda58 commit dc52e23

File tree

9 files changed

+242
-17
lines changed

9 files changed

+242
-17
lines changed

CHANGELOG.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,7 @@ This project follows [Semantic Versioning](http://semver.org) guidelines.
171171
* Nest and Egg listings now show the associated ID in order to make API requests easier.
172172
* Added star indicators to user listing in Admin CP to indicate users who are set as a root admin.
173173
* Creating a new node will now requires a SSL connection if the Panel is configured to use SSL as well.
174-
* Socketio error messages due to permissions are now rendered correctly in the UI rather than causing a silent failure.
174+
* Connector error messages due to permissions are now rendered correctly in the UI rather than causing a silent failure.
175175
* File manager now supports mass deletion option for files and folders.
176176
* Support for CS:GO as a default service option selection.
177177
* Support for GMOD as a default service option selection.
@@ -301,7 +301,7 @@ This project follows [Semantic Versioning](http://semver.org) guidelines.
301301
* Changed 2FA login process to be more secure. Previously authentication checking happened on the 2FA post page, now it happens prior and is passed along to the 2FA page to avoid storing any credentials.
302302

303303
### Added
304-
* Socketio error messages due to permissions are now rendered correctly in the UI rather than causing a silent failure.
304+
* Connector error messages due to permissions are now rendered correctly in the UI rather than causing a silent failure.
305305

306306
## v0.7.0-beta.1 (Derelict Dermodactylus)
307307
### Added

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
"vue": "^2.5.7",
99
"vue-axios": "^2.1.1",
1010
"vue-router": "^3.0.1",
11-
"vue-socket.io-extended": "^3.1.0",
1211
"vuex": "^3.0.1",
1312
"vuex-i18n": "^1.10.5",
1413
"vuex-router-sync": "^5.0.0",
@@ -29,6 +28,7 @@
2928
"babel-plugin-transform-runtime": "^6.23.0",
3029
"babel-plugin-transform-strict-mode": "^6.18.0",
3130
"babel-register": "^6.26.0",
31+
"camelcase": "^5.0.0",
3232
"clean-webpack-plugin": "^0.1.19",
3333
"css-loader": "^0.28.11",
3434
"extract-text-webpack-plugin": "^4.0.0-beta.0",

resources/assets/scripts/components/server/Server.vue

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -70,18 +70,19 @@
7070
import Navigation from '../core/Navigation';
7171
import ProgressBar from './components/ProgressBar';
7272
import { mapState } from 'vuex';
73-
import VueSocketio from 'vue-socket.io-extended';
7473
import io from 'socket.io-client';
75-
import Vue from 'vue';
74+
import { Socketio } from './../../mixins/socketio';
7675
7776
import PowerButtons from './components/PowerButtons';
7877
import Flash from '../Flash';
7978
8079
export default {
80+
name: 'server',
81+
mixins: [Socketio],
8182
components: {
8283
Flash,
8384
PowerButtons, ProgressBar, Navigation,
84-
TerminalIcon, FolderIcon, UsersIcon, CalendarIcon, DatabaseIcon, GlobeIcon, SettingsIcon
85+
TerminalIcon, FolderIcon, UsersIcon, CalendarIcon, DatabaseIcon, GlobeIcon, SettingsIcon,
8586
},
8687
8788
computed: {
@@ -95,10 +96,20 @@
9596
};
9697
},
9798
99+
sockets: {
100+
'console': function () {
101+
console.log('server CONSOLE');
102+
},
103+
},
104+
98105
mounted: function () {
99106
this.loadServer();
100107
},
101108
109+
beforeDestroy: function () {
110+
this.removeSocket();
111+
},
112+
102113
methods: {
103114
/**
104115
* Load the core server information needed for these pages to be functional.
@@ -115,7 +126,7 @@
115126
query: `token=${this.credentials.key}`,
116127
});
117128
118-
Vue.use(VueSocketio, socket, { store: this.$store });
129+
this.$socket().connect(socket);
119130
this.loadingServerData = false;
120131
})
121132
.catch(console.error);

resources/assets/scripts/components/server/components/PowerButtons.vue

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,12 @@
2424

2525
<script>
2626
import Status from './../../../helpers/statuses';
27+
import { Socketio } from './../../../mixins/socketio';
2728
import { mapState } from 'vuex';
2829
2930
export default {
3031
name: 'power-buttons',
31-
32+
mixins: [Socketio],
3233
computed: {
3334
...mapState('socket', ['connected', 'status']),
3435
},
@@ -41,7 +42,7 @@
4142
4243
methods: {
4344
sendPowerAction: function (action) {
44-
this.$socket.emit('set status', action)
45+
this.$socket().instance().emit('set status', action)
4546
},
4647
},
4748
};

resources/assets/scripts/components/server/subpages/Console.vue

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,10 +28,12 @@
2828
import { Terminal } from 'xterm';
2929
import * as TerminalFit from 'xterm/lib/addons/fit/fit';
3030
import {mapState} from 'vuex';
31+
import {Socketio} from './../../../mixins/socketio';
3132
3233
Terminal.applyAddon(TerminalFit);
3334
3435
export default {
36+
mixins: [Socketio],
3537
name: 'console-page',
3638
computed: {
3739
...mapState('socket', ['connected']),
@@ -103,7 +105,7 @@
103105
this.terminal.fit();
104106
this.terminal.clear();
105107
106-
this.$socket.emit('send server log');
108+
this.$socket().instance().emit('send server log');
107109
},
108110
109111
/**
@@ -112,7 +114,7 @@
112114
sendCommand: function () {
113115
this.commandHistoryIndex = -1;
114116
this.commandHistory.unshift(this.command);
115-
this.$socket.emit('send command', this.command);
117+
this.$socket().instance().emit('send command', this.command);
116118
this.command = '';
117119
},
118120
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
import io from 'socket.io-client';
2+
import camelCase from 'camelcase';
3+
import SocketEmitter from './emitter';
4+
5+
const SYSTEM_EVENTS = [
6+
'connect',
7+
'error',
8+
'disconnect',
9+
'reconnect',
10+
'reconnect_attempt',
11+
'reconnecting',
12+
'reconnect_error',
13+
'reconnect_failed',
14+
'connect_error',
15+
'connect_timeout',
16+
'connecting',
17+
'ping',
18+
'pong',
19+
];
20+
21+
export default class SocketioConnector {
22+
constructor (store = null) {
23+
this.socket = null;
24+
this.store = store;
25+
}
26+
27+
/**
28+
* Initialize a new Socket connection.
29+
*
30+
* @param {io} socket
31+
*/
32+
connect (socket) {
33+
if (!socket instanceof io) {
34+
throw new Error('First argument passed to connect() should be an instance of socket.io-client.');
35+
}
36+
37+
this.socket = socket;
38+
this.registerEventListeners();
39+
}
40+
41+
/**
42+
* Return the socket instance we are working with.
43+
*
44+
* @return {io|null}
45+
*/
46+
instance () {
47+
return this.socket;
48+
}
49+
50+
/**
51+
* Register the event listeners for this socket including user-defined ones in the store as
52+
* well as global system events from Socekt.io.
53+
*/
54+
registerEventListeners () {
55+
this.socket['onevent'] = (packet) => {
56+
const [event, ...args] = packet.data;
57+
SocketEmitter.emit(event, ...args);
58+
this.passToStore(event, args);
59+
};
60+
61+
SYSTEM_EVENTS.forEach((event) => {
62+
this.socket.on(event, (payload) => {
63+
SocketEmitter.emit(event, payload);
64+
this.passToStore(event, payload);
65+
})
66+
});
67+
}
68+
69+
/**
70+
* Pass event calls off to the Vuex store if there is a corresponding function.
71+
*
72+
* @param {String|Number|Symbol} event
73+
* @param {Array} payload
74+
*/
75+
passToStore (event, payload) {
76+
if (!this.store) {
77+
return;
78+
}
79+
80+
const mutation = `SOCKET_${event.toUpperCase()}`;
81+
const action = `socket_${camelCase(event)}`;
82+
83+
Object.keys(this.store._mutations).filter((namespaced) => {
84+
return namespaced.split('/').pop() === mutation;
85+
}).forEach((namespaced) => {
86+
this.store.commit(namespaced, this.unwrap(payload));
87+
});
88+
89+
Object.keys(this.store._actions).filter((namespaced) => {
90+
return namespaced.split('/').pop() === action;
91+
}).forEach((namespaced) => {
92+
this.store.dispatch(namespaced, this.unwrap(payload));
93+
});
94+
}
95+
96+
/**
97+
* @param {Array} args
98+
* @return {Array<Object>|Object}
99+
*/
100+
unwrap (args) {
101+
return (args && args.length <= 1) ? args[0] : args;
102+
}
103+
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import isFunction from 'lodash/isFunction';
2+
3+
export default new class SocketEmitter {
4+
constructor () {
5+
this.listeners = new Map();
6+
}
7+
8+
/**
9+
* Add an event listener for socket events.
10+
*
11+
* @param {String|Number|Symbol} event
12+
* @param {Function} callback
13+
* @param {*} vm
14+
*/
15+
addListener (event, callback, vm) {
16+
if (!isFunction(callback)) {
17+
return;
18+
}
19+
20+
if (!this.listeners.has(event)) {
21+
this.listeners.set(event, []);
22+
}
23+
24+
this.listeners.get(event).push({callback, vm});
25+
}
26+
27+
/**
28+
* Remove an event listener for socket events based on the context passed through.
29+
*
30+
* @param {String|Number|Symbol} event
31+
* @param {Function} callback
32+
* @param {*} vm
33+
*/
34+
removeListener (event, callback, vm) {
35+
if (!isFunction(callback) || !this.listeners.has(event)) {
36+
return;
37+
}
38+
39+
const filtered = this.listeners.get(event).filter((listener) => {
40+
return listener.callback !== callback || listener.vm !== vm;
41+
});
42+
43+
if (filtered.length > 0) {
44+
this.listeners.set(event, filtered);
45+
} else {
46+
this.listeners.delete(event);
47+
}
48+
}
49+
50+
/**
51+
* Emit a socket event.
52+
*
53+
* @param {String|Number|Symbol} event
54+
* @param {Array} args
55+
*/
56+
emit (event, ...args) {
57+
(this.listeners.get(event) || []).forEach((listener) => {
58+
listener.callback.call(listener.vm, ...args);
59+
});
60+
}
61+
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import SocketEmitter from './emitter';
2+
import SocketioConnector from './connector';
3+
4+
let connector = null;
5+
6+
export const Socketio = {
7+
/**
8+
* Setup the socket when we create the first component using the mixin. This is the Server.vue
9+
* file, unless you mess up all of this code. Subsequent components to use this mixin will
10+
* receive the existing connector instance, so it is very important that the top-most component
11+
* calls the connectors destroy function when it is destroyed.
12+
*/
13+
created: function () {
14+
if (!connector) {
15+
connector = new SocketioConnector(this.$store);
16+
}
17+
18+
const sockets = this.$options.sockets || {};
19+
Object.keys(sockets).forEach((event) => {
20+
SocketEmitter.addListener(event, sockets[event], this);
21+
});
22+
},
23+
24+
/**
25+
* Before destroying the component we need to remove any event listeners registered for it.
26+
*/
27+
beforeDestroy: function () {
28+
const sockets = this.$options.sockets || {};
29+
Object.keys(sockets).forEach((event) => {
30+
SocketEmitter.removeListener(event, sockets[event], this);
31+
});
32+
},
33+
34+
methods: {
35+
/**
36+
* @return {SocketioConnector}
37+
*/
38+
'$socket': function () {
39+
return connector;
40+
},
41+
42+
/**
43+
* Disconnects from the active socket and sets the connector to null.
44+
*/
45+
removeSocket: function () {
46+
if (connector !== null && connector.instance() !== null) {
47+
connector.instance().close();
48+
}
49+
50+
connector = null;
51+
},
52+
},
53+
};

yarn.lock

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6880,12 +6880,6 @@ vue-router@^3.0.1:
68806880
version "3.0.1"
68816881
resolved "https://registry.yarnpkg.com/vue-router/-/vue-router-3.0.1.tgz#d9b05ad9c7420ba0f626d6500d693e60092cc1e9"
68826882

6883-
vue-socket.io-extended@^3.1.0:
6884-
version "3.1.0"
6885-
resolved "https://registry.yarnpkg.com/vue-socket.io-extended/-/vue-socket.io-extended-3.1.0.tgz#0c1833377f902381c861c43a403a476eee542bc9"
6886-
dependencies:
6887-
camelcase "^5.0.0"
6888-
68896883
vue-style-loader@^4.0.1:
68906884
version "4.1.0"
68916885
resolved "https://registry.yarnpkg.com/vue-style-loader/-/vue-style-loader-4.1.0.tgz#7588bd778e2c9f8d87bfc3c5a4a039638da7a863"

0 commit comments

Comments
 (0)