Skip to content

Commit

Permalink
* Updated vue, socket.io, socket.io-client and cookie-parser NPM pack…
Browse files Browse the repository at this point in the history
…ages.

* Installed @types/cookie-parser NPM package.
* Overhauled socket and eventBus functionality to add type safety, consolidate socket event handling, and fix issues relating to event subscription/unsubscription (ie those times when the highlight for the event/chat buttons wouldn't update when they should), as well as adding connection resiliency.
* Updated readyToQuit() and unreadyToQuit() endpoints in GameController to not broadcast gamePlayerReadyToQuit or gamePlayerNotReadyToQuit events, respectively, if the readyToQuitVisibility setting is set to 'hidden'.  If broadcasting the event, if the readyToQuitVisibility setting is NOT set to visible, the player Id is not included in the response data.
* Updated LedgerRow component:
    - Added functionality to prevent a player from trying to settle a debt if they have none of the relevant funds (credits/specialist tokens).
    - Fixed settleDebt() method not updating the user player's credit/specialist token amounts when settling a debt.
    - Updated settleDebt() method to improve toast message so that it reflects whether or not the player was only able to pay off some of the debt.
* Renamed main.js to main.ts to add Typescript support.
* Updated store.ts to use constants for some mutation names.
* Updated GamePlayerReadyToQuit and GamePlayerNotReadyToQuit mutations in store.ts to account for the playerId not being supplied, and to update the readyToQuitCount.
* Renamed sockets.ts to socket.js and updated to be SocketService.
    - Updated getUserId() method to fix circumstances where the returned Promise would not get resolved.
    - Moved gameRoomjoined and gameRoomLeft socket event handling functionality to PlayerServerSocketEventHandler.
* Moved roomExists(), playerRoomExists() and getOnlinePlayers() methods out of BroadcastService and into new SocketService.
* Moved LedgerType enum from server to common.
  • Loading branch information
Metritutus committed Jan 4, 2025
1 parent 93fc956 commit 21544ec
Show file tree
Hide file tree
Showing 69 changed files with 2,089 additions and 871 deletions.
2 changes: 1 addition & 1 deletion client/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
</noscript>
<div id="app" class="app" style="padding-top: 0"></div>

<script type="module" src="/src/main.js"></script>
<script type="module" src="/src/main.ts" lang="ts"></script>

</body>
</html>
7 changes: 5 additions & 2 deletions client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,10 @@
"pixi-viewport": "6.0.3",
"pixi.js": "8.6.3",
"random-seed": "^0.3.0",
"socket.io-client": "2.5.0",
"socket.io-client": "4.8.1",
"uuid": "^10.0.0",
"voronoi": "^1.0.0",
"vue": "3.4.31",
"vue": "3.5.13",
"vue-chartjs": "5.3.1",
"vue-recaptcha": "2.0.3",
"vue-router": "^4.4.0",
Expand All @@ -40,5 +40,8 @@
"less": "^3.13.1",
"vite": "^5.4.8",
"vue-tsc": "^2.1.10"
},
"optionalDependencies": {
"@rollup/rollup-win32-x64-msvc": "^4.29.1"
}
}
33 changes: 33 additions & 0 deletions client/src/clientEventBus.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import mitt, { type EventType, type Handler } from 'mitt';
import type { EventBus } from './eventBus';
import { type EventBusEventName } from './eventBusEventNames/eventBusEventName';

type Events<T> = Record<EventType, T>;

const emitter = mitt<Record<EventType, unknown>>();

export class ClientEventBus implements EventBus {
public on<TEventBusEventName extends EventBusEventName<TEventBusEventType, TData>,
TEventBusEventType,
TData>(type: TEventBusEventName, handler: (e: TData) => void): void {
emitter.on(type as TEventBusEventName, handler as Handler<Events<unknown>[TEventBusEventName]>);
}

public off<TEventBusEventName extends EventBusEventName<TEventBusEventType, TData>,
TEventBusEventType,
TData>(type: TEventBusEventName, handler?: (e: TData) => void): void {
emitter.off(type as TEventBusEventName, handler as Handler<Events<unknown>[TEventBusEventName]>);
}

public emit<TEventBusEventName extends EventBusEventName<TEventBusEventType, TData>,
TEventBusEventType,
TData>(type: TEventBusEventName, e: TData): void;
public emit<TEventBusEventName extends EventBusEventName<TEventBusEventType, TData>,
TEventBusEventType,
TData>(type: TEventBusEventName): void;
public emit<TEventBusEventName extends EventBusEventName<TEventBusEventType, TData>,
TEventBusEventType,
TData>(type: TEventBusEventName, e?: TData): void {
emitter.emit(type, e);
}
}
27 changes: 18 additions & 9 deletions client/src/eventBus.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,22 @@
import mitt from 'mitt'
import type { InjectionKey } from 'vue';
import { type EventBusEventName } from './eventBusEventNames/eventBusEventName';

