Skip to content

Commit e87c5f6

Browse files
committed
Implement basic support for connecting to wings console via websocket rather than socketio
1 parent 0757d88 commit e87c5f6

File tree

12 files changed

+122
-117
lines changed

12 files changed

+122
-117
lines changed

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,15 @@
66
"feather-icons": "^4.10.0",
77
"jquery": "^3.3.1",
88
"lodash": "^4.17.11",
9-
"socket.io-client": "^2.1.1",
9+
"socket.io-client": "^2.2.0",
1010
"vee-validate": "^2.1.7",
1111
"vue": "^2.6.4",
1212
"vue-axios": "^2.1.1",
1313
"vue-i18n": "^8.6.0",
1414
"vue-router": "^3.0.1",
1515
"vuex": "^3.0.1",
1616
"vuex-router-sync": "^5.0.0",
17+
"ws-wrapper": "^2.0.0",
1718
"xterm": "^3.5.1"
1819
},
1920
"devDependencies": {

public/themes/pterodactyl/js/frontend/console.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ $(document).ready(function () {
6565

6666
if (e.which === 13) {
6767
saveToHistory($(this).val());
68-
Socket.emit((ConsoleServerStatus !== 0) ? 'send command' : 'set status', $(this).val());
68+
Socket.emit((ConsoleServerStatus !== 0) ? 'send command' : 'set state', $(this).val());
6969

7070
$(this).val('');
7171
}
@@ -233,7 +233,7 @@ function updateServerPowerControls (data) {
233233
$(document).ready(function () {
234234
$('[data-attr="power"]').click(function (event) {
235235
if (! $(this).hasClass('disabled')) {
236-
Socket.emit('set status', $(this).data('action'));
236+
Socket.emit('set state', $(this).data('action'));
237237
}
238238
});
239239

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

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,11 @@
3434
Databases
3535
</router-link>
3636
</li>
37+
<li>
38+
<router-link :to="{ name: 'server-network' }">
39+
Networking
40+
</router-link>
41+
</li>
3742
</ul>
3843
</div>
3944
</div>
@@ -54,7 +59,6 @@
5459
import Vue from 'vue';
5560
import Navigation from '@/components/core/Navigation.vue';
5661
import {mapState} from 'vuex';
57-
import * as io from 'socket.io-client';
5862
import {Socketio} from "@/mixins/socketio";
5963
import PowerButtons from "@/components/server/components/PowerButtons.vue";
6064
import Flash from "@/components/Flash.vue";
@@ -105,13 +109,12 @@
105109
this.$store.dispatch('server/getCredentials', {server: this.$route.params.id})
106110
])
107111
.then(() => {
108-
// Configure the socket.io implementation. This is a really ghetto way of handling things
109-
// but all of these plugins assume you have some constant connection, which we don't.
110-
const socket = io(`${this.credentials.node}/v1/ws/${this.server.uuid}`, {
111-
query: `token=${this.credentials.key}`,
112-
});
112+
// Configure the websocket implementation and assign it to the mixin.
113+
this.$socket().connect(
114+
`ws://192.168.50.3:8080/api/servers/${this.server.uuid}/ws`,
115+
'CC8kHCuMkXPosgzGO6d37wvhNcksWxG6kTrA',
116+
);
113117
114-
this.$socket().connect(socket);
115118
this.loadingServerData = false;
116119
})
117120
.catch(err => {

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
<div v-if="connected">
44
<transition name="slide-fade" mode="out-in">
55
<button class="btn btn-green uppercase text-xs px-4 py-2"
6-
v-if="status === statuses.STATUS_OFF"
6+
v-if="status === 'offline'"
77
v-on:click.prevent="sendPowerAction('start')"
88
>Start
99
</button>
@@ -45,7 +45,7 @@
4545
4646
methods: {
4747
sendPowerAction: function (action: string) {
48-
this.$socket().instance().emit('set status', action)
48+
this.$socket().emit('set state', action)
4949
},
5050
},
5151
});

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

Lines changed: 6 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -64,20 +64,12 @@
6464
* Listen for specific socket.io emits from the server.
6565
*/
6666
sockets: {
67-
'server log': function (data: string) {
68-
data.split(/\n/g).forEach((line: string): void => {
69-
if (this.terminal) {
70-
this.terminal.writeln(line + '\u001b[0m');
71-
}
72-
});
67+
'server log': function (lines: Array<string>) {
68+
lines.forEach(data => data.split(/\n/g).forEach(line => this.terminal && this.terminal.writeln(line + '\u001b[0m')));
7369
},
7470
75-
'console': function (data: { line: string }) {
76-
data.line.split(/\n/g).forEach((line: string): void => {
77-
if (this.terminal) {
78-
this.terminal.writeln(line + '\u001b[0m');
79-
}
80-
});
71+
'console output': function (line: string) {
72+
this.terminal && this.terminal.writeln(line.replace(/(?:\r\n|\r|\n)$/im, '') + '\u001b[0m');
8173
},
8274
},
8375
@@ -114,7 +106,7 @@
114106
this.terminal.fit();
115107
this.terminal.clear();
116108
117-
this.$socket().instance().emit('send server log');
109+
this.$socket().emit('send logs');
118110
},
119111
120112
/**
@@ -123,7 +115,7 @@
123115
sendCommand: function () {
124116
this.commandHistoryIndex = -1;
125117
this.commandHistory.unshift(this.command);
126-
this.$socket().instance().emit('send command', this.command);
118+
this.$socket().emit('send command', this.command);
127119
this.command = '';
128120
},
129121
Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
export default {
2-
STATUS_OFF: 0,
3-
STATUS_ON: 1,
4-
STATUS_STARTING: 2,
5-
STATUS_STOPPING: 3,
2+
STATUS_OFF: 'offline',
3+
STATUS_ON: 'running',
4+
STATUS_STARTING: 'starting',
5+
STATUS_STOPPING: 'stopping',
66
};

resources/assets/scripts/helpers/ziggy.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 60 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,23 @@
1-
import * as io from 'socket.io-client';
21
import {camelCase} from 'lodash';
32
import SocketEmitter from './emitter';
43
import {Store} from "vuex";
54

6-
const SYSTEM_EVENTS: Array<string> = [
7-
'connect',
8-
'error',
9-
'disconnect',
10-
'reconnect',
11-
'reconnect_attempt',
12-
'reconnecting',
13-
'reconnect_error',
14-
'reconnect_failed',
15-
'connect_error',
16-
'connect_timeout',
17-
'connecting',
18-
'ping',
19-
'pong',
20-
];
5+
const SOCKET_CONNECT = 'connect';
6+
const SOCKET_ERROR = 'error';
7+
const SOCKET_DISCONNECT = 'disconnect';
8+
9+
// This is defined in the wings daemon code and referenced here so that it is obvious
10+
// where we are pulling these random data objects from.
11+
type WingsWebsocketResponse = {
12+
event: string,
13+
args: Array<string>
14+
}
2115

2216
export default class SocketioConnector {
2317
/**
2418
* The socket instance.
2519
*/
26-
socket: null | SocketIOClient.Socket;
20+
socket: null | WebSocket;
2721

2822
/**
2923
* The vuex store being used to persist data and socket state.
@@ -37,21 +31,33 @@ export default class SocketioConnector {
3731

3832
/**
3933
* Initialize a new Socket connection.
40-
*
41-
* @param {io} socket
4234
*/
43-
connect(socket: SocketIOClient.Socket) {
44-
this.socket = socket;
35+
connect(url: string, protocols?: string | string[]): void {
36+
this.socket = new WebSocket(url, protocols);
4537
this.registerEventListeners();
4638
}
4739

4840
/**
4941
* Return the socket instance we are working with.
5042
*/
51-
instance(): SocketIOClient.Socket | null {
43+
instance(): WebSocket | null {
5244
return this.socket;
5345
}
5446

47+
/**
48+
* Sends an event along to the websocket. If there is no active connection, a void
49+
* result is returned.
50+
*/
51+
emit(event: string, payload?: string | Array<string>): void | false {
52+
if (!this.socket) {
53+
return false
54+
}
55+
56+
this.socket.send(JSON.stringify({
57+
event, args: typeof payload === 'string' ? [payload] : payload
58+
}));
59+
}
60+
5561
/**
5662
* Register the event listeners for this socket including user-defined ones in the store as
5763
* well as global system events from Socekt.io.
@@ -61,55 +67,66 @@ export default class SocketioConnector {
6167
return;
6268
}
6369

64-
// @ts-ignore
65-
this.socket['onevent'] = (packet: { data: Array<any> }): void => {
66-
const [event, ...args] = packet.data;
67-
SocketEmitter.emit(event, ...args);
68-
69-
this.passToStore(event, args);
70+
this.socket.onopen = () => this.emitAndPassToStore(SOCKET_CONNECT);
71+
this.socket.onclose = () => this.emitAndPassToStore(SOCKET_DISCONNECT);
72+
this.socket.onerror = () => {
73+
// @todo reconnect?
74+
if (this.socket && this.socket.readyState !== 1) {
75+
this.emitAndPassToStore(SOCKET_ERROR, ['Failed to connect to websocket.']);
76+
}
7077
};
7178

72-
SYSTEM_EVENTS.forEach((event: string): void => {
73-
if (!this.socket) {
74-
return;
75-
}
79+
this.socket.onmessage = (wse): void => {
80+
console.log('Socket message:', wse.data);
7681

77-
this.socket.on(event, (payload: any) => {
78-
SocketEmitter.emit(event, payload);
82+
try {
83+
let {event, args}: WingsWebsocketResponse = JSON.parse(wse.data);
7984

80-
this.passToStore(event, payload);
81-
});
82-
});
85+
this.emitAndPassToStore(event, args);
86+
} catch (ex) {
87+
// do nothing, bad JSON response
88+
console.error(ex);
89+
return
90+
}
91+
};
92+
}
93+
94+
/**
95+
* Emits the event over the event emitter and also passes it along to the vuex store.
96+
*/
97+
emitAndPassToStore(event: string, payload?: Array<string>) {
98+
payload ? SocketEmitter.emit(event, ...payload) : SocketEmitter.emit(event);
99+
this.passToStore(event, payload);
83100
}
84101

85102
/**
86103
* Pass event calls off to the Vuex store if there is a corresponding function.
87104
*/
88-
passToStore(event: string | number, payload: Array<any>) {
105+
passToStore(event: string, payload?: Array<string>) {
89106
if (!this.store) {
90107
return;
91108
}
92109

93110
const s: Store<any> = this.store;
94-
const mutation = `SOCKET_${String(event).toUpperCase()}`;
95-
const action = `socket_${camelCase(String(event))}`;
111+
const mutation = `SOCKET_${event.toUpperCase()}`;
112+
const action = `socket_${camelCase(event)}`;
96113

97114
// @ts-ignore
98115
Object.keys(this.store._mutations).filter((namespaced: string): boolean => {
99116
return namespaced.split('/').pop() === mutation;
100117
}).forEach((namespaced: string): void => {
101-
s.commit(namespaced, this.unwrap(payload));
118+
s.commit(namespaced, payload ? this.unwrap(payload) : null);
102119
});
103120

104121
// @ts-ignore
105122
Object.keys(this.store._actions).filter((namespaced: string): boolean => {
106123
return namespaced.split('/').pop() === action;
107124
}).forEach((namespaced: string): void => {
108-
s.dispatch(namespaced, this.unwrap(payload)).catch(console.error);
125+
s.dispatch(namespaced, payload ? this.unwrap(payload) : null).catch(console.error);
109126
});
110127
}
111128

112-
unwrap(args: Array<any>) {
129+
unwrap(args: Array<string>) {
113130
return (args && args.length <= 1) ? args[0] : args;
114131
}
115132
}

resources/assets/scripts/mixins/socketio/index.ts

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -34,10 +34,7 @@ export const Socketio: ComponentOptions<Vue> = {
3434
},
3535

3636
methods: {
37-
/**
38-
* @return {SocketioConnector}
39-
*/
40-
'$socket': function () {
37+
'$socket': function (): SocketioConnector | null {
4138
return connector;
4239
},
4340

@@ -49,7 +46,7 @@ export const Socketio: ComponentOptions<Vue> = {
4946
return;
5047
}
5148

52-
const instance: SocketIOClient.Socket | null = connector.instance();
49+
const instance = connector.instance();
5350
if (instance) {
5451
instance.close();
5552
}

resources/assets/scripts/store/modules/socket.ts

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -19,17 +19,12 @@ export default {
1919
state.connectionError = err;
2020
},
2121

22-
SOCKET_CONNECT_ERROR: (state: SocketState, err: Error) => {
23-
state.connected = false;
24-
state.connectionError = err;
25-
},
26-
27-
'SOCKET_INITIAL STATUS': (state: SocketState, data: { status: number }) => {
28-
state.status = data.status;
22+
'SOCKET_INITIAL STATUS': (state: SocketState, data: string) => {
23+
state.status = data;
2924
},
3025

31-
SOCKET_STATUS: (state: SocketState, data: { status: number }) => {
32-
state.status = data.status;
26+
SOCKET_STATUS: (state: SocketState, data: string) => {
27+
state.status = data;
3328
}
3429
},
3530
};

0 commit comments

Comments
 (0)