Cleanup socketio stuff for typescript

This commit is contained in:
Dane Everitt 2018-12-16 18:57:34 -08:00
parent 3ad4422a94
commit 5e4ca8ef83
No known key found for this signature in database
GPG Key ID: EEA66103B3D71F53
22 changed files with 246 additions and 210 deletions

View File

@ -20,7 +20,9 @@
"@babel/plugin-transform-async-to-generator": "^7.0.0-beta.49",
"@babel/plugin-transform-runtime": "^7.0.0-beta.49",
"@babel/preset-env": "^7.0.0-beta.49",
"@types/lodash": "^4.14.119",
"@types/node": "^10.12.15",
"@types/socket.io-client": "^1.4.32",
"@types/webpack-env": "^1.13.6",
"autoprefixer": "^8.2.0",
"axios": "^0.18.0",
@ -33,7 +35,6 @@
"babel-plugin-transform-runtime": "^6.23.0",
"babel-plugin-transform-strict-mode": "^6.18.0",
"babel-register": "^6.26.0",
"camelcase": "^5.0.0",
"clean-webpack-plugin": "^0.1.19",
"css-loader": "^0.28.11",
"eslint": "^5.6.0",

View File

@ -69,7 +69,7 @@
import ProgressBar from './components/ProgressBar';
import { mapState } from 'vuex';
import io from 'socket.io-client';
import { Socketio } from './../../mixins/socketio';
import { Socketio } from '../../mixins/socketio/index';
import PowerButtons from './components/PowerButtons';
import Flash from '../Flash';

View File