// TODO: Handle stuff without an event bus or find a better typed solution at least
export const eventBusInjectionKey: InjectionKey<EventBus> = Symbol('EventBus');

const emitter = mitt();
export interface EventBus {
on<TEventBusEventName extends EventBusEventName<TEventBusEventType, TData>,
TEventBusEventType,
TData>(type: TEventBusEventName, handler: (e: TData) => void): void;

const eventBus = {
$on: emitter.on,
$off: emitter.off,
$emit: emitter.emit
};
off<TEventBusEventName extends EventBusEventName<TEventBusEventType, TData>,
TEventBusEventType,
TData>(type: TEventBusEventName, handler?: (e: TData) => void): void;

export default eventBus;
emit<TEventBusEventName extends EventBusEventName<TEventBusEventType, TData>,
TEventBusEventType,
TData>(type: TEventBusEventName): void;

emit<TEventBusEventName extends EventBusEventName<TEventBusEventType, TData>,
TEventBusEventType,
TData>(type: TEventBusEventName, e: TData): void;
}
14 changes: 14 additions & 0 deletions client/src/eventBusEventNames/diplomacy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import type { DiplomaticStatus } from "solaris-common/src/api/types/common/diplomacy";
import { makeCastFunc } from "solaris-common/src/utilities/cast";
import type { EventBusEventName } from "./eventBusEventName";

export type DiplomacyEventBusEventType = { diplomacyEventBusEventType: 'diplomacyEventBusEventType' };
export type DiplomacyEventBusEventName<TData> = EventBusEventName<DiplomacyEventBusEventType, TData> & { diplomacyEventBusEventName: 'diplomacyEventBusEventName' }

const toEventName: <TData>(value: string) => DiplomacyEventBusEventName<TData> = makeCastFunc();

export default class DiplomacyEventBusEventNames {
private constructor() { };

public static readonly PlayerDiplomaticStatusChanged: DiplomacyEventBusEventName<{ diplomaticStatus: DiplomaticStatus<string> }> = toEventName('playerDiplomaticStatusChanged');
}
1 change: 1 addition & 0 deletions client/src/eventBusEventNames/eventBusEventName.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export type EventBusEventName<TEventBusEventType, TData> = string & { eventBusEventType?: TEventBusEventType, data?: TData, eventBusEventName: 'eventBusEventName' }
15 changes: 15 additions & 0 deletions client/src/eventBusEventNames/game.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import type { GameState } from "solaris-common/src";
import { makeCastFunc } from "solaris-common/src/utilities/cast";
import type { EventBusEventName } from "./eventBusEventName";

export type GameEventBusEventType = { gameEventBusEventType: 'gameEventBusEventType' };
export type GameEventBusEventName<TData> = EventBusEventName<GameEventBusEventType, TData> & { gameEventBusEventName: 'gameEventBusEventName' }

const toEventName: <TData>(value: string) => GameEventBusEventName<TData> = makeCastFunc();

export default class GameEventBusEventNames {
private constructor() { };

public static readonly GameStarted: GameEventBusEventName<{ state: GameState<string> }> = toEventName('gameStarted');
public static readonly OnGameTick: GameEventBusEventName<void> = toEventName('onGameTick');
}
20 changes: 20 additions & 0 deletions client/src/eventBusEventNames/menu.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { makeCastFunc } from "solaris-common/src/utilities/cast";
import MENU_STATES from '../services/data/menuStates';
import type { EventBusEventName } from "./eventBusEventName";

export type MenuEventBusEventType = { menuEventBusEventType: 'menuEventBusEventType' };
export type MenuEventBusEventName<TData> = EventBusEventName<MenuEventBusEventType, TData> & { menuEventBusEventName: 'menuEventBusEventName' }

const toEventName: <TData>(value: string) => MenuEventBusEventName<TData> = makeCastFunc();

