Skip to content

Commit

Permalink
Wss tachyon protocol (#299)
Browse files Browse the repository at this point in the history
* Add a version of tachyon-client

* Server status

* debugging stuff

* wip

* fix request

* debug messages

* fix inverted logic

* ranked

* matchmaking

* a bit more stylish

* matchmaking poc works

* clean up

* syncing server status

* fix token renewal

* error handling

* update deps

* slight change to app suffix

* wip

* wip

* better handling of current user

* moved login page to root

* cleanup login step

* fix typing of userId

* fix some initialization / typing

* eslint

* fix some more initialization issues

* change server

* wip

* cleanup

* await service initialization

* fix host for bots

* move installed version to a Set

* drop latest game engine constant / latest game version

* new api to start mutiplayer games

* list all available engine versions

* download any engine version

* dropped engine db for plain reactive store

* fix a bunch of issues on first load

* fix byar:lastest download

* simpler reconnection mecanism

* update deps

* fix checks

* cleanup
  • Loading branch information
bcdrme authored Jan 30, 2025
1 parent a7ee04f commit ca775a3
Show file tree
Hide file tree
Showing 69 changed files with 2,142 additions and 1,070 deletions.
12 changes: 11 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ npm start
- [**Renderer process**](https://www.electronjs.org/docs/latest/tutorial/process-model#the-renderer-process)
- Runs in a web environment and has **no** direct access to Node.js APIs

### Build & Publish:
### Build & Publish

- [`electron-forge`](https://www.electronforge.io/config/plugins/vite)
- Builds the app with a pre-configured Vite setup for Electron apps
Expand Down Expand Up @@ -113,3 +113,13 @@ npm start
- Runs `electron-builder` to create a self-signed cert for Windows apps.
- After selecting "None" in the pop-up, a cert file should be created called `BAR Team.pfx`
- Then run `npm run build:win:dev-cert` to build a signed Windows installer

### Tips

You can start multiple separate instances using `APP_NAME_SUFFIX` env variable (e.g. to work on multiplayer features) :

```
APP_NAME_SUFFIX=1 npm start
APP_NAME_SUFFIX=2 npm start
APP_NAME_SUFFIX=3 npm start
```
1,110 changes: 564 additions & 546 deletions package-lock.json

Large diffs are not rendered by default.

52 changes: 29 additions & 23 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "bar-lobby",
"type": "module",
"version": "0.15.0-alpha.1",
"version": "0.15.0-alpha.2",
"private": true,
"description": "Lobby client for the RTS game Beyond All Reason",
"author": "BAR Team",
Expand Down Expand Up @@ -33,21 +33,21 @@
"node": "20.18.0"
},
"dependencies": {
"7zip-bin": "^5.2.0",
"@extractus/feed-extractor": "^7.1.3",
"@iconify-icons/mdi": "^1.2.48",
"@iconify/json": "^2.2.297",
"@iconify/json": "^2.2.301",
"@iconify/vue": "^4.3.0",
"@octokit/rest": "^21.1.0",
"@sinclair/typebox": "^0.34.14",
"@vueuse/core": "^12.4.0",
"7zip-bin": "^5.2.0",
"@sinclair/typebox": "^0.34.15",
"@vueuse/core": "^12.5.0",
"ajv": "^8.17.1",
"ajv-formats": "^3.0.1",
"axios": "^1.7.9",
"chokidar": "^4.0.3",
"date-fns": "^4.1.0",
"dexie": "^4.0.11",
"dompurify": "^3.2.3",
"dompurify": "^3.2.4",
"env-paths": "^3.0.0",
"flag-icons": "^7.3.2",
"glob-promise": "^6.0.7",
Expand All @@ -58,45 +58,51 @@
"pino-pretty": "^13.0.0",
"primeicons": "^6.0.1",
"primevue": "3.23.0",
"vue-i18n": "^10.0.5",
"vue-router": "^4.5.0"
"tachyon-protocol": "^1.9.3",
"vue-i18n": "^11.0.1",
"vue-router": "^4.5.0",
"ws": "^8.18.0"
},
"devDependencies": {
"@electron-forge/cli": "^7.6.0",
"@electron-forge/maker-deb": "^7.6.0",
"@electron-forge/maker-flatpak": "^7.6.0",
"@electron-forge/maker-rpm": "^7.6.0",
"@electron-forge/maker-squirrel": "^7.6.0",
"@electron-forge/plugin-auto-unpack-natives": "^7.6.0",
"@electron-forge/plugin-fuses": "^7.6.0",
"@electron-forge/plugin-vite": "^7.6.0",
"@electron-forge/shared-types": "^7.6.0",
"@electron-forge/cli": "^7.6.1",
"@electron-forge/maker-deb": "^7.6.1",
"@electron-forge/maker-flatpak": "^7.6.1",
"@electron-forge/maker-rpm": "^7.6.1",
"@electron-forge/maker-squirrel": "^7.6.1",
"@electron-forge/plugin-auto-unpack-natives": "^7.6.1",
"@electron-forge/plugin-fuses": "^7.6.1",
"@electron-forge/plugin-vite": "^7.6.1",
"@electron-forge/shared-types": "^7.6.1",
"@electron/fuses": "^1.8.0",
"@types/better-sqlite3": "^7.6.12",
"@types/howler": "^2.2.12",
"@types/luaparse": "^0.2.12",
"@types/node": "^20.17.14",
"@types/node": "^20.17.16",
"@types/ws": "^8.5.14",
"@vitejs/plugin-vue": "^5.2.1",
"cross-env": "^7.0.3",
"electron": "33.2.1",
"electron-builder": "^25.1.8",
"electron-updater": "^6.3.9",
"eslint": "^9.18.0",
"eslint": "^9.19.0",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-unused-imports": "^4.1.4",
"eslint-plugin-vue": "^9.32.0",
"globals": "^15.14.0",
"prettier": "^3.4.2",
"sass": "^1.83.4",
"ts-node": "^10.9.2",
"type-fest": "^4.32.0",
"type-fest": "^4.33.0",
"typescript": "5.7.2",
"typescript-eslint": "^8.20.0",
"typescript-eslint": "^8.22.0",
"unplugin-vue-router": "^0.10.9",
"vite": "^6.0.9",
"vite": "^6.0.11",
"vite-plugin-static-copy": "^2.2.0",
"vite-plugin-vue-devtools": "^7.7.0",
"vite-plugin-vue-devtools": "^7.7.1",
"vitest": "^2.1.8",
"vue-tsc": "^2.2.0"
},
"optionalDependencies": {
"bufferutil": "^4.0.9"
}
}
3 changes: 2 additions & 1 deletion src/main/config/app.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { contentSources } from "@main/config/content-sources";
import envPaths from "env-paths";
import path from "path";
import { env } from "process";

export const APP_NAME = "Beyond All Reason";

const paths = envPaths(APP_NAME, { suffix: "" });
const paths = envPaths(APP_NAME, { suffix: env.APP_NAME_SUFFIX || "" });
export const CONTENT_PATH = paths.data;
export const CONFIG_PATH = paths.config;

Expand Down
3 changes: 0 additions & 3 deletions src/main/config/default-versions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,5 @@
* In the future these values should probably be set and fetched from the master server, so we don't need to deploy a new lobby release every time.
*/

export const DEFAULT_ENGINE_VERSION = "2025.01.3";
export const LATEST = "LATEST";

export const DEFAULT_GAME_VERSION = "byar:test";
export const LATEST_GAME_VERSION = "byar:test";
13 changes: 11 additions & 2 deletions src/main/config/server.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,13 @@
export const OAUTH_AUTHORIZATION_SERVER_URL = `https://server5.beyondallreason.info`;
export const WELL_KNOWN_OAUTH_AUTHORIZATION_SERVER_URL = `${OAUTH_AUTHORIZATION_SERVER_URL}/.well-known/oauth-authorization-server`;
// TODO: this should reference a real server like server5.beyondallreason.info
// there is an issue with port handling right now, so we can't use the real server
// see https://github.com/beyond-all-reason/teiserver/pull/555

// export const TEISERVER_HOSTNAME_PORT = "server5.beyondallreason.info";
export const TEISERVER_HOSTNAME_PORT = "lobby-server-dev.beyondallreason.dev";

export const OAUTH_AUTHORIZATION_SERVER_URL = `https://${TEISERVER_HOSTNAME_PORT}`;
export const OAUTH_WELL_KNOWN_URL = `${OAUTH_AUTHORIZATION_SERVER_URL}/.well-known/oauth-authorization-server`;
export const OAUTH_CLIENT_ID = "generic_lobby";
export const OAUTH_SCOPE = "tachyon.lobby";

export const WS_SERVER_URL = `wss://${TEISERVER_HOSTNAME_PORT}/tachyon`;
8 changes: 4 additions & 4 deletions src/main/content/abstract-content.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { Signal } from "$/jaz-ts-utils/signal";
import { DownloadInfo } from "./downloads";

export abstract class AbstractContentAPI<T> {
public installedVersions: T[] = [];
export abstract class AbstractContentAPI<ID, T> {
public availableVersions: Map<ID, T> = new Map();
public currentDownloads: DownloadInfo[] = [];

public onDownloadStart: Signal<DownloadInfo> = new Signal();
Expand All @@ -14,9 +14,9 @@ export abstract class AbstractContentAPI<T> {
return this;
}

public abstract isVersionInstalled(id: string): boolean;
public abstract isVersionInstalled(id: ID): boolean;

public abstract uninstallVersion(version: T): Promise<void>;
public abstract uninstallVersion(version: ID | T): Promise<void>;

protected async downloadStarted(downloadInfo: DownloadInfo) {
this.onDownloadStart.dispatch(downloadInfo);
Expand Down
62 changes: 43 additions & 19 deletions src/main/content/engine/engine-content.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,14 @@ import { extract7z } from "@main/utils/extract-7z";
import { contentSources } from "@main/config/content-sources";
import { AbstractContentAPI } from "@main/content/abstract-content";
import { CONTENT_PATH } from "@main/config/app";
import { LATEST } from "@main/config/default-versions";

const log = logger("engine-content.ts");

export class EngineContentAPI extends AbstractContentAPI<EngineVersion> {
// TODO: add support for old engine version tag naming scheme, careful it is not string sortable (!)
// Regex matching new engine version tags (e.g. "2025.01.3", "2025.01.3-rc1")
const compatibleVersionRegex = /^\d{4}\.\d{2}\.\d{1,2}(-rc\d+)?$/;

export class EngineContentAPI extends AbstractContentAPI<string, EngineVersion> {
protected readonly engineDirs = path.join(CONTENT_PATH, "engine");
protected readonly ocotokit = new Octokit();

Expand All @@ -26,41 +29,63 @@ export class EngineContentAPI extends AbstractContentAPI<EngineVersion> {
log.info("Initializing engine content API");
await fs.promises.mkdir(this.engineDirs, { recursive: true });
const files = await fs.promises.readdir(this.engineDirs, { withFileTypes: true });
const dirs = files.filter((file) => file.isDirectory() || file.isSymbolicLink()).map((dir) => dir.name);
const dirs = files
.filter((file) => file.isDirectory() || file.isSymbolicLink())
.map((dir) => dir.name)
.filter((dir) => compatibleVersionRegex.test(dir) || dir.includes("local"));
log.info(`Found ${dirs.length} installed engine versions`);
for (const dir of dirs) {
log.info(`-- Engine ${dir}`);
const ais = await this.parseAis(dir);
this.installedVersions.push({ id: dir, lastLaunched: new Date(), ais });
this.availableVersions.set(dir, { id: dir, ais, installed: true });
}
try {
await this.fetchAvailableVersionsOnline();
} catch (err) {
log.error(`Failed to fetch available engine versions online: ${err}`);
}
log.info(`Found ${this.availableVersions.size} engine versions total.`);
} catch (err) {
log.error(err);
}
return this;
}

public isVersionInstalled(id: string): boolean {
return this.installedVersions.some((installedVersion) => installedVersion.id === id);
return this.availableVersions.get(id)?.installed ?? false;
}

public getLatestInstalledVersion() {
return this.availableVersions
.values()
.filter((version) => version.installed)
.toArray()
.sort((a, b) => a.id.localeCompare(b.id))
.at(-1);
}

protected async getLatestTagName() {
const { data } = await this.ocotokit.rest.repos.getLatestRelease({
protected async fetchAvailableVersionsOnline() {
const { data } = await this.ocotokit.rest.repos.listReleases({
owner: contentSources.engineGitHub.owner,
repo: contentSources.engineGitHub.repo,
});
return data.tag_name;
}

public async isNewVersionAvailable() {
const tagName = await this.getLatestTagName();
return !this.isVersionInstalled(tagName);
data.map((release) => release.tag_name)
.filter((tag) => compatibleVersionRegex.test(tag))
.filter((tag) => !this.availableVersions.has(tag))
.map((tag) => {
return {
id: tag,
ais: [],
installed: false,
};
})
.forEach((version) => {
this.availableVersions.set(version.id, version);
});
}

public async downloadEngine(engineVersion: string) {
try {
if (engineVersion === LATEST) {
engineVersion = await this.getLatestTagName();
}
if (this.isVersionInstalled(engineVersion)) {
return;
}
Expand Down Expand Up @@ -121,13 +146,12 @@ export class EngineContentAPI extends AbstractContentAPI<EngineVersion> {
}
const engineDir = path.join(this.engineDirs, version);
await fs.promises.rm(engineDir, { force: true, recursive: true });
const index = this.installedVersions.findIndex((installedVersion) => installedVersion.id === version);
this.installedVersions.splice(index, 1);
this.availableVersions.delete(version);
}

protected override async downloadComplete(downloadInfo: DownloadInfo) {
log.debug(`Download complete: ${downloadInfo.name}`);
this.installedVersions.push({ id: downloadInfo.name, lastLaunched: new Date(), ais: [] });
this.availableVersions.set(downloadInfo.name, { id: downloadInfo.name, ais: [], installed: true });
super.downloadComplete(downloadInfo);
}

Expand Down
2 changes: 1 addition & 1 deletion src/main/content/engine/engine-version.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ import { LuaOptionSection } from "@main/content/game/lua-options";

export type EngineVersion = {
id: string;
lastLaunched: Date;
ais: EngineAI[];
installed: boolean;
};

export interface EngineAI {
Expand Down
Loading

0 comments on commit ca775a3

Please sign in to comment.