Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 11 additions & 2 deletions src/main/model/user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,26 @@
//
// SPDX-License-Identifier: MIT

export type ClanBaseData = {
clanId: string;
name: string;
tag: string;
language: string;
};

export type User = {
userId: string;
username: string;
displayName: string;
clanId: string | null;
clanBaseData?: ClanBaseData | null;
partyId: string | null;
countryCode: string;
status: "offline" | "menu" | "playing" | "lobby";
rating?: { value: number } | null;
roles?: ReadonlyArray<"contributor" | "admin" | "moderator" | "tournament_winner" | "tournament_caster">;

// Is the user me?
isMe?: 0 | 1;
isMe: boolean;

// When user is a contender in a battle
battleRoomState: {
Expand Down
20 changes: 17 additions & 3 deletions src/renderer/assets/languages/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -1896,9 +1896,23 @@
},
"views": {
"profile": {
"status": "Status: ",
"clan": "Clan: ",
"userNotFound": "User not found"
"status": "Status",
"userId": "User ID",
"clan": "Clan",
"userNotFound": "User not found",
"myProfile": "My profile",
"findAClan": "Find a clan",
"statusOffline": "Offline",
"statusMenu": "Menu",
"statusPlaying": "Playing",
"statusLobby": "Lobby",
"rating": "Rating",
"roles": "Roles",
"roleContributor": "Contributor",
"roleAdmin": "Admin",
"roleModerator": "Moderator",
"roleTournamentWinner": "Tournament Winner",
"roleTournamentCaster": "Tournament Caster"
},
"play": {
"comingSoon": "(Coming Soon)",
Expand Down
70 changes: 70 additions & 0 deletions src/renderer/components/battle/BattleMessage.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
<!--
SPDX-FileCopyrightText: 2025 The BAR Lobby Authors

SPDX-License-Identifier: MIT
-->

<template>
<div class="message" :class="[{ 'from-host': fromHost }, message.type]">
<div v-if="user" class="user">
<Flag :countryCode="user.countryCode" style="width: 16px" />
<div>{{ user.username }}</div>
</div>
<Markdown :source="message.text" />
</div>
</template>

<script lang="ts" setup>
import { User } from "@main/model/user";
import Flag from "@renderer/components/misc/Flag.vue";
import Markdown from "@renderer/components/misc/Markdown.vue";
import { Message } from "@renderer/model/messages";
import { me } from "@renderer/store/me.store";

defineProps<{
message: Message;
}>();

const user: User = {
battleRoomState: {},
countryCode: "US",
userId: "123",
clanBaseData: null,
username: "Test User",
displayName: "Test User",
partyId: "123",
status: "lobby",
isMe: false,
};

// const user = api.session.getUserById(props.message.senderUserId);
const fromHost = user.userId === me.userId;
</script>

<style lang="scss" scoped>
.message {
display: flex;
flex-direction: row;
background: rgba(255, 255, 255, 0.1);
border: 1px solid rgba(255, 255, 255, 0.1);
border-radius: 3px;
overflow: hidden;
&.from-host {
background: rgba(110, 186, 216, 0.4);
}
&.battle-announcement {
background: rgba(119, 255, 180, 0.24);
}
}
.user {
display: flex;
flex-direction: row;
align-items: center;
gap: 5px;
padding: 4px 8px;
background: rgba(255, 255, 255, 0.05);
border-right: 1px solid rgba(255, 255, 255, 0.1);
box-shadow: 2px 0 5px rgba(0, 0, 0, 0.4);
font-weight: 500;
}
</style>
16 changes: 16 additions & 0 deletions src/renderer/store/db.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,22 @@ db.version(1).stores({
users: "userId, username, countryCode, status, displayName, clanId, partyId, scopes, isMe",
});

db.version(2)
Comment thread
Rimek86 marked this conversation as resolved.
.stores({
users: "userId, username, countryCode, status, displayName, partyId, scopes, isMe",
})
.upgrade(async (tx) => {
await tx
.table("users")
.toCollection()
.modify((user) => {
// TODO: For backward compatibility, I've left it in for now to support older database entries if necessary. But it can be removed later!
if (user.isMe === "undefined") {
user.isMe = false;
}
});
});
Comment thread
Rimek86 marked this conversation as resolved.

db.on("ready", function () {
console.debug("Database is ready");
});
Expand Down
11 changes: 7 additions & 4 deletions src/renderer/store/me.store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,14 @@ export const me = reactive<
>({
isInitialized: false,
userId: "0",
clanId: null,
clanBaseData: null,
partyId: null,
countryCode: "",
displayName: "",
status: "offline",
rating: { value: 0 },
roles: [],
isMe: true,
isAuthenticated: false,
username: "Player",
battleRoomState: {},
Expand Down Expand Up @@ -74,11 +77,11 @@ async function changeAccount() {
window.tachyon.onEvent("user/self", async (event) => {
console.debug(`Received user/self event: ${JSON.stringify(event)}`);
if (event && event.user) {
await db.users.where({ isMe: 1 }).modify({ isMe: 0 });
await db.users.where({ isMe: true }).modify({ isMe: false });
Object.assign(me, event.user);
db.users.put({
...toRaw(me),
isMe: 1,
isMe: true,
});

await processFriendData(event.user);
Expand Down Expand Up @@ -211,7 +214,7 @@ export const friends = {

export async function initMeStore() {
await db.users
.where({ isMe: 1 })
.where({ isMe: true })
.first()
.then((user) => {
if (user) {
Expand Down
42 changes: 37 additions & 5 deletions src/renderer/store/users.store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
import { db } from "@renderer/store/db";
import { reactive } from "vue";
import { SubsManager } from "@renderer/utils/subscriptions-manager";
import { UserInfoOkResponseData } from "tachyon-protocol/types";
import { apply as applyPatch } from "json8-merge-patch";

export const usersStore: {
isInitialized: boolean;
Expand All @@ -23,24 +25,54 @@ export function initUsersStore() {
console.warn("Received user/updated event with no userId, skipping update.");
return;
}
const updated = await db.users.update(user.userId, { ...user });

Comment thread
Rimek86 marked this conversation as resolved.
const existingUser = await db.users.get(user.userId);
const updatedUser = applyPatch(existingUser || {}, {
...user,
clanBaseData: user.clanBaseData
? {
...user.clanBaseData,
language: user.clanBaseData.language || "unknown",
}
: undefined,
});

const updated = await db.users.update(user.userId, updatedUser);

if (updated === 0) {
// No records updated, so user doesn't exist - create new user
db.users.add({
userId: user.userId,
username: user.username ?? "Unknown User",
displayName: user.displayName ?? "Unknown User",
Comment on lines +46 to +47

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think there's a problem there. If you don't have anything in the DB, but you are getting a partial update, then you can't really create the correct record. So either you need to check that the event you got has all the data, that is, it's the initial event and use that. Or, if it's a partial update, you can't use the event and would need to fetchUserInfo to then create the data.

I am not sure how that could happen, but creating a user with these default seems just wrong.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you don't have anything in the DB, (...)

The original code just assumes that it can happen and fills in some blanks, no harm done if server functions correctly, but we can make it cleaner.

Or, if it's a partial update, you can't use the event and would need to fetchUserInfo to then create the data.

Not really. Recall the issues with ordering of events/reponses. This would happen here too.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the original author programmed defensively here. I don't think it's wrong to do it that way. It only covers the case where there is inconsistent or incomplete data. It should never happen...

@p2004a How we can make it cleaner? (My goal wasn't to imrpove the users.store.ts My goal was to improve the profiles page here. :-) )

You once asked me not to change too much at once. That's why I wanted to stick to it and not touch everything directly with a PR. Can we possibly make this an issue that will then be changed later?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@p2004a Hi, i was absent in the last weeks. But now i thin i find time again to continue. What's about my question so that i can continoue here...

@p2004a p2004a May 31, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It needs to be changed in protocol to be merge patch too, including all the implications of that: drop all the allowed nulls, it's reserved to patch semantic. (overall, having a optional field that can also take null is weird)

My goal wasn't to imrpove the users.store.ts My goal was to improve the profiles page here. :-)

I mean, piling up more mess on the top is not correct strategy, this is not deep refactoring, you are changing this code.

All this code can be simplified to

const toUpdate = await db.users.bulkGet(event.users.map((u) => u.userId));
const updatedUsers = event.users.map((u, i) => applyPatch(toUpdate[i] || {}, u));
await db.users.bulkPut(updatedUsers);

clanId: null,
clanBaseData: user.clanBaseData
? {
...user.clanBaseData,
language: user.clanBaseData.language || "unknown",
}
: null,
partyId: null,
countryCode: "??",
status: "offline",
countryCode: user.countryCode ?? "??",
status: user.status ?? "offline",
roles: user.roles ?? [],
rating: user.rating ?? null,
battleRoomState: {},
...user, // Override defaults with actual data
isMe: false,
...user,
});
}
});
});

usersStore.isInitialized = true;
}

export async function fetchUserInfo(userId: string): Promise<UserInfoOkResponseData | null> {
try {
const response = await window.tachyon.request("user/info", { userId: userId });
return response.data;
} catch (error) {
console.error("Error fetching user info:", error);
return null;
}
}
Loading
Loading