export default class MenuEventBusEventNames {
private constructor() { };

public static readonly OnMenuRequested: MenuEventBusEventName<{ menuState: MENU_STATES | null, menuArguments: unknown | null }> = toEventName('onMenuRequested');

public static readonly OnMenuChatSidebarRequested: MenuEventBusEventName<void> = toEventName('onMenuChatSidebarRequested');

public static readonly OnCreateNewConversationRequested: MenuEventBusEventName<{ participantIds?: string[] }> = toEventName('onCreateNewConversationRequested');
public static readonly OnViewConversationRequested: MenuEventBusEventName<{ conversationId: string, participantIds: string[] }> = toEventName('onViewConversationRequested');
public static readonly OnOpenInboxRequested: MenuEventBusEventName<void> = toEventName('onOpenInboxRequested');
}
33 changes: 33 additions & 0 deletions client/src/eventBusEventNames/player.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { type ConversationMessageSentResult } from "solaris-common/src/api/types/common/conversationMessage";
import type { LedgerType } from "solaris-common/src/api/types/common/ledger";
import type { TradeEventTechnology } from "solaris-common/src/api/types/common/trade";
import { makeCastFunc } from "solaris-common/src/utilities/cast";
import { type EventBusEventName } from "./eventBusEventName";

export type PlayerEventBusEventType = { playerEventBusEventType: 'playerEventBusEventType' };
export type PlayerEventBusEventName<TData> = EventBusEventName<PlayerEventBusEventType, TData> & { playerEventBusEventName: 'playerEventBusEventName' }

const toEventName: <TData>(value: string) => PlayerEventBusEventName<TData> = makeCastFunc();

export default class PlayerEventBusEventNames {
private constructor() { };

public static readonly GameMessageSent: PlayerEventBusEventName<ConversationMessageSentResult<string>> = toEventName('gameMessageSent');
public static readonly GameConversationRead: PlayerEventBusEventName<{ conversationId: string, readByPlayerId: string }> = toEventName('gameConversationRead');

public static readonly GameConversationLeft: PlayerEventBusEventName<{ conversationId: string, playerId: string }> = toEventName('gameConversationLeft');
public static readonly GameConversationMessagePinned: PlayerEventBusEventName<{ conversationId: string, messageId: string }> = toEventName('gameConversationMessagePinned');
public static readonly GameConversationMessageUnpinned: PlayerEventBusEventName<{ conversationId: string, messageId: string }> = toEventName('gameConversationMessageUnpinned');

public static readonly PlayerEventRead: PlayerEventBusEventName<{ eventId: string }> = toEventName('playerEventRead');
public static readonly PlayerAllEventsRead: PlayerEventBusEventName<{}> = toEventName('playerAllEventsRead');

public static readonly PlayerCreditsReceived: PlayerEventBusEventName<{ playerId: string, type: string, date: Date, data: { fromPlayerId: string, toPlayerId: string, credits: number } }> = toEventName('playerCreditsReceived');
public static readonly PlayerCreditsSpecialistsReceived: PlayerEventBusEventName<{ playerId: string, type: string, date: Date, data: { fromPlayerId: string, toPlayerId: string, creditsSpecialists: number } }> = toEventName('playerCreditsSpecialistsReceived');
public static readonly PlayerTechnologyReceived: PlayerEventBusEventName<{ playerId: string, type: string, date: Date, data: { fromPlayerId: string, toPlayerId: string, technology: TradeEventTechnology } }> = toEventName('playerTechnologyReceived');
public static readonly PlayerRenownReceived: PlayerEventBusEventName<{ playerId: string, type: string, date: Date, data: { fromPlayerId: string, toPlayerId: string, renown: number } }> = toEventName('playerRenownReceived');

public static readonly PlayerDebtAdded: PlayerEventBusEventName<{ debtorPlayerId: string, creditorPlayerId: string, amount: number, ledgerType: LedgerType }> = toEventName('playerDebtAdded');
public static readonly PlayerDebtForgiven: PlayerEventBusEventName<{ debtorPlayerId: string, creditorPlayerId: string, amount: number, ledgerType: LedgerType }> = toEventName('playerDebtForgiven');
public static readonly PlayerDebtSettled: PlayerEventBusEventName<{ debtorPlayerId: string, creditorPlayerId: string, amount: number, ledgerType: LedgerType }> = toEventName('playerDebtSettled');
}
79 changes: 0 additions & 79 deletions client/src/main.js