@ -24,7 +24,7 @@
<script>
import Status from '../../../helpers/statuses';
import { Socketio } from './../../../mixins/socketio';
import { Socketio } from '../../../mixins/socketio/index';
import { mapState } from 'vuex';
export default {

View File

@ -15,7 +15,7 @@
</template>
<script>
import * as Helpers from './../../../../helpers/index';
import * as Helpers from '../../../../helpers/index';
import { FileTextIcon, Link2Icon } from 'vue-feather-icons';
import FileManagerContextMenu from './FileManagerContextMenu';

View File

@ -16,7 +16,7 @@
<script>
import { FolderIcon } from 'vue-feather-icons';
import { formatDate } from './../../../../helpers/index';
import { formatDate } from '../../../../helpers/index';
export default {
name: 'file-manager-folder-row',

View File

@ -28,7 +28,7 @@
import { Terminal } from 'xterm';
import * as TerminalFit from 'xterm/lib/addons/fit/fit';
import {mapState} from 'vuex';
import {Socketio} from './../../../mixins/socketio';
import {Socketio} from '../../../mixins/socketio/index';
Terminal.applyAddon(TerminalFit);

View File

@ -1,15 +1,18 @@
import axios, {AxiosResponse} from 'axios';
/**
* We'll load the axios HTTP library which allows us to easily issue requests
* to our Laravel back-end. This library automatically handles sending the
* CSRF token as a header based on the value of the "XSRF" token cookie.
*/
let axios = require('axios');
axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
axios.defaults.headers.common['Accept'] = 'application/json';
// Attach the response data to phpdebugbar so that we can see everything happening.
// @ts-ignore
if (typeof phpdebugbar !== 'undefined') {
axios.interceptors.response.use(function (response) {
axios.interceptors.response.use(function (response: AxiosResponse) {
// @ts-ignore
phpdebugbar.ajaxHandler.handle(response.request);
return response;

View File

@ -1,19 +1,16 @@
import format from 'date-fns/format';
import { format } from 'date-fns';
/**
* Return the human readable filesize for a given number of bytes. This
* uses 1024 as the base, so the response is denoted accordingly.
*
* @param {Number} bytes
* @return {String}
*/
export function readableSize (bytes) {
export function readableSize (bytes: number): string {
if (Math.abs(bytes) < 1024) {
return `${bytes} Bytes`;
}
let u = -1;
const units = ['KiB', 'MiB', 'GiB', 'TiB'];
let u: number = -1;
const units: Array<string> = ['KiB', 'MiB', 'GiB', 'TiB'];
do {
bytes /= 1024;
@ -25,10 +22,7 @@ export function readableSize (bytes) {
/**
* Format the given date as a human readable string.
*
* @param {String} date
* @return {String}
*/
export function formatDate (date) {
export function formatDate (date: string): string {
return format(date, 'MMM D, YYYY [at] HH:MM');
}

View File

@ -2,60 +2,50 @@ export const flash = {
methods: {
/**
* Flash a message to the event stream in the browser.
*
* @param {string} message
* @param {string} title
* @param {string} severity
*/
flash: function (message, title, severity = 'info') {
flash: function (message: string, title: string, severity: string = 'info'): void {
severity = severity || 'info';
if (['danger', 'fatal', 'error'].includes(severity)) {
severity = 'error';
}
// @ts-ignore
window.events.$emit('flash', { message, title, severity });
},
/**
* Clear all of the flash messages from the screen.
*/
clearFlashes: function () {
clearFlashes: function (): void {
// @ts-ignore
window.events.$emit('clear-flashes');
},
/**
* Helper function to flash a normal success message to the user.
*
* @param {string} message
*/
success: function (message) {
success: function (message: string): void {
this.flash(message, 'Success', 'success');
},
/**
* Helper function to flash a normal info message to the user.
*
* @param {string} message
*/
info: function (message) {
info: function (message: string): void {
this.flash(message, 'Info', 'info');
},
/**
* Helper function to flash a normal warning message to the user.
*
* @param {string} message
*/
warning: function (message) {
warning: function (message: string): void {
this.flash(message, 'Warning', 'warning');
},
/**
* Helper function to flash a normal error message to the user.
*
* @param {string} message
*/
error: function (message) {
error: function (message: string): void {
this.flash(message, 'Error', 'danger');
},
}

View File

@ -1,103 +0,0 @@
import io from 'socket.io-client';
import camelCase from 'camelcase';
import SocketEmitter from './emitter';
const SYSTEM_EVENTS = [
'connect',
'error',
'disconnect',
'reconnect',
'reconnect_attempt',
'reconnecting',
'reconnect_error',
'reconnect_failed',
'connect_error',
'connect_timeout',
'connecting',
'ping',
'pong',
];
export default class SocketioConnector {
constructor (store = null) {
this.socket = null;
this.store = store;
}
/**
* Initialize a new Socket connection.
*
* @param {io} socket
*/
connect (socket) {
if (!socket instanceof io) {
throw new Error('First argument passed to connect() should be an instance of socket.io-client.');
}
this.socket = socket;
this.registerEventListeners();
}
/**
* Return the socket instance we are working with.
*
* @return {io|null}
*/
instance () {
return this.socket;
}
/**
* Register the event listeners for this socket including user-defined ones in the store as
* well as global system events from Socekt.io.
*/
registerEventListeners () {
this.socket['onevent'] = (packet) => {
const [event, ...args] = packet.data;
SocketEmitter.emit(event, ...args);
this.passToStore(event, args);
};
SYSTEM_EVENTS.forEach((event) => {
this.socket.on(event, (payload) => {
SocketEmitter.emit(event, payload);
this.passToStore(event, payload);
})
});
}
/**
* Pass event calls off to the Vuex store if there is a corresponding function.
*
* @param {String|Number|Symbol} event
* @param {Array} payload
*/
passToStore (event, payload) {
if (!this.store) {
return;
}
const mutation = `SOCKET_${event.toUpperCase()}`;
const action = `socket_${camelCase(event)}`;
Object.keys(this.store._mutations).filter((namespaced) => {
return namespaced.split('/').pop() === mutation;
}).forEach((namespaced) => {
this.store.commit(namespaced, this.unwrap(payload));
});
Object.keys(this.store._actions).filter((namespaced) => {
return namespaced.split('/').pop() === action;
}).forEach((namespaced) => {
this.store.dispatch(namespaced, this.unwrap(payload));
});
}
/**
* @param {Array} args
* @return {Array<Object>|Object}
*/
unwrap (args) {
return (args && args.length <= 1) ? args[0] : args;
}
}

View File

@ -0,0 +1,115 @@
import * as io from 'socket.io-client';
import {camelCase} from 'lodash';
import SocketEmitter from './emitter';
import {Store} from "vuex";
const SYSTEM_EVENTS: Array<string> = [
'connect',
'error',
'disconnect',
'reconnect',
'reconnect_attempt',
'reconnecting',
'reconnect_error',
'reconnect_failed',
'connect_error',
'connect_timeout',
'connecting',
'ping',
'pong',
];
export default class SocketioConnector {
/**
* The socket instance.
*/
socket: null | SocketIOClient.Socket;
/**
* The vuex store being used to persist data and socket state.
*/
store: Store<any> | undefined;
constructor(store: Store<any> | undefined) {
this.socket = null;
this.store = store;
}
/**
* Initialize a new Socket connection.
*
* @param {io} socket
*/
connect(socket: SocketIOClient.Socket) {
this.socket = socket;
this.registerEventListeners();
}
/**
* Return the socket instance we are working with.
*/
instance(): SocketIOClient.Socket | null {
return this.socket;
}
/**
* Register the event listeners for this socket including user-defined ones in the store as
* well as global system events from Socekt.io.
*/
registerEventListeners() {
if (!this.socket) {
return;
}
// @ts-ignore
this.socket['onevent'] = (packet: { data: Array<any> }): void => {
const [event, ...args] = packet.data;
SocketEmitter.emit(event, ...args);
this.passToStore(event, args);
};
SYSTEM_EVENTS.forEach((event: string): void => {
if (!this.socket) {
return;
}
this.socket.on(event, (payload: any) => {
SocketEmitter.emit(event, payload);
this.passToStore(event, payload);
});
});
}
/**
* Pass event calls off to the Vuex store if there is a corresponding function.
*/
passToStore(event: string | number, payload: Array<any>) {
if (!this.store) {
return;
}
const s: Store<any> = this.store;
const mutation = `SOCKET_${String(event).toUpperCase()}`;
const action = `socket_${camelCase(String(event))}`;
// @ts-ignore
Object.keys(this.store._mutations).filter((namespaced: string): boolean => {
return namespaced.split('/').pop() === mutation;
}).forEach((namespaced: string): void => {
s.commit(namespaced, this.unwrap(payload));
});
// @ts-ignore
Object.keys(this.store._actions).filter((namespaced: string): boolean => {
return namespaced.split('/').pop() === action;
}).forEach((namespaced: string): void => {
s.dispatch(namespaced, this.unwrap(payload)).catch(console.error);
});
}
unwrap(args: Array<any>) {
return (args && args.length <= 1) ? args[0] : args;
}
}

View File

@ -1,18 +1,21 @@
import isFunction from 'lodash/isFunction';
import {isFunction} from 'lodash';
import {ComponentOptions} from "vue";
import {Vue} from "vue/types/vue";
export default new class SocketEmitter {
constructor () {
listeners: Map<string | number, Array<{
callback: (a: ComponentOptions<Vue>) => void,
vm: ComponentOptions<Vue>,
}>>;
constructor() {
this.listeners = new Map();
}
/**
* Add an event listener for socket events.
*
* @param {String|Number|Symbol} event
* @param {Function} callback
* @param {*} vm
*/
addListener (event, callback, vm) {
addListener(event: string | number, callback: (data: any) => void, vm: ComponentOptions<Vue>) {
if (!isFunction(callback)) {
return;
}
@ -21,21 +24,19 @@ export default new class SocketEmitter {
this.listeners.set(event, []);
}
// @ts-ignore
this.listeners.get(event).push({callback, vm});
}
/**
* Remove an event listener for socket events based on the context passed through.
*
* @param {String|Number|Symbol} event
* @param {Function} callback
* @param {*} vm
*/
removeListener (event, callback, vm) {
removeListener(event: string | number, callback: (data: any) => void, vm: ComponentOptions<Vue>) {
if (!isFunction(callback) || !this.listeners.has(event)) {
return;
}
// @ts-ignore
const filtered = this.listeners.get(event).filter((listener) => {
return listener.callback !== callback || listener.vm !== vm;
});
@ -49,13 +50,10 @@ export default new class SocketEmitter {
/**
* Emit a socket event.
*
* @param {String|Number|Symbol} event
* @param {Array} args
*/
emit (event, ...args) {
emit(event: string | number, ...args: any) {
(this.listeners.get(event) || []).forEach((listener) => {
listener.callback.call(listener.vm, ...args);
listener.callback.call(listener.vm, args);
});
}
}

View File

@ -1,9 +1,11 @@
import SocketEmitter from './emitter';
import SocketioConnector from './connector';
import {ComponentOptions} from 'vue';
import {Vue} from "vue/types/vue";
let connector = null;
let connector: SocketioConnector | null = null;
export const Socketio = {
export const Socketio: ComponentOptions<Vue> = {
/**
* Setup the socket when we create the first component using the mixin. This is the Server.vue
* file, unless you mess up all of this code. Subsequent components to use this mixin will
@ -15,7 +17,7 @@ export const Socketio = {
connector = new SocketioConnector(this.$store);
}
const sockets = this.$options.sockets || {};
const sockets = (this.$options || {}).sockets || {};
Object.keys(sockets).forEach((event) => {
SocketEmitter.addListener(event, sockets[event], this);
});
@ -25,7 +27,7 @@ export const Socketio = {
* Before destroying the component we need to remove any event listeners registered for it.
*/
beforeDestroy: function () {
const sockets = this.$options.sockets || {};
const sockets = (this.$options || {}).sockets || {};
Object.keys(sockets).forEach((event) => {
SocketEmitter.removeListener(event, sockets[event], this);
});
@ -43,8 +45,13 @@ export const Socketio = {
* Disconnects from the active socket and sets the connector to null.
*/
removeSocket: function () {
if (connector !== null && connector.instance() !== null) {
connector.instance().close();
if (!connector) {
return;
}
const instance: SocketIOClient.Socket | null = connector.instance();
if (instance) {
instance.close();
}
connector = null;

View File

@ -1,20 +1,14 @@
import Vue from 'vue';
import Vuex from 'vuex';
import auth, {AuthenticationState} from './modules/auth';
import dashboard, {DashboardState} from './modules/dashboard';
import server, {ServerState} from './modules/server';
import socket, {SocketState} from './modules/socket';
import auth from './modules/auth';
import dashboard from './modules/dashboard';
import server from './modules/server';
import socket from './modules/socket';
import {ApplicationState} from "./types";
Vue.use(Vuex);
export type ApplicationState = {
socket: SocketState,
server: ServerState,
auth: AuthenticationState,
dashboard: DashboardState,
}
const store = new Vuex.Store({
const store = new Vuex.Store<ApplicationState>({
strict: process.env.NODE_ENV !== 'production',
modules: {auth, dashboard, server, socket},
});

View File

@ -1,24 +1,19 @@
import User, {UserData} from '../../models/user';
import {ActionContext} from "vuex";
import {AuthenticationState} from "../types";
const route = require('./../../../../../vendor/tightenco/ziggy/src/js/route').default;
type LoginAction = {
type: 'login',
user: string,
password: string,
}
type UpdateEmailAction = {
type: 'updateEmail',
email: string,
password: string,
}
export type AuthenticationState = {
user: null | User,
}
export default {
namespaced: true,
state: {

View File

@ -1,12 +1,8 @@
import Server, {ServerData} from '../../models/server';
import {ActionContext} from "vuex";
import {DashboardState} from "../types";
const route = require('./../../../../../vendor/tightenco/ziggy/src/js/route').default;
export type DashboardState = {
searchTerm: string,
servers: Array<Server>,
};
export default {
namespaced: true,
state: {

View File

@ -2,17 +2,7 @@
import route from '../../../../../vendor/tightenco/ziggy/src/js/route';
import {ActionContext} from "vuex";
import {ServerData} from "../../models/server";
type ServerApplicationCredentials = {
node: string,
key: string,
};
export type ServerState = {
server: ServerData,
credentials: ServerApplicationCredentials,
console: Array<string>,
};
import {ServerApplicationCredentials, ServerState} from "../types";
export default {
namespaced: true,

View File

@ -1,10 +1,5 @@
import Status from '../../helpers/statuses';
export type SocketState = {
connected: boolean,
connectionError: boolean | Error,
status: number,
}
import {SocketState} from "../types";
export default {
namespaced: true,

View File

@ -0,0 +1,37 @@
import {ServerData} from "../models/server";
import Server from "../models/server";
import User from "../models/user";
export type ApplicationState = {
socket: SocketState,
server: ServerState,
auth: AuthenticationState,
dashboard: DashboardState,
}
export type SocketState = {
connected: boolean,
connectionError: boolean | Error,
status: number,
}
export type ServerApplicationCredentials = {
node: string,
key: string,
};
export type ServerState = {
server: ServerData,
credentials: ServerApplicationCredentials,
console: Array<string>,
};
export type DashboardState = {
searchTerm: string,
servers: Array<Server>,
};
export type AuthenticationState = {
user: null | User,
}

View File

@ -1,4 +1,23 @@
import Vue, {ComponentOptions} from "vue";
import {Store} from "vuex";
declare module '*.vue' {
import Vue from 'vue';
export default Vue;
}
declare module 'vue/types/options' {
interface ComponentOptions<V extends Vue> {
$store?: Store<any>,
$options?: {
sockets?: {
[s: string]: (data: any) => void,
}
},
}
}
declare module 'vue/types/vue' {
interface Vue {
$store: Store<any>,
}
}

View File

@ -1,8 +1,9 @@
{
"compilerOptions": {
"target": "es5",
"target": "es6",
"lib": [
"es2015",
"es2016",
"dom"
],
"strict": true,

View File

@ -778,10 +778,18 @@
"@shellscape/koa-send" "^4.1.0"
debug "^2.6.8"
"@types/lodash@^4.14.119":
version "4.14.119"
resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.119.tgz#be847e5f4bc3e35e46d041c394ead8b603ad8b39"
"@types/node@^10.12.15":
version "10.12.15"
resolved "https://registry.yarnpkg.com/@types/node/-/node-10.12.15.tgz#20e85651b62fd86656e57c9c9bc771ab1570bc59"
"@types/socket.io-client@^1.4.32":
version "1.4.32"
resolved "https://registry.yarnpkg.com/@types/socket.io-client/-/socket.io-client-1.4.32.tgz#988a65a0386c274b1c22a55377fab6a30789ac14"
"@types/webpack-env@^1.13.6":
version "1.13.6"
resolved "http://registry.npmjs.org/@types/webpack-env/-/webpack-env-1.13.6.tgz#128d1685a7c34d31ed17010fc87d6a12c1de6976"
@ -1725,10 +1733,6 @@ camelcase@^4.0.0, camelcase@^4.1.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-4.1.0.tgz#d545635be1e33c542649c69173e5de6acfae34dd"
camelcase@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.0.0.tgz#03295527d58bd3cd4aa75363f35b2e8d97be2f42"
caniuse-api@^1.5.2:
version "1.6.1"
resolved "https://registry.yarnpkg.com/caniuse-api/-/caniuse-api-1.6.1.tgz#b534e7c734c4f81ec5fbe8aca2ad24354b962c6c"