Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Debug/websocket disconnect #126

Merged
merged 8 commits into from
Nov 25, 2024
Merged
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
15 changes: 12 additions & 3 deletions mobile-app/api/entities/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ export class ProfileImpl {

export class TeamMemberImpl {
public id: string;
public team: 'red' | 'blue';
public team!: 'red' | 'blue';

public get name(): string {
return this.player!.profile!.name;
Expand Down Expand Up @@ -111,9 +111,12 @@ export class TeamMemberImpl {
return this.player.profile.avatarUrl;
}

public setTeamColor(color: 'red' | 'blue'): void {
this.team = color;
}

constructor(_data: Components.Schemas.TeamMemberDto) {
this.id = _data.id!;
this.team = _data.teamId as 'red' | 'blue';

this.playerId = _data.playerId!;
}
Expand All @@ -128,7 +131,7 @@ export class TeamMemberImpl {
}
public toJSON(): TeamMember {
return {
id: this.id,
id: this.playerId,
change: this.change,
moves: this.moves.map((i) => i.toJSON()),
name: this.name,
Expand Down Expand Up @@ -259,6 +262,12 @@ export class MatchImpl {
);
}
}
for (const player of this._blueTeam.members) {
player.setTeamColor('blue');
}
for (const player of this._redTeam.members) {
player.setTeamColor('red');
}
}
public get redCups(): number {
return this._redTeam.points!;
Expand Down
4 changes: 2 additions & 2 deletions mobile-app/api/env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
/** the spaced format of group invite codes. `[3, 4, 3]` => `"xxx xxxx xxx"` */
const groupCodeFormat = [3, 3, 3];

const assertEnvString = (name: string): string => {

Check warning on line 8 in mobile-app/api/env.ts

View workflow job for this annotation

GitHub Actions / Lint Code

'assertEnvString' is assigned a value but never used
const value = process.env?.[name];

if (typeof value !== 'string')
Expand Down Expand Up @@ -37,8 +37,8 @@
};

export const env = {
apiBaseUrl: process.env.EXPO_PUBLIC_API_BASE_URL, //assertEnvString('EXPO_PUBLIC_API_BASE_URL'),
realtimeBaseUrl: process.env.EXPO_PUBLIC_API_WS_URL, //assertEnvString('EXPO_PUBLIC_API_WS_URL')!,
apiBaseUrl: process.env.EXPO_PUBLIC_API_BASE_URL!, //assertEnvString('EXPO_PUBLIC_API_BASE_URL'),
realtimeBaseUrl: process.env.EXPO_PUBLIC_API_WS_URL!, //assertEnvString('EXPO_PUBLIC_API_WS_URL')!,

groupCode: {
format: groupCodeFormat,
Expand Down
4 changes: 4 additions & 0 deletions mobile-app/api/realtime/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { TextEncoder } from 'text-encoding';

import { Logger, ScopedLogger } from '@/utils/logging';

Check warning on line 3 in mobile-app/api/realtime/index.ts

View workflow job for this annotation

GitHub Actions / Lint Code

'Logger' is defined but never used

/**
* stompjs is an abstraction layer on top of websocket that uses the global TextEncoder class.
Expand Down Expand Up @@ -65,6 +65,7 @@

this.ws.addEventListener('close', () => {
this.logger.info('connection closed');
this.connect();
});

this.ws.addEventListener('error', (e) => {
Expand Down Expand Up @@ -137,4 +138,7 @@
this.registerHandler('*', handler);
},
};
public get isOpen(): boolean {
return this.ws.OPEN === 1;
}
}
17 changes: 6 additions & 11 deletions mobile-app/api/realtime/useRealtimeConnection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,7 @@
import { useLogging } from '@/utils/useLogging';
import { useGroupStore } from '@/zustand/group/stateGroupStore';

import {
RealtimeAffectedEntity,
RealtimeClient,
RealtimeEvent,
RealtimeEventHandler,
} from '.';
import { RealtimeClient, RealtimeEventHandler } from '.';
import { env } from '../env';
import { ignoreSeason, QK } from '../utils/reactQuery';

Expand Down Expand Up @@ -39,7 +34,7 @@

// refetch because of PlayerDto.statistics.matches
qc.invalidateQueries({
queryKey: [QK.group, e.groupId, QK.players],
predicate: ignoreSeason([QK.group, e.groupId, QK.players]),
});

client.current.logger.info('refetching matches');
Expand All @@ -54,18 +49,18 @@

// refetch because a newly created season will have new players
qc.invalidateQueries({
queryKey: [QK.group, e.groupId, QK.players],
predicate: ignoreSeason([QK.group, e.groupId, QK.players]),
});

// refetch because a newly created season will have no matches
qc.invalidateQueries({
queryKey: [QK.group, e.groupId, QK.matches],
predicate: ignoreSeason([QK.group, e.groupId, QK.matches]),
});

client.current.logger.info('refetching seasons');

qc.invalidateQueries({
queryKey: [QK.group, e.groupId, QK.seasons],
predicate: ignoreSeason([QK.group, e.groupId, QK.seasons]),
});
break;
case 'PLAYERS':
Expand All @@ -74,7 +69,7 @@

// TODO: only refetch matches on player delete
qc.invalidateQueries({
queryKey: [QK.group, e.groupId, QK.matches],
predicate: ignoreSeason([QK.group, e.groupId, QK.matches]),
});

client.current.logger.info('refetching players');
Expand Down Expand Up @@ -119,9 +114,9 @@
client.current.on.event(hoher);

return () =>
client.current.logger.removeEventListener('*', writeLogs);

Check warning on line 117 in mobile-app/api/realtime/useRealtimeConnection.ts

View workflow job for this annotation

GitHub Actions / Lint Code

The ref value 'client.current' will likely have changed by the time this effect cleanup function runs. If this ref points to a node rendered by React, copy 'client.current' to a variable inside the effect, and use that variable in the cleanup function
}
}, [client.current]);

Check warning on line 119 in mobile-app/api/realtime/useRealtimeConnection.ts

View workflow job for this annotation

GitHub Actions / Lint Code

React Hook useEffect has missing dependencies: 'hoher' and 'writeLogs'. Either include them or remove the dependency array. Mutable values like 'client.current' aren't valid dependencies because mutating them doesn't re-render the component

useEffect(() => {
client.current.subscribeToGroups(groupIds);
Expand Down
51 changes: 51 additions & 0 deletions mobile-app/api/utils/create-api.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import * as Sentry from '@sentry/react-native';
import OpenAPIClientAxios, { Document } from 'openapi-client-axios';
import React, {
createContext,
Expand All @@ -7,6 +8,8 @@
useState,
} from 'react';

import { useLogging } from '@/utils/useLogging';

import beerpongDefinition from '../../api/generated/openapi.json';
import { Client as BeerPongClient } from '../../openapi/openapi';
import { env } from '../env';
Expand All @@ -33,6 +36,7 @@
const api = openApi.getClient<BeerPongClient>();

const realtime = useRealtimeConnection();
const { writeLog } = useLogging();

const [isLoading, setIsLoading] = useState(true);
const [error, setError] = useState<Error | null>(null);
Expand All @@ -41,6 +45,53 @@
const initializeApi = async () => {
try {
await openApi.init();

const awaitedApi = await api;

awaitedApi.interceptors.response.use(
(res) => res,
(err) => {
if (err.response) {
writeLog(
'[api] request failed:',
err.config?.method,
err.config?.url,
err.response.status,
err.response.data
);
Sentry.captureException(err, {
extra: {
url: err.config?.url,
method: err.config?.method,
status: err.response.status,
statusText: err.response.statusText,
responseData: err.response.data,
},
});
} else if (err.request) {
writeLog(
'[api] no response received:',
err.config?.method,
err.config?.url
);
Sentry.captureException(err, {
extra: {
url: err.config?.url,
method: err.config?.method,
request: err.request,
},
});
} else {
writeLog('[api] setup error:', err.message);
Sentry.captureException(err, {
extra: {
message: err.message,
},
});
}
return Promise.reject(err);
}
);
} catch (err) {
setError(
err instanceof Error
Expand All @@ -52,7 +103,7 @@
}
};
initializeApi();
}, []);

Check warning on line 106 in mobile-app/api/utils/create-api.tsx

View workflow job for this annotation

GitHub Actions / Lint Code

React Hook useEffect has missing dependencies: 'api' and 'writeLog'. Either include them or remove the dependency array

const contextValue: ApiContextType = {
realtime,
Expand Down
21 changes: 20 additions & 1 deletion mobile-app/app/debugLog.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import dayjs from 'dayjs';
import { Stack } from 'expo-router';
import React, { useEffect, useState } from 'react';
import { ScrollView } from 'react-native';
import { GestureHandlerRootView } from 'react-native-gesture-handler';

import { env } from '@/api/env';
import { useApi } from '@/api/utils/create-api';
import { navStyles } from '@/app/navigation/navStyles';
import { Heading } from '@/components/Menu/MenuSection';
import Text from '@/components/Text';
Expand All @@ -28,6 +30,16 @@ function stringifyLogs(logs: Logs): string {
export default function Page() {
const { logs } = useLogging();

const [isRealtimeOpen, setIsRealtimeOpen] = useState(false);

const { realtime } = useApi();

useEffect(() => {
// we need to keep this in state because `realtime` is a ref and will not cause a rerender if it changes,
// so the indicator could be misleading
setIsRealtimeOpen(realtime.isOpen);
}, [realtime.isOpen]);

return (
<GestureHandlerRootView>
<Stack.Screen
Expand All @@ -48,7 +60,14 @@ export default function Page() {
paddingBottom: 128,
}}
>
<Heading title="Debug Logs" />
<Heading
title={
<>
Debug Logs{' '}
{env.isDev ? (isRealtimeOpen ? '✅' : '❌') : ''}
</>
}
/>
{logs.map((i, idx) => (
<Text color="primary" key={idx} style={{ fontSize: 12 }}>
<Text color="secondary" style={{ fontSize: 12 }}>
Expand Down
61 changes: 35 additions & 26 deletions mobile-app/components/MatchPlayers/Player.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,35 @@ import {
} from 'react-native';
import Icon from 'react-native-vector-icons/MaterialCommunityIcons';

import { env } from '@/api/env';
import { TeamMember } from '@/api/utils/matchDtoToMatch';
import { useNavigation } from '@/app/navigation/useNavigation';
import Avatar from '@/components/Avatar';
import { theme } from '@/theme';

import Text from '../Text';

function Change({ value }: { value: number }) {
return (
<>
<Icon
color={value >= 0 ? theme.color.positive : theme.color.negative}
size={8}
name="triangle"
style={{
marginLeft: 8,
marginRight: 2,
marginTop: 1,
transform: value >= 0 ? undefined : [{ rotateX: '180deg' }],
}}
/>
<Text variant="body2" color={value >= 0 ? 'positive' : 'negative'}>
{Math.abs(value)}
</Text>
</>
);
}

export interface PlayerProps {
player: TeamMember;

Expand Down Expand Up @@ -48,7 +70,7 @@ export default function Player({
// Interpolate the animated value to control height
const contentHeight = animation.interpolate({
inputRange: [0, 1],
outputRange: [0, 44 * 8], // customize the height range based on your content
outputRange: [0, 44 * moves.length], // customize the height range based on your content
});

const nav = useNavigation();
Expand Down Expand Up @@ -94,37 +116,24 @@ export default function Player({
{points} points
</Text>
) : (
<Text variant="body2" color="tertiary">
<Text
variant="body2"
color="tertiary"
style={{
fontStyle:
moves.length < 1
? 'italic'
: undefined,
}}
>
{moves.length < 1 && 'No moves'}
{moves
.filter((i) => i.count > 0)
.map((i) => i.count + ' ' + i.title)
.join(', ')}
</Text>
)}
<Icon
color={
change >= 0
? theme.color.positive
: theme.color.negative
}
size={8}
name="triangle"
style={{
marginLeft: 8,
marginRight: 2,
marginTop: 1,
transform:
change >= 0
? undefined
: [{ rotateX: '180deg' }],
}}
/>
<Text
variant="body2"
color={change >= 0 ? 'positive' : 'negative'}
>
{Math.abs(change)}
</Text>
{env.isDev && <Change value={change} />}
</View>
</View>
{editable ? (
Expand Down
18 changes: 14 additions & 4 deletions mobile-app/components/MatchVsHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
hasScore = true,
...rest
}: MatchVsHeaderProps) {
const redWon = match.redCups > match.blueCups;

Check warning on line 23 in mobile-app/components/MatchVsHeader.tsx

View workflow job for this annotation

GitHub Actions / Lint Code

'redWon' is assigned a value but never used

const winner: TeamId =
match.redCups > match.blueCups
Expand All @@ -45,7 +45,12 @@
{Array(Math.max(MAX_ITEMS - match.blueTeam.length, 0))
.fill(null)
.map((_, index) => {
return <Avatar key={index} style={{ opacity: 0 }} />;
return (
<Avatar
key={index}
style={{ opacity: 0, marginLeft: -16 }}
/>
);
})}
{match.blueTeam.slice(0, MAX_ITEMS).map((i, index) => (
<Avatar
Expand All @@ -59,7 +64,7 @@
}
name={i.name}
borderColor={theme.color.team.blue}
style={{ marginLeft: index === 0 ? 0 : -16 }}
style={{ marginLeft: -16 }}
/>
))}
</View>
Expand Down Expand Up @@ -110,13 +115,18 @@
}
name={i.name}
borderColor={theme.color.team.red}
style={{ marginLeft: index === 0 ? 0 : -16 }}
style={{ marginRight: -16 }}
/>
))}
{Array(Math.max(MAX_ITEMS - match.redTeam.length, 0))
.fill(null)
.map((_, index) => {
return <Avatar key={index} style={{ opacity: 0 }} />;
return (
<Avatar
key={index}
style={{ opacity: 0, marginRight: -16 }}
/>
);
})}
</View>
</View>
Expand Down
2 changes: 1 addition & 1 deletion mobile-app/components/Menu/MenuSection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ export function Heading({
}

export interface MenuSectionProps extends PropsWithChildren {
title?: string;
title?: JSX.Element | string;
titleHeadIcon?: JSX.Element;

background?: boolean;
Expand Down
Loading
Loading