This file was deleted.

132 changes: 132 additions & 0 deletions client/src/main.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
import "@/assets/styles.css"
import $ from 'jquery'
import 'pixi-viewport'
import 'pixi.js'
import { io } from 'socket.io-client'
import { createApp } from 'vue'
import ToastPlugin from "vue-toast-notification"
import 'vue-toast-notification/dist/theme-default.css'
import App from './App.vue'
import { ClientEventBus } from "./clientEventBus"
import { eventBusInjectionKey, type EventBus } from './eventBus'
import router from './router'
import GameHelper from './services/gameHelper'
import { PlayerClientSocketEmitter, playerClientSocketEmitterInjectionKey } from './socketEmitters/player'
import { DiplomacyClientSocketHandler } from './socketHandlers/diplomacy'
import { GameClientSocketHandler } from './socketHandlers/game'
import { PlayerClientSocketHandler } from "./socketHandlers/player"
import { createSolarisStore } from './store'

// Note: This was done to get around an issue where the Steam client
// had bootstrap as undefined. This also affects the UI template we're using,
// we are forced to bring in Bootstrap and FontAwesome manually as a dependency
// instead of using the vendor files provided by the template.
// DO NOT use top-level await since that silently breaks the bundle
import('bootstrap/dist/js/bootstrap.bundle.js').then((mod) => window.bootstrap = mod);
declare var bootstrap: any; // Hnnngh.
import('../public/assets/js/app.min.js')

const socketUrl: string = import.meta.env.VUE_APP_SOCKETS_HOST;

window.$ = $;

window._solaris = {
errors: []
};

const app = createApp(App);

app.config.errorHandler = (err, vm, info) => {
if (err instanceof Error) {
window._solaris.errors.push(`Vue error: ${err.message}\n ${err.cause} ${info}\n ${err.stack}`);
}
else {
window._solaris.errors.push(`Unknown error: ${err}`);
}

console.error(err);
};

window.addEventListener("error", (ev) => {
window._solaris.errors.push(ev.error + ' ' + ev.message);
});

window.addEventListener("unhandledrejection", (event) => {
window._solaris.errors.push(event.reason);
reportError(event.reason);
});

const eventBus: EventBus = new ClientEventBus();

let store = createSolarisStore(eventBus);

app.use(store);

app.use(ToastPlugin);

let socket = io(socketUrl, { withCredentials: true });

socket.on('connect', () => {
console.log('Socket connection established.');
});

socket.io.on('error', e => {
console.error('Socket.io error.');
console.error(e);
});

export const diplomacyClientSocketHandler: DiplomacyClientSocketHandler = new DiplomacyClientSocketHandler(socket, eventBus);
export const gameClientSocketHandler: GameClientSocketHandler = new GameClientSocketHandler(socket, store, app.config.globalProperties.$toast, eventBus);
export const playerClientSocketHandler: PlayerClientSocketHandler = new PlayerClientSocketHandler(socket, store, eventBus);

const playerClientSocketEmitter: PlayerClientSocketEmitter = new PlayerClientSocketEmitter(socket);

app.provide(playerClientSocketEmitterInjectionKey, playerClientSocketEmitter);
app.provide(eventBusInjectionKey, eventBus);

socket.io.on('reconnect', () => {
let gameId = store.state.game?._id;

if (gameId != null) {
let player = GameHelper.getUserPlayer(store.state.game)

console.log('Rejoining game room.');

playerClientSocketEmitter.emitGameRoomJoined({
gameId: gameId,
playerId: player?._id
});
}
});


app.config.globalProperties.$confirm = async function(title, text, confirmText = 'Yes', cancelText = 'No', hideCancelButton = false, cover = false) {
return this.$store.dispatch('confirm', {
titleText: title,
text,
confirmText,
cancelText,
hideCancelButton,
cover
})
}

app.config.globalProperties.$isHistoricalMode = function() {
return this.$store.state.tick !== this.$store.state.game.state.tick
}

app.config.globalProperties.$isMobile = function () {
return window.matchMedia('only screen and (max-width: 576px)').matches
}

app.directive('tooltip', function(el, binding) {
new bootstrap.Tooltip($(el), {
title: binding.value,
placement: binding.arg,
trigger: 'hover'
})
})

app.use(router);

app.mount('#app');
Loading

0 comments on commit 21544ec

Please sign in to comment.