From e6ba0ef2e9882aa0599eb04b940142e25f7ea9fc Mon Sep 17 00:00:00 2001 From: James Monger Date: Thu, 24 Aug 2023 22:45:17 +0000 Subject: [PATCH 01/16] refactor: remove unused Auth0User --- apps/web-menu/src/app.tsx | 5 ++--- modules/@creature-chess/auth-server/src/user.ts | 2 -- modules/@creature-chess/auth-web/index.ts | 1 - modules/@creature-chess/auth-web/types.ts | 5 ----- modules/@creature-chess/auth-web/useUser.ts | 3 +-- 5 files changed, 3 insertions(+), 13 deletions(-) delete mode 100644 modules/@creature-chess/auth-web/types.ts diff --git a/apps/web-menu/src/app.tsx b/apps/web-menu/src/app.tsx index b560ce9e..82677500 100644 --- a/apps/web-menu/src/app.tsx +++ b/apps/web-menu/src/app.tsx @@ -3,7 +3,7 @@ import * as React from "react"; import { useAuth0 } from "@auth0/auth0-react"; import ReactModal from "react-modal"; -import { Auth0User, useUser } from "@creature-chess/auth-web"; +import { useUser } from "@creature-chess/auth-web"; import { MenuPage, MenuPageContextProvider, @@ -21,8 +21,7 @@ const UnauthenticatedRootPage: React.FunctionComponent = () => { }; const AuthenticatedRootPage: React.FunctionComponent = () => { - const { logout, getAccessTokenSilently, getIdTokenClaims } = - useAuth0(); + const { logout, getAccessTokenSilently, getIdTokenClaims } = useAuth0(); const { user, isFetching, refresh } = useUser(); diff --git a/modules/@creature-chess/auth-server/src/user.ts b/modules/@creature-chess/auth-server/src/user.ts index 20c38b10..adff887b 100644 --- a/modules/@creature-chess/auth-server/src/user.ts +++ b/modules/@creature-chess/auth-server/src/user.ts @@ -18,8 +18,6 @@ export interface UserModel { profile: PlayerProfile | null; } -export type Auth0User = User; - export const convertDatabaseUserToUserModel = ( user: DatabaseUser ): UserModel => { diff --git a/modules/@creature-chess/auth-web/index.ts b/modules/@creature-chess/auth-web/index.ts index 1559aaf4..d7b93c1f 100644 --- a/modules/@creature-chess/auth-web/index.ts +++ b/modules/@creature-chess/auth-web/index.ts @@ -5,5 +5,4 @@ export { GameAuthContextProvider, usePlayerId, } from "./context"; -export { Auth0User } from "./types"; export { useUser } from "./useUser"; diff --git a/modules/@creature-chess/auth-web/types.ts b/modules/@creature-chess/auth-web/types.ts deleted file mode 100644 index f5884382..00000000 --- a/modules/@creature-chess/auth-web/types.ts +++ /dev/null @@ -1,5 +0,0 @@ -export type Auth0User = { - "https://creaturechess.jamesmonger.com/playerId": string | null; - "https://creaturechess.jamesmonger.com/playerNickname": string | null; - "https://creaturechess.jamesmonger.com/playerPicture": number | null; -}; diff --git a/modules/@creature-chess/auth-web/useUser.ts b/modules/@creature-chess/auth-web/useUser.ts index 5b6b1b46..5d748d83 100644 --- a/modules/@creature-chess/auth-web/useUser.ts +++ b/modules/@creature-chess/auth-web/useUser.ts @@ -5,7 +5,6 @@ import { useAuth0 } from "@auth0/auth0-react"; import { SanitizedUser } from "@creature-chess/models"; import { getCurrentUser } from "./getCurrentUser"; -import { Auth0User } from "./types"; /** * Calls the current user endpoint and returns the user @@ -20,7 +19,7 @@ import { Auth0User } from "./types"; * - refresh: a function to refresh the user */ export function useUser() { - const { user: auth0User, getAccessTokenSilently } = useAuth0(); + const { user: auth0User, getAccessTokenSilently } = useAuth0(); const [currentUser, setCurrentUser] = React.useState( null From 9735024f0a49a951d9893b09f4194e35a886fce1 Mon Sep 17 00:00:00 2001 From: James Monger Date: Thu, 24 Aug 2023 23:01:05 +0000 Subject: [PATCH 02/16] refactor: remove unused UserAppMetadata --- apps/server-game/src/external/auth0.ts | 6 ++---- apps/server-game/src/handshake/types.ts | 3 +-- apps/server-info/src/util/auth0.ts | 4 +--- modules/@creature-chess/auth-server/index.ts | 1 - modules/@creature-chess/auth-server/src/authenticate.ts | 3 +-- modules/@creature-chess/auth-server/src/user.ts | 8 -------- package.json | 3 ++- 7 files changed, 7 insertions(+), 21 deletions(-) diff --git a/apps/server-game/src/external/auth0.ts b/apps/server-game/src/external/auth0.ts index 1ebe7fd7..c1f9c89a 100644 --- a/apps/server-game/src/external/auth0.ts +++ b/apps/server-game/src/external/auth0.ts @@ -1,8 +1,6 @@ import { ManagementClient } from "auth0"; -import { UserAppMetadata } from "@creature-chess/auth-server"; - -export const createManagementClient = (): ManagementClient => { +export const createManagementClient = (): ManagementClient => { const { AUTH0_DOMAIN, AUTH0_MACHINE_TO_MACHINE_CLIENT_ID, @@ -17,7 +15,7 @@ export const createManagementClient = (): ManagementClient => { throw Error("No Auth0 configuration found"); } - const client = new ManagementClient({ + const client = new ManagementClient({ domain: AUTH0_DOMAIN, clientId: AUTH0_MACHINE_TO_MACHINE_CLIENT_ID, clientSecret: AUTH0_MANAGEMENT_CLIENT_SECRET, diff --git a/apps/server-game/src/handshake/types.ts b/apps/server-game/src/handshake/types.ts index b1c15ec5..73dc46e7 100644 --- a/apps/server-game/src/handshake/types.ts +++ b/apps/server-game/src/handshake/types.ts @@ -1,12 +1,11 @@ import { ManagementClient } from "auth0"; import { Server } from "socket.io"; -import { UserAppMetadata } from "@creature-chess/auth-server"; import { DatabaseConnection } from "@creature-chess/data"; export type HandshakeListenerDependencies = { io: Server; - authClient: ManagementClient; + authClient: ManagementClient; database: DatabaseConnection; }; diff --git a/apps/server-info/src/util/auth0.ts b/apps/server-info/src/util/auth0.ts index 2d224341..ecc77b62 100644 --- a/apps/server-info/src/util/auth0.ts +++ b/apps/server-info/src/util/auth0.ts @@ -1,7 +1,5 @@ import { ManagementClient } from "auth0"; -import { UserAppMetadata } from "@creature-chess/auth-server"; - export function getManagementClient() { const { AUTH0_DOMAIN, @@ -17,7 +15,7 @@ export function getManagementClient() { throw Error("No Auth0 configuration found"); } - const client = new ManagementClient({ + const client = new ManagementClient({ domain: AUTH0_DOMAIN, clientId: AUTH0_MACHINE_TO_MACHINE_CLIENT_ID, clientSecret: AUTH0_MANAGEMENT_CLIENT_SECRET, diff --git a/modules/@creature-chess/auth-server/index.ts b/modules/@creature-chess/auth-server/index.ts index 474b4738..07556993 100644 --- a/modules/@creature-chess/auth-server/index.ts +++ b/modules/@creature-chess/auth-server/index.ts @@ -1,6 +1,5 @@ export { authenticate } from "./src/authenticate"; export { - UserAppMetadata, UserModel, convertDatabaseUserToUserModel, } from "./src/user"; diff --git a/modules/@creature-chess/auth-server/src/authenticate.ts b/modules/@creature-chess/auth-server/src/authenticate.ts index cbf63024..f67b8c12 100644 --- a/modules/@creature-chess/auth-server/src/authenticate.ts +++ b/modules/@creature-chess/auth-server/src/authenticate.ts @@ -4,13 +4,12 @@ import { DatabaseConnection } from "@creature-chess/data"; import { convertDatabaseUserToUserModel, - UserAppMetadata, UserModel, } from "./user"; import { verifyDecodeJwt } from "./verifyDecodeJwt"; export const authenticate = async ( - managementClient: ManagementClient, + managementClient: ManagementClient, database: DatabaseConnection, token: string ): Promise => { diff --git a/modules/@creature-chess/auth-server/src/user.ts b/modules/@creature-chess/auth-server/src/user.ts index adff887b..e3045931 100644 --- a/modules/@creature-chess/auth-server/src/user.ts +++ b/modules/@creature-chess/auth-server/src/user.ts @@ -1,14 +1,6 @@ -import { User, UserMetadata } from "auth0"; - import { DatabaseUser } from "@creature-chess/data"; import { PlayerProfile } from "@creature-chess/models"; -export interface UserAppMetadata { - playerId: string; - playerNickname: string | null; - playerPicture: number | null; -} - export interface UserModel { id: number; authId: string; diff --git a/package.json b/package.json index cfa2db00..3d051118 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,8 @@ "build-storybook": "build-storybook -o ./output", "lint": "eslint \"apps/**/*.ts\" \"modules/**/*.ts\" \"apps/**/*.tsx\" \"modules/**/*.tsx\"", "prettier": "prettier -w ./", - "prettier-check": "prettier -c ./" + "prettier-check": "prettier -c ./", + "build": "yarn workspaces foreach run build" }, "workspaces": [ "modules/@creature-chess/*", From e01534ec2e833ff6e2d3bdac0b6c6eeaf1c82f08 Mon Sep 17 00:00:00 2001 From: James Monger Date: Thu, 24 Aug 2023 23:08:39 +0000 Subject: [PATCH 03/16] refactor: create UserDTO --- apps/server-info/index.ts | 6 +++--- .../src/util/{sanitize-user.ts => user-model-to-dto.ts} | 4 ++-- modules/@creature-chess/auth-web/context.tsx | 4 ++-- modules/@creature-chess/auth-web/useUser.ts | 4 ++-- modules/@creature-chess/models/{src => dto}/user.ts | 2 +- modules/@creature-chess/models/index.ts | 1 - 6 files changed, 10 insertions(+), 11 deletions(-) rename apps/server-info/src/util/{sanitize-user.ts => user-model-to-dto.ts} (71%) rename modules/@creature-chess/models/{src => dto}/user.ts (77%) diff --git a/apps/server-info/index.ts b/apps/server-info/index.ts index fe6da038..fcf9dcd9 100644 --- a/apps/server-info/index.ts +++ b/apps/server-info/index.ts @@ -16,7 +16,7 @@ import { import { logger } from "./src/log"; import { getManagementClient } from "./src/util/auth0"; -import { sanitize } from "./src/util/sanitize-user"; +import { userModelToDto } from "./src/util/user-model-to-dto"; import Filter = require("bad-words"); @@ -104,7 +104,7 @@ async function startServer() { authorization as string ); - res.status(200).json(sanitize(user)); + res.status(200).json(userModelToDto(user)); }); app.patch("/user/current", async (req, res) => { @@ -168,7 +168,7 @@ async function startServer() { }); } - res.status(200).json(sanitize(convertDatabaseUserToUserModel(updatedUser))); + res.status(200).json(userModelToDto(convertDatabaseUserToUserModel(updatedUser))); }); // Start the server diff --git a/apps/server-info/src/util/sanitize-user.ts b/apps/server-info/src/util/user-model-to-dto.ts similarity index 71% rename from apps/server-info/src/util/sanitize-user.ts rename to apps/server-info/src/util/user-model-to-dto.ts index 44d554bf..f26a27a5 100644 --- a/apps/server-info/src/util/sanitize-user.ts +++ b/apps/server-info/src/util/user-model-to-dto.ts @@ -1,7 +1,7 @@ import { UserModel } from "@creature-chess/auth-server"; -import { SanitizedUser } from "@creature-chess/models"; +import { UserDTO } from "@creature-chess/models/dto/user"; -export const sanitize = (user: UserModel): SanitizedUser => { +export const userModelToDto = (user: UserModel): UserDTO => { const { id, nickname, stats, registered } = user; return { diff --git a/modules/@creature-chess/auth-web/context.tsx b/modules/@creature-chess/auth-web/context.tsx index fa627e45..a0fac726 100644 --- a/modules/@creature-chess/auth-web/context.tsx +++ b/modules/@creature-chess/auth-web/context.tsx @@ -1,11 +1,11 @@ import * as React from "react"; -import { SanitizedUser } from "@creature-chess/models"; +import { UserDTO } from "@creature-chess/models/dto/user"; import { useUser } from "./useUser"; const GameAuthContext = React.createContext<{ - user: SanitizedUser | null; + user: UserDTO | null; }>({ user: null, }); diff --git a/modules/@creature-chess/auth-web/useUser.ts b/modules/@creature-chess/auth-web/useUser.ts index 5d748d83..c69ffe38 100644 --- a/modules/@creature-chess/auth-web/useUser.ts +++ b/modules/@creature-chess/auth-web/useUser.ts @@ -2,7 +2,7 @@ import React from "react"; import { useAuth0 } from "@auth0/auth0-react"; -import { SanitizedUser } from "@creature-chess/models"; +import { UserDTO } from "@creature-chess/models/dto/user"; import { getCurrentUser } from "./getCurrentUser"; @@ -21,7 +21,7 @@ import { getCurrentUser } from "./getCurrentUser"; export function useUser() { const { user: auth0User, getAccessTokenSilently } = useAuth0(); - const [currentUser, setCurrentUser] = React.useState( + const [currentUser, setCurrentUser] = React.useState( null ); const [isFetching, setIsFetching] = React.useState(false); diff --git a/modules/@creature-chess/models/src/user.ts b/modules/@creature-chess/models/dto/user.ts similarity index 77% rename from modules/@creature-chess/models/src/user.ts rename to modules/@creature-chess/models/dto/user.ts index 4ef1fda1..0040ba18 100644 --- a/modules/@creature-chess/models/src/user.ts +++ b/modules/@creature-chess/models/dto/user.ts @@ -1,4 +1,4 @@ -export interface SanitizedUser { +export interface UserDTO { id: string; nickname: string | null; stats: { diff --git a/modules/@creature-chess/models/index.ts b/modules/@creature-chess/models/index.ts index 60d0cd27..5e2f1c8b 100644 --- a/modules/@creature-chess/models/index.ts +++ b/modules/@creature-chess/models/index.ts @@ -23,7 +23,6 @@ export { GamePhase } from "./src/game-phase"; export { RoundInfoState } from "./src/roundInfoState"; export { PlayerTitle, TITLES } from "./src/titles"; export { PlayerProfile } from "./src/playerProfile"; -export { SanitizedUser } from "./src/user"; export { TileType, From 90b005ba306b8912f9b453d07b202557d039224f Mon Sep 17 00:00:00 2001 From: James Monger Date: Fri, 25 Aug 2023 00:38:30 +0000 Subject: [PATCH 04/16] refactor: make auth0 imports optional --- apps/server-game/src/external/auth0.ts | 5 + apps/server-info/src/util/auth0.ts | 5 + .../src/game/board/BoardContainer.stories.tsx | 2 - .../src/game/board/hooks/renderPiece.tsx | 6 +- .../overlays/quickChat/quickChatButton.tsx | 4 +- .../src/game/board/overlays/readyOverlay.tsx | 4 +- .../layouts/desktop/DesktopGame.stories.tsx | 21 ++--- .../layouts/mobile/MobileGame.stories.tsx | 21 ++--- .../playerList/components/playerList.tsx | 4 +- .../src/game/module/profile/profile.tsx | 4 +- apps/web-game/src/index.tsx | 37 ++++++-- apps/web-menu/src/app.tsx | 91 ++----------------- apps/web-menu/src/index.tsx | 35 +++++-- apps/web-menu/src/pages/home.tsx | 37 ++++++++ apps/web-menu/src/pages/login.tsx | 24 +++++ apps/web-menu/src/pages/register.tsx | 41 +++++++++ docker-compose.yml | 1 + .../auth-server/src/authenticate.ts | 9 +- .../auth-web/{ => auth0}/config.ts | 2 + .../auth0/internal/localUserProvider.tsx | 34 +++++++ .../internal/useLocalUserDto.ts} | 4 +- .../auth-web/{ => auth0}/provider.tsx | 11 ++- modules/@creature-chess/auth-web/context.tsx | 41 ++------- .../auth-web/{ => http}/getCurrentUser.ts | 0 modules/@creature-chess/auth-web/index.ts | 8 -- modules/@creature-chess/auth-web/player.ts | 14 +++ .../@creature-chess/auth-web/tsconfig.json | 5 +- .../ui/pages/menu/login/LoginPage.tsx | 33 +++++-- 28 files changed, 316 insertions(+), 187 deletions(-) create mode 100644 apps/web-menu/src/pages/home.tsx create mode 100644 apps/web-menu/src/pages/login.tsx create mode 100644 apps/web-menu/src/pages/register.tsx rename modules/@creature-chess/auth-web/{ => auth0}/config.ts (80%) create mode 100644 modules/@creature-chess/auth-web/auth0/internal/localUserProvider.tsx rename modules/@creature-chess/auth-web/{useUser.ts => auth0/internal/useLocalUserDto.ts} (94%) rename modules/@creature-chess/auth-web/{ => auth0}/provider.tsx (68%) rename modules/@creature-chess/auth-web/{ => http}/getCurrentUser.ts (100%) delete mode 100644 modules/@creature-chess/auth-web/index.ts create mode 100644 modules/@creature-chess/auth-web/player.ts diff --git a/apps/server-game/src/external/auth0.ts b/apps/server-game/src/external/auth0.ts index c1f9c89a..215b181e 100644 --- a/apps/server-game/src/external/auth0.ts +++ b/apps/server-game/src/external/auth0.ts @@ -1,6 +1,11 @@ +import { AUTH0_ENABLED } from "@creature-chess/auth-web/auth0/config"; import { ManagementClient } from "auth0"; export const createManagementClient = (): ManagementClient => { + if (!AUTH0_ENABLED) { + return null as any; + } + const { AUTH0_DOMAIN, AUTH0_MACHINE_TO_MACHINE_CLIENT_ID, diff --git a/apps/server-info/src/util/auth0.ts b/apps/server-info/src/util/auth0.ts index ecc77b62..1bbe5374 100644 --- a/apps/server-info/src/util/auth0.ts +++ b/apps/server-info/src/util/auth0.ts @@ -1,6 +1,11 @@ +import { AUTH0_ENABLED } from "@creature-chess/auth-web/auth0/config"; import { ManagementClient } from "auth0"; export function getManagementClient() { + if (!AUTH0_ENABLED) { + return null as any; + } + const { AUTH0_DOMAIN, AUTH0_MACHINE_TO_MACHINE_CLIENT_ID, diff --git a/apps/web-game/src/game/board/BoardContainer.stories.tsx b/apps/web-game/src/game/board/BoardContainer.stories.tsx index 1b1729b3..93ace038 100644 --- a/apps/web-game/src/game/board/BoardContainer.stories.tsx +++ b/apps/web-game/src/game/board/BoardContainer.stories.tsx @@ -3,8 +3,6 @@ import React from "react"; import { Meta, Story } from "@storybook/react"; import { Provider } from "react-redux"; -import { AuthContextProvider } from "@creature-chess/auth-web"; -import { GamePhase } from "@creature-chess/models"; import { useGlobalStyles } from "@creature-chess/ui"; import { createMockStore } from "../layouts/stories-utils"; diff --git a/apps/web-game/src/game/board/hooks/renderPiece.tsx b/apps/web-game/src/game/board/hooks/renderPiece.tsx index 6999a704..722e7cf7 100644 --- a/apps/web-game/src/game/board/hooks/renderPiece.tsx +++ b/apps/web-game/src/game/board/hooks/renderPiece.tsx @@ -1,13 +1,13 @@ import React from "react"; -import { usePlayerId } from "@creature-chess/auth-web"; +import { useLocalPlayerId } from "@creature-chess/auth-web/context"; import { PieceModel } from "@creature-chess/models"; import { MatchPiece, PieceContextProvider } from "@creature-chess/ui"; import { SelectablePiece } from "../piece/SelectablePiece"; const useRenderSelectablePiece = () => { - const viewingPlayerId = usePlayerId(); + const viewingPlayerId = useLocalPlayerId(); return (piece: PieceModel): React.ReactNode => ( @@ -20,7 +20,7 @@ export const useRenderBoardPiece = useRenderSelectablePiece; export const useRenderBenchPiece = useRenderSelectablePiece; export const useRenderMatchBoardPiece = () => { - const viewingPlayerId = usePlayerId(); + const viewingPlayerId = useLocalPlayerId(); return (piece: PieceModel): React.ReactNode => ( diff --git a/apps/web-game/src/game/board/overlays/quickChat/quickChatButton.tsx b/apps/web-game/src/game/board/overlays/quickChat/quickChatButton.tsx index f83d9f1f..76fd0be4 100644 --- a/apps/web-game/src/game/board/overlays/quickChat/quickChatButton.tsx +++ b/apps/web-game/src/game/board/overlays/quickChat/quickChatButton.tsx @@ -4,7 +4,7 @@ import React from "react"; import { createUseStyles } from "react-jss"; import { useDispatch } from "react-redux"; -import { usePlayerId } from "@creature-chess/auth-web"; +import { useLocalPlayerId } from "@creature-chess/auth-web/context"; import { PlayerActions } from "@creature-chess/gamemode"; import { QuickChatOption } from "@creature-chess/models"; @@ -20,7 +20,7 @@ const QuickChatButton: React.FunctionComponent<{ const styles = useStyles(); const dispatch = useDispatch(); - const sendingPlayerId = usePlayerId(); + const sendingPlayerId = useLocalPlayerId(); const onClick = () => { dispatch( diff --git a/apps/web-game/src/game/board/overlays/readyOverlay.tsx b/apps/web-game/src/game/board/overlays/readyOverlay.tsx index ea717f4d..b80a7de2 100644 --- a/apps/web-game/src/game/board/overlays/readyOverlay.tsx +++ b/apps/web-game/src/game/board/overlays/readyOverlay.tsx @@ -3,7 +3,7 @@ import * as React from "react"; import { createUseStyles } from "react-jss"; import { useSelector } from "react-redux"; -import { usePlayerId } from "@creature-chess/auth-web"; +import { useLocalPlayerId } from "@creature-chess/auth-web/context"; import { GamePhase } from "@creature-chess/models"; import { Label, StreakIndicator } from "@creature-chess/ui"; import { @@ -56,7 +56,7 @@ const ReadyOverlay: React.FunctionComponent = () => { const playerList = useSelector((state: AppState) => state.game.playerList); - const localId = usePlayerId(); + const localId = useLocalPlayerId(); const localPlayer = playerList.find((p) => p.id === localId); const opponent = useSelector((state: AppState) => { diff --git a/apps/web-game/src/game/layouts/desktop/DesktopGame.stories.tsx b/apps/web-game/src/game/layouts/desktop/DesktopGame.stories.tsx index bfd0592f..df2230d0 100644 --- a/apps/web-game/src/game/layouts/desktop/DesktopGame.stories.tsx +++ b/apps/web-game/src/game/layouts/desktop/DesktopGame.stories.tsx @@ -3,7 +3,7 @@ import React from "react"; import { Meta, Story } from "@storybook/react"; import { Provider } from "react-redux"; -import { AuthContextProvider } from "@creature-chess/auth-web"; +import { LocalPlayerContextProvider } from "@creature-chess/auth-web/context"; import { GamePhase } from "@creature-chess/models"; import { useGlobalStyles } from "@creature-chess/ui"; @@ -42,23 +42,22 @@ const Template: Story = (args) => { return (
- - +
); }; diff --git a/apps/web-game/src/game/layouts/mobile/MobileGame.stories.tsx b/apps/web-game/src/game/layouts/mobile/MobileGame.stories.tsx index dda72caf..07d78cbb 100644 --- a/apps/web-game/src/game/layouts/mobile/MobileGame.stories.tsx +++ b/apps/web-game/src/game/layouts/mobile/MobileGame.stories.tsx @@ -3,7 +3,7 @@ import React from "react"; import { Meta, Story } from "@storybook/react"; import { Provider } from "react-redux"; -import { AuthContextProvider } from "@creature-chess/auth-web"; +import { LocalPlayerContextProvider } from "@creature-chess/auth-web/context"; import { GamePhase } from "@creature-chess/models"; import { useGlobalStyles } from "@creature-chess/ui"; @@ -53,23 +53,22 @@ const Template: Story = (args) => { ); return ( - - + ); }; diff --git a/apps/web-game/src/game/module/playerList/components/playerList.tsx b/apps/web-game/src/game/module/playerList/components/playerList.tsx index 4dfa85db..7cde9bc4 100644 --- a/apps/web-game/src/game/module/playerList/components/playerList.tsx +++ b/apps/web-game/src/game/module/playerList/components/playerList.tsx @@ -2,7 +2,7 @@ import * as React from "react"; import { useDispatch, useSelector } from "react-redux"; -import { usePlayerId } from "@creature-chess/auth-web"; +import { useLocalPlayerId } from "@creature-chess/auth-web/context"; import { PlayerActions } from "@creature-chess/gamemode"; import { PlayerListPlayer, @@ -44,7 +44,7 @@ const getOpponentName = (battle: PlayerBattle, players: PlayerListPlayer[]) => { const PlayerList: React.FunctionComponent = () => { const dispatch = useDispatch(); - const localPlayerId = usePlayerId(); + const localPlayerId = useLocalPlayerId(); const players = useSelector( (state) => state.game.playerList ); diff --git a/apps/web-game/src/game/module/profile/profile.tsx b/apps/web-game/src/game/module/profile/profile.tsx index 6b0ee757..2d1cb932 100644 --- a/apps/web-game/src/game/module/profile/profile.tsx +++ b/apps/web-game/src/game/module/profile/profile.tsx @@ -4,7 +4,7 @@ import { useDispatch, useSelector } from "react-redux"; import { BoardSelectors, BoardState } from "@shoki/board"; -import { usePlayerId } from "@creature-chess/auth-web"; +import { useLocalPlayerId } from "@creature-chess/auth-web/context"; import { getPlayerLevel, getPlayerXp, @@ -19,7 +19,7 @@ import { AppState } from "../../../store"; export const Profile: React.FunctionComponent = () => { const dispatch = useDispatch(); - const playerId = usePlayerId(); + const playerId = useLocalPlayerId(); const level = useSelector((state) => getPlayerLevel(state.game) diff --git a/apps/web-game/src/index.tsx b/apps/web-game/src/index.tsx index c48bd52f..988206e7 100644 --- a/apps/web-game/src/index.tsx +++ b/apps/web-game/src/index.tsx @@ -1,27 +1,52 @@ import * as React from "react"; +import { LocalPlayerContextProvider } from "modules/@creature-chess/auth-web/context"; import "pepjs"; import * as ReactDOM from "react-dom"; import { Provider as ReduxProvider } from "react-redux"; -import { AuthProvider } from "@creature-chess/auth-web"; +import { AUTH0_ENABLED } from "@creature-chess/auth-web/auth0/config"; import { App } from "./app"; import { createAppStore } from "./store"; -const AppRoot: React.FunctionComponent = () => { - const onRedirectCallback = (appState: any) => { +const Auth0AppRoot = ({ children }: { children: React.ReactNode }) => { + const onRedirectCallback = React.useCallback((appState: any) => { window.location.href = appState?.returnTo || window.location.pathname; - }; + }, []); + const Auth0AuthProvider = React.useMemo( + // eslint-disable-next-line @typescript-eslint/no-var-requires + () => require("@creature-chess/auth-web/auth0/provider").Auth0AuthProvider, + [] + ); + + return ( + + {children} + + ); +}; + +const AppRoot = () => { const store = createAppStore(); + if (AUTH0_ENABLED) { + return ( + + + + + + ); + } + return ( - + - + ); }; diff --git a/apps/web-menu/src/app.tsx b/apps/web-menu/src/app.tsx index 82677500..39882d89 100644 --- a/apps/web-menu/src/app.tsx +++ b/apps/web-menu/src/app.tsx @@ -1,100 +1,25 @@ import * as React from "react"; -import { useAuth0 } from "@auth0/auth0-react"; import ReactModal from "react-modal"; -import { useUser } from "@creature-chess/auth-web"; -import { - MenuPage, - MenuPageContextProvider, - RegistrationPage, - LoginPage, - useGlobalStyles, -} from "@creature-chess/ui"; +import { useLocalPlayer } from "@creature-chess/auth-web/context"; +import { useGlobalStyles } from "@creature-chess/ui"; -import { patchUser } from "./patchUser"; - -const UnauthenticatedRootPage: React.FunctionComponent = () => { - const { loginWithRedirect, isLoading } = useAuth0(); - - return ; -}; - -const AuthenticatedRootPage: React.FunctionComponent = () => { - const { logout, getAccessTokenSilently, getIdTokenClaims } = useAuth0(); - - const { user, isFetching, refresh } = useUser(); - - if (isFetching) { - return Loading; - } - - if (!user) { - return an error occured, please tell a dev; - } - - // todo move the contexts out of here - - if (!user.registered) { - const updateUser = async (nickname: string, image: number) => { - const token = await getAccessTokenSilently(); - const response = await patchUser(token, nickname, image); - - if (response.status === 400) { - const { error } = await response.json(); - - return { error }; - } - - if (response.status === 200) { - await getAccessTokenSilently({ ignoreCache: true }); - await getIdTokenClaims(); - - refresh(); - return { error: null }; - } - - return { error: "An unknown error occured" }; - }; - - return ; - } - - const onLogoutClick = () => logout(); - const onFindGameClick = () => { - window.location.href = process.env.GAME_SERVER_URL!; - }; - - const menuPageContext = { - findGame: onFindGameClick, - auth: { - logout: onLogoutClick, - }, - }; - - return ( - - - - ); -}; +import { MenuHomePage } from "./pages/home"; +import { MenuLoginPage } from "./pages/login"; ReactModal.setAppElement("#approot"); const App: React.FunctionComponent = () => { - const { isAuthenticated, isLoading } = useAuth0(); + const localPlayer = useLocalPlayer(); useGlobalStyles(); - if (isLoading) { - return Loading; - } - - if (isAuthenticated) { - return ; + if (!localPlayer) { + return ; } - return ; + return ; }; export { App }; diff --git a/apps/web-menu/src/index.tsx b/apps/web-menu/src/index.tsx index 6d1bbddf..c4eb7bda 100644 --- a/apps/web-menu/src/index.tsx +++ b/apps/web-menu/src/index.tsx @@ -1,20 +1,43 @@ import * as React from "react"; +import { LocalPlayerContextProvider } from "modules/@creature-chess/auth-web/context"; import * as ReactDOM from "react-dom"; -import { AuthProvider } from "@creature-chess/auth-web"; +import { AUTH0_ENABLED } from "@creature-chess/auth-web/auth0/config"; import { App } from "./app"; -const AppRoot: React.FunctionComponent = () => { - const onRedirectCallback = (appState: any) => { +const Auth0AppRoot = ({ children }: { children: React.ReactNode }) => { + const onRedirectCallback = React.useCallback((appState: any) => { window.location.href = appState?.returnTo || window.location.pathname; - }; + }, []); + + const Auth0AuthProvider = React.useMemo( + // eslint-disable-next-line @typescript-eslint/no-var-requires + () => require("@creature-chess/auth-web/auth0/provider").Auth0AuthProvider, + [] + ); + + return ( + + {children} + + ); +}; + +const AppRoot: React.FunctionComponent = () => { + if (AUTH0_ENABLED) { + return ( + + + + ); + } return ( - + - + ); }; diff --git a/apps/web-menu/src/pages/home.tsx b/apps/web-menu/src/pages/home.tsx new file mode 100644 index 00000000..7d25bcff --- /dev/null +++ b/apps/web-menu/src/pages/home.tsx @@ -0,0 +1,37 @@ +import React from "react"; + +import { useLocalPlayer } from "@creature-chess/auth-web/context"; +import { MenuPage, MenuPageContextProvider } from "@creature-chess/ui"; + +import { MenuRegistrationPage } from "./register"; + +export function MenuHomePage() { + const localPlayer = useLocalPlayer(); + + if (!localPlayer) { + return please wait...; + } + + if (localPlayer.type === "user" && localPlayer.registered) { + return ; + } + + const onFindGameClick = () => { + window.location.href = process.env.GAME_SERVER_URL!; + }; + + const menuPageContext = { + findGame: onFindGameClick, + auth: { + logout: () => { + /* .. */ + }, + }, + }; + + return ( + + + + ); +} diff --git a/apps/web-menu/src/pages/login.tsx b/apps/web-menu/src/pages/login.tsx new file mode 100644 index 00000000..ab46ae74 --- /dev/null +++ b/apps/web-menu/src/pages/login.tsx @@ -0,0 +1,24 @@ +import React from "react"; + +import { AUTH0_ENABLED } from "@creature-chess/auth-web/auth0/config"; +import { LoginPage } from "@creature-chess/ui"; + +// eslint-disable-next-line @typescript-eslint/no-var-requires +const useAuth0 = AUTH0_ENABLED ? require("@auth0/auth0-react").useAuth0 : null; + +export const MenuLoginPage = () => { + if (useAuth0) { + // eslint-disable-next-line react-hooks/rules-of-hooks + const { loginWithRedirect, isLoading } = useAuth0(); + + return ( + + ); + } + + return ; +}; diff --git a/apps/web-menu/src/pages/register.tsx b/apps/web-menu/src/pages/register.tsx new file mode 100644 index 00000000..e8a3cf31 --- /dev/null +++ b/apps/web-menu/src/pages/register.tsx @@ -0,0 +1,41 @@ +import React from "react"; + +import { AUTH0_ENABLED } from "@creature-chess/auth-web/auth0/config"; +import { RegistrationPage } from "@creature-chess/ui"; + +import { patchUser } from "../patchUser"; + +// eslint-disable-next-line @typescript-eslint/no-var-requires +const useAuth0 = AUTH0_ENABLED ? require("@auth0/auth0-react").useAuth0 : null; + +export function MenuRegistrationPage() { + if (!useAuth0) { + return an error occured, please clear cache and try again; + } + + // eslint-disable-next-line react-hooks/rules-of-hooks + const { getAccessTokenSilently, getIdTokenClaims, refresh } = useAuth0(); + + const updateUser = async (nickname: string, image: number) => { + const token = await getAccessTokenSilently(); + const response = await patchUser(token, nickname, image); + + if (response.status === 400) { + const { error } = await response.json(); + + return { error }; + } + + if (response.status === 200) { + await getAccessTokenSilently({ ignoreCache: true }); + await getIdTokenClaims(); + + refresh(); + return { error: null }; + } + + return { error: "An unknown error occured" }; + }; + + return ; +} diff --git a/docker-compose.yml b/docker-compose.yml index ddc81d99..73cf7788 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -17,6 +17,7 @@ services: - APP_DIR=web-game environment: - NODE_ENV + - AUTH0_ENABLED - AUTH0_DOMAIN - AUTH0_SPA_CLIENT_ID - API_INFO_URL diff --git a/modules/@creature-chess/auth-server/src/authenticate.ts b/modules/@creature-chess/auth-server/src/authenticate.ts index f67b8c12..aa3b4900 100644 --- a/modules/@creature-chess/auth-server/src/authenticate.ts +++ b/modules/@creature-chess/auth-server/src/authenticate.ts @@ -6,13 +6,20 @@ import { convertDatabaseUserToUserModel, UserModel, } from "./user"; -import { verifyDecodeJwt } from "./verifyDecodeJwt"; + +import { AUTH0_ENABLED } from "@creature-chess/auth-web/auth0/config"; +// eslint-disable-next-line @typescript-eslint/no-var-requires +const verifyDecodeJwt = AUTH0_ENABLED ? require("./verifyDecodeJwt").verifyDecodeJwt : null; export const authenticate = async ( managementClient: ManagementClient, database: DatabaseConnection, token: string ): Promise => { + if (!verifyDecodeJwt) { + throw new Error("todo"); + } + const decoded = await verifyDecodeJwt(token); if (decoded === null) { diff --git a/modules/@creature-chess/auth-web/config.ts b/modules/@creature-chess/auth-web/auth0/config.ts similarity index 80% rename from modules/@creature-chess/auth-web/config.ts rename to modules/@creature-chess/auth-web/auth0/config.ts index 8ab99238..94679e00 100644 --- a/modules/@creature-chess/auth-web/config.ts +++ b/modules/@creature-chess/auth-web/auth0/config.ts @@ -1,5 +1,7 @@ import { APP_BASE_URL } from "@creature-chess/models"; +export const AUTH0_ENABLED = process.env.AUTH0_ENABLED === "true"; + export const auth0Config = { domain: process.env.AUTH0_DOMAIN, clientID: process.env.AUTH0_SPA_CLIENT_ID, diff --git a/modules/@creature-chess/auth-web/auth0/internal/localUserProvider.tsx b/modules/@creature-chess/auth-web/auth0/internal/localUserProvider.tsx new file mode 100644 index 00000000..0714ecbe --- /dev/null +++ b/modules/@creature-chess/auth-web/auth0/internal/localUserProvider.tsx @@ -0,0 +1,34 @@ +import React from "react"; + +import { LocalPlayerContextProvider } from "../../context"; +import { useLocalUserDTO } from "./useLocalUserDto"; + +/** + * Provides the local user (from auth0) to the LocalPlayerContext + */ +export const Auth0LocalUserProvider = ({ + children, +}: { + children: React.ReactNode; +}) => { + const { user } = useLocalUserDTO(); + + if (!user) { + return ( + + {children} + + ); + } + + const player = { + ...user, + type: "user" as const, + }; + + return ( + + {children} + + ); +}; diff --git a/modules/@creature-chess/auth-web/useUser.ts b/modules/@creature-chess/auth-web/auth0/internal/useLocalUserDto.ts similarity index 94% rename from modules/@creature-chess/auth-web/useUser.ts rename to modules/@creature-chess/auth-web/auth0/internal/useLocalUserDto.ts index c69ffe38..37019518 100644 --- a/modules/@creature-chess/auth-web/useUser.ts +++ b/modules/@creature-chess/auth-web/auth0/internal/useLocalUserDto.ts @@ -4,7 +4,7 @@ import { useAuth0 } from "@auth0/auth0-react"; import { UserDTO } from "@creature-chess/models/dto/user"; -import { getCurrentUser } from "./getCurrentUser"; +import { getCurrentUser } from "../../http/getCurrentUser"; /** * Calls the current user endpoint and returns the user @@ -18,7 +18,7 @@ import { getCurrentUser } from "./getCurrentUser"; * - error: any error that occurred * - refresh: a function to refresh the user */ -export function useUser() { +export function useLocalUserDTO() { const { user: auth0User, getAccessTokenSilently } = useAuth0(); const [currentUser, setCurrentUser] = React.useState( diff --git a/modules/@creature-chess/auth-web/provider.tsx b/modules/@creature-chess/auth-web/auth0/provider.tsx similarity index 68% rename from modules/@creature-chess/auth-web/provider.tsx rename to modules/@creature-chess/auth-web/auth0/provider.tsx index 8811e398..f0f29389 100644 --- a/modules/@creature-chess/auth-web/provider.tsx +++ b/modules/@creature-chess/auth-web/auth0/provider.tsx @@ -3,14 +3,19 @@ import React from "react"; import { Auth0Provider } from "@auth0/auth0-react"; import { auth0Config } from "./config"; -import { GameAuthContextProvider } from "./context"; +import { Auth0LocalUserProvider } from "./internal/localUserProvider"; type Props = { children: React.ReactNode; onRedirectCallback?: (appState: any) => void; }; -export const AuthProvider: React.FC = ({ +/** + * Handles authentication with auth0 and loading of user data from the API + * + * This file should be conditionally imported based on whether or not auth0 is enabled + */ +export const Auth0AuthProvider: React.FC = ({ children, onRedirectCallback, }) => { @@ -39,7 +44,7 @@ export const AuthProvider: React.FC = ({ onRedirectCallback={onRedirectCallback} useRefreshTokens > - {children} + {children} ); }; diff --git a/modules/@creature-chess/auth-web/context.tsx b/modules/@creature-chess/auth-web/context.tsx index a0fac726..d230e5b5 100644 --- a/modules/@creature-chess/auth-web/context.tsx +++ b/modules/@creature-chess/auth-web/context.tsx @@ -1,38 +1,11 @@ -import * as React from "react"; +import { createContext, useContext } from "react"; -import { UserDTO } from "@creature-chess/models/dto/user"; +import { LocalPlayer } from "./player"; -import { useUser } from "./useUser"; +export const LocalPlayerContext = createContext(null); +LocalPlayerContext.displayName = "LocalPlayerContext"; -const GameAuthContext = React.createContext<{ - user: UserDTO | null; -}>({ - user: null, -}); -GameAuthContext.displayName = "GameAuthContext"; +export const LocalPlayerContextProvider = LocalPlayerContext.Provider; -export const AuthContextProvider = GameAuthContext.Provider; - -export function GameAuthContextProvider({ - children, -}: { - children: React.ReactNode | React.ReactNode[]; -}) { - const { user } = useUser(); - - return ( - - {children} - - ); -} - -export const usePlayerId = (): string => { - const { user } = React.useContext(GameAuthContext); - - if (!user) { - return ""; - } - - return user.id; -}; +export const useLocalPlayer = () => useContext(LocalPlayerContext); +export const useLocalPlayerId = () => useLocalPlayer()?.id || ""; diff --git a/modules/@creature-chess/auth-web/getCurrentUser.ts b/modules/@creature-chess/auth-web/http/getCurrentUser.ts similarity index 100% rename from modules/@creature-chess/auth-web/getCurrentUser.ts rename to modules/@creature-chess/auth-web/http/getCurrentUser.ts diff --git a/modules/@creature-chess/auth-web/index.ts b/modules/@creature-chess/auth-web/index.ts deleted file mode 100644 index d7b93c1f..00000000 --- a/modules/@creature-chess/auth-web/index.ts +++ /dev/null @@ -1,8 +0,0 @@ -export { AuthProvider } from "./provider"; - -export { - AuthContextProvider, - GameAuthContextProvider, - usePlayerId, -} from "./context"; -export { useUser } from "./useUser"; diff --git a/modules/@creature-chess/auth-web/player.ts b/modules/@creature-chess/auth-web/player.ts new file mode 100644 index 00000000..ecc55e27 --- /dev/null +++ b/modules/@creature-chess/auth-web/player.ts @@ -0,0 +1,14 @@ +import { UserDTO } from "@creature-chess/models/dto/user"; + +type Guest = { + type: "guest"; + + id: string; + nickname: string; +}; + +type User = { + type: "user"; +} & UserDTO; + +export type LocalPlayer = Guest | User; diff --git a/modules/@creature-chess/auth-web/tsconfig.json b/modules/@creature-chess/auth-web/tsconfig.json index 184dc5af..e3f982c1 100644 --- a/modules/@creature-chess/auth-web/tsconfig.json +++ b/modules/@creature-chess/auth-web/tsconfig.json @@ -1,4 +1,7 @@ { "extends": "../../../tsconfig.json", - "include": ["./src/**/*", "./src/*", "./index.ts", "./*.ts"] + "include": [ + "./**/*.ts", + "./*.ts" + ] } diff --git a/modules/@creature-chess/ui/pages/menu/login/LoginPage.tsx b/modules/@creature-chess/ui/pages/menu/login/LoginPage.tsx index 48924920..82fba02b 100644 --- a/modules/@creature-chess/ui/pages/menu/login/LoginPage.tsx +++ b/modules/@creature-chess/ui/pages/menu/login/LoginPage.tsx @@ -5,26 +5,30 @@ import { useStyles } from "./LoginPage.styles"; type LoginPageProps = { isLoading: boolean; - onSignInClick: () => void; + auth0Enabled: boolean; + onSignInClick?: () => void; }; -const LoginPage: React.FunctionComponent = ({ +const LoginPage = ({ + auth0Enabled, isLoading, onSignInClick, -}) => { +}: LoginPageProps) => { const styles = useStyles(); const [loadingSignIn, setLoadingSignIn] = React.useState(false); const currentlyLoading = isLoading || loadingSignIn; const handleSignInClick = () => { - if (currentlyLoading) { + if (!auth0Enabled || currentlyLoading) { return; } setLoadingSignIn(true); - onSignInClick(); + if (onSignInClick) { + onSignInClick(); + } }; if (currentlyLoading) { @@ -43,9 +47,22 @@ const LoginPage: React.FunctionComponent = ({ Creature Chess is a multiplayer game, so you need an account to play

- + {auth0Enabled && ( + <> + + + + )}

Join us on Discord to find other players and give feedback on the From d85a54d616d941232207bbbc235222fa8e43bc95 Mon Sep 17 00:00:00 2001 From: James Monger Date: Fri, 25 Aug 2023 00:38:38 +0000 Subject: [PATCH 05/16] fix: fix ejs params --- apps/web-menu/src/index.ejs | 2 -- 1 file changed, 2 deletions(-) diff --git a/apps/web-menu/src/index.ejs b/apps/web-menu/src/index.ejs index 9e735916..964376f3 100644 --- a/apps/web-menu/src/index.ejs +++ b/apps/web-menu/src/index.ejs @@ -9,8 +9,6 @@ /> Creature Chess - - <%= googleAnalyticsScript %> <%= ghPagesRedirectScript %> From ae8ef104a40c7cf3095b43ba376b9a136361c5c8 Mon Sep 17 00:00:00 2001 From: James Monger Date: Fri, 25 Aug 2023 00:38:52 +0000 Subject: [PATCH 06/16] build: split docker layers for shoki builds --- apps/nodejs-builder.Dockerfile | 4 +++- apps/web-builder.Dockerfile | 2 ++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/apps/nodejs-builder.Dockerfile b/apps/nodejs-builder.Dockerfile index b879ba69..0d7601f1 100644 --- a/apps/nodejs-builder.Dockerfile +++ b/apps/nodejs-builder.Dockerfile @@ -57,4 +57,6 @@ ADD tsconfig.json ./ ADD modules/ ./modules/ RUN yarn workspace @creature-chess/data prisma-generate -RUN yarn workspaces foreach --exclude "@creature-chess-app/*" run build + +RUN yarn workspaces foreach --include "@shoki/*" --exclude "@shoki/board-react" run build +RUN yarn workspaces foreach --include "@creature-chess/*" run build diff --git a/apps/web-builder.Dockerfile b/apps/web-builder.Dockerfile index 791b8c19..03954372 100644 --- a/apps/web-builder.Dockerfile +++ b/apps/web-builder.Dockerfile @@ -5,6 +5,8 @@ ENV APP_DIR ${APP_DIR} WORKDIR /code +RUN yarn workspace @shoki/board-react run build + ADD apps/$APP_DIR/ ./apps/$APP_DIR/ VOLUME /code/apps/$APP_DIR/dist From 63b9b36c0d9a6da1ed77001c97eb9329ed97e343 Mon Sep 17 00:00:00 2001 From: James Monger Date: Fri, 25 Aug 2023 08:14:34 +0000 Subject: [PATCH 07/16] feat: add "Play as guest" button --- apps/web-menu/src/pages/login.tsx | 7 ++++- .../ui/pages/menu/login/LoginPage.styles.ts | 9 +++++++ .../ui/pages/menu/login/LoginPage.tsx | 27 ++++++++++++++----- 3 files changed, 35 insertions(+), 8 deletions(-) diff --git a/apps/web-menu/src/pages/login.tsx b/apps/web-menu/src/pages/login.tsx index ab46ae74..d749566c 100644 --- a/apps/web-menu/src/pages/login.tsx +++ b/apps/web-menu/src/pages/login.tsx @@ -7,12 +7,17 @@ import { LoginPage } from "@creature-chess/ui"; const useAuth0 = AUTH0_ENABLED ? require("@auth0/auth0-react").useAuth0 : null; export const MenuLoginPage = () => { + const onPlayAsGuestClick = () => { + window.location.href = process.env.GAME_SERVER_URL!; + }; + if (useAuth0) { // eslint-disable-next-line react-hooks/rules-of-hooks const { loginWithRedirect, isLoading } = useAuth0(); return ( { ); } - return ; + return ; }; diff --git a/modules/@creature-chess/ui/pages/menu/login/LoginPage.styles.ts b/modules/@creature-chess/ui/pages/menu/login/LoginPage.styles.ts index 71939d92..beff8857 100644 --- a/modules/@creature-chess/ui/pages/menu/login/LoginPage.styles.ts +++ b/modules/@creature-chess/ui/pages/menu/login/LoginPage.styles.ts @@ -47,6 +47,15 @@ export const useStyles = createUseStyles({ background: "#b13e53", border: "none", }, + guestButton: { + padding: "0.6em 1.2em", + marginBottom: "1rem", + fontSize: "1.4em", + color: "#393939", + cursor: "pointer", + background: "#e1cab9", + border: "none", + }, discordButton: { maxWidth: "12em", marginBottom: "1rem", diff --git a/modules/@creature-chess/ui/pages/menu/login/LoginPage.tsx b/modules/@creature-chess/ui/pages/menu/login/LoginPage.tsx index 82fba02b..85041536 100644 --- a/modules/@creature-chess/ui/pages/menu/login/LoginPage.tsx +++ b/modules/@creature-chess/ui/pages/menu/login/LoginPage.tsx @@ -4,15 +4,17 @@ import { Footer } from "../../../src/Footer"; import { useStyles } from "./LoginPage.styles"; type LoginPageProps = { - isLoading: boolean; - auth0Enabled: boolean; + isLoading?: boolean; + auth0Enabled?: boolean; onSignInClick?: () => void; + onPlayAsGuestClick?: () => void; }; const LoginPage = ({ - auth0Enabled, - isLoading, + auth0Enabled = false, + isLoading = false, onSignInClick, + onPlayAsGuestClick, }: LoginPageProps) => { const styles = useStyles(); const [loadingSignIn, setLoadingSignIn] = React.useState(false); @@ -31,6 +33,12 @@ const LoginPage = ({ } }; + const handlePlayAsGuestClick = () => { + if (onPlayAsGuestClick) { + onPlayAsGuestClick(); + } + }; + if (currentlyLoading) { return Loading; } @@ -43,9 +51,7 @@ const LoginPage = ({

-

- Creature Chess is a multiplayer game, so you need an account to play -

+

Creature Chess is a multiplayer auto-chess battler

{auth0Enabled && ( <> @@ -64,6 +70,13 @@ const LoginPage = ({ )} + +

Join us on Discord to find other players and give feedback on the game From 49a7480f977df538db9cf6884fbd02fab0aa2879 Mon Sep 17 00:00:00 2001 From: James Monger Date: Fri, 25 Aug 2023 08:57:57 +0000 Subject: [PATCH 08/16] feat: create initial guest auth --- apps/server-game/src/handshake/index.ts | 25 ++++++- apps/server-game/src/handshake/listener.ts | 4 +- apps/server-game/src/handshake/types.ts | 2 - apps/web-game/src/app.tsx | 66 +++++++++++++++---- apps/web-game/src/networking/saga.ts | 26 ++++---- apps/web-game/src/networking/socket.ts | 5 +- apps/web-menu/src/pages/login.tsx | 2 +- .../@creature-chess/networking/handshake.ts | 3 + 8 files changed, 99 insertions(+), 34 deletions(-) create mode 100644 modules/@creature-chess/networking/handshake.ts diff --git a/apps/server-game/src/handshake/index.ts b/apps/server-game/src/handshake/index.ts index fe090fdb..3dd05b56 100644 --- a/apps/server-game/src/handshake/index.ts +++ b/apps/server-game/src/handshake/index.ts @@ -19,13 +19,34 @@ export const onHandshakeSuccess = ( logger.info("Listening for successful handshakes - inner A"); - handshakeListener(deps, async (socket, { idToken }) => { + handshakeListener(deps, async (socket, request) => { try { logger.info("Authenticating new handshake", { meta: { socketId: socket.id }, }); - const user = await authenticate(authClient, database, idToken); + if (request.type === "guest") { + logger.info(`[socket ${socket.id}] Handshake successful for guest`); + + successHandshake(socket); + + const guestSocket = socket as AuthenticatedSocket; + + guestSocket.data = { + id: -1, + nickname: "Guest", + profile: { + picture: 17, + title: null + }, + }; + + onReceive(guestSocket); + + return; + } + + const user = await authenticate(authClient, database, request.data.accessToken); if (!user.registered) { failHandshake(socket, { error: { type: "not_registered" } }); diff --git a/apps/server-game/src/handshake/listener.ts b/apps/server-game/src/handshake/listener.ts index 1b89e258..981b3ee8 100644 --- a/apps/server-game/src/handshake/listener.ts +++ b/apps/server-game/src/handshake/listener.ts @@ -1,7 +1,9 @@ import { Socket } from "socket.io"; +import { HandshakeRequest } from "@creature-chess/networking/handshake"; + import { logger } from "../log"; -import { HandshakeListenerDependencies, HandshakeRequest } from "./types"; +import { HandshakeListenerDependencies } from "./types"; /** * Attaches to the socket server and raises incoming handshake requests. diff --git a/apps/server-game/src/handshake/types.ts b/apps/server-game/src/handshake/types.ts index 73dc46e7..ae2e8b5c 100644 --- a/apps/server-game/src/handshake/types.ts +++ b/apps/server-game/src/handshake/types.ts @@ -8,5 +8,3 @@ export type HandshakeListenerDependencies = { authClient: ManagementClient; database: DatabaseConnection; }; - -export type HandshakeRequest = { idToken: string }; diff --git a/apps/web-game/src/app.tsx b/apps/web-game/src/app.tsx index 3f70cce0..e51f28b7 100644 --- a/apps/web-game/src/app.tsx +++ b/apps/web-game/src/app.tsx @@ -1,10 +1,10 @@ import * as React from "react"; -import { useAuth0 } from "@auth0/auth0-react"; import ReactModal from "react-modal"; import { useDispatch, useSelector } from "react-redux"; import { withErrorBoundary, useErrorBoundary } from "react-use-error-boundary"; +import { AUTH0_ENABLED } from "@creature-chess/auth-web/auth0/config"; import { LobbyPageContextProvider, LobbyPage, @@ -16,26 +16,26 @@ import { GamePage } from "./game"; import { openConnection } from "./networking"; import { AppState } from "./store"; +// eslint-disable-next-line @typescript-eslint/no-var-requires +const useAuth0 = AUTH0_ENABLED ? require("@auth0/auth0-react").useAuth0 : null; + ReactModal.setAppElement("#approot"); -export const App = withErrorBoundary(() => { - const [error, resetError] = useErrorBoundary(); +function useIsGuestRequest() { + const query = new URLSearchParams(window.location.search); - const dispatch = useDispatch(); - const { isAuthenticated, getAccessTokenSilently } = useAuth0(); - const lobbyInfo = useSelector((state: AppState) => state.lobby); - const isInGame = useSelector((state: AppState) => state.game.ui.inGame); + return query.get("guest") === "true"; +} - const [loadingMessage, setLoadingMessage] = React.useState("loading..."); +function useOpenAuth0Connection() { + const { isAuthenticated, getAccessTokenSilently } = useAuth0(); + const dispatch = useDispatch(); React.useEffect(() => { const open = async () => { - setLoadingMessage("getting access token"); try { - const idToken = await getAccessTokenSilently(); - - setLoadingMessage("opening connection"); - dispatch(openConnection({ idToken })); + const accessToken = await getAccessTokenSilently(); + dispatch(openConnection({ type: "auth0", data: { accessToken } })); } catch (e) { console.log({ error: e }); } @@ -43,6 +43,45 @@ export const App = withErrorBoundary(() => { open(); }, [isAuthenticated, getAccessTokenSilently, dispatch]); +} + +function useOpenGuestConnection() { + const dispatch = useDispatch(); + + React.useEffect(() => { + const open = async () => { + try { + dispatch(openConnection({ type: "guest" })); + } catch (e) { + console.log({ error: e }); + } + }; + + open(); + }, [dispatch]); +} + +function useOpenConnection() { + if (useIsGuestRequest()) { + // eslint-disable-next-line react-hooks/rules-of-hooks + return useOpenGuestConnection(); + } + + if (useAuth0) { + // eslint-disable-next-line react-hooks/rules-of-hooks + return useOpenAuth0Connection(); + } + + throw new Error("No connection method available"); +} + +export const App = withErrorBoundary(() => { + const [error, resetError] = useErrorBoundary(); + + const lobbyInfo = useSelector((state: AppState) => state.lobby); + const isInGame = useSelector((state: AppState) => state.game.ui.inGame); + + useOpenConnection(); useGlobalStyles(); @@ -72,7 +111,6 @@ export const App = withErrorBoundary(() => { return ( <> - ({loadingMessage}) ); diff --git a/apps/web-game/src/networking/saga.ts b/apps/web-game/src/networking/saga.ts index 46d0e27a..1eb2e440 100644 --- a/apps/web-game/src/networking/saga.ts +++ b/apps/web-game/src/networking/saga.ts @@ -7,6 +7,7 @@ import { LobbyServerToClient, GameServerToClient, } from "@creature-chess/networking"; +import { HandshakeRequest } from "@creature-chess/networking/handshake"; import { lobbyNetworking } from "../../lobby"; import { gameNetworking } from "../game"; @@ -15,15 +16,15 @@ import { getSocket } from "./socket"; type ConnectionResult = | { - type: "lobby"; - payload: LobbyServerToClient.LobbyConnectionPacket; - } + type: "lobby"; + payload: LobbyServerToClient.LobbyConnectionPacket; + } | { - type: "game"; - payload: GameServerToClient.GameConnectionPacket; - }; + type: "game"; + payload: GameServerToClient.GameConnectionPacket; + }; -const listenForConnection = function* (socket: Socket) { +const listenForConnection = function*(socket: Socket) { const channel = eventChannel((emit) => { const onLobbyConnected = ( payload: LobbyServerToClient.LobbyConnectionPacket @@ -52,20 +53,21 @@ const listenForConnection = function* (socket: Socket) { return connection; }; + type OpenConnectionAction = ReturnType; -export const openConnection = createAction<{ idToken: string }>( +export const openConnection = createAction( "openConnection" ); -export const networkingSaga = function* () { +export const networkingSaga = function*() { const { - payload: { idToken }, + payload: request, } = yield* take(openConnection.toString() as any); let socket: Socket; try { - socket = yield* call(getSocket, idToken); + socket = yield* call(getSocket, request); } catch (error) { console.error("error getting socket", error); return; @@ -76,7 +78,7 @@ export const networkingSaga = function* () { yield* all([ call(lobbyNetworking, socket), call(gameNetworking, socket), - call(function* () { + call(function*() { if (connection.type === "lobby") { yield put(lobbyConnectedEvent(connection.payload)); } else if (connection.type === "game") { diff --git a/apps/web-game/src/networking/socket.ts b/apps/web-game/src/networking/socket.ts index d456e238..babb40e2 100644 --- a/apps/web-game/src/networking/socket.ts +++ b/apps/web-game/src/networking/socket.ts @@ -1,8 +1,9 @@ import { io, Socket } from "socket.io-client"; import { GameServerToClient } from "@creature-chess/networking"; +import { HandshakeRequest } from "@creature-chess/networking/handshake"; -export const getSocket = (idToken: string) => { +export const getSocket = (request: HandshakeRequest) => { const socket = (io as any)( { path: "/game/socket.io", @@ -15,7 +16,7 @@ export const getSocket = (idToken: string) => { return new Promise((resolve, reject) => { socket.on("connect", () => { - socket.emit("authenticate", { idToken }); + socket.emit("authenticate", request); }); const onAuthenticated = ({ diff --git a/apps/web-menu/src/pages/login.tsx b/apps/web-menu/src/pages/login.tsx index d749566c..449dd58e 100644 --- a/apps/web-menu/src/pages/login.tsx +++ b/apps/web-menu/src/pages/login.tsx @@ -8,7 +8,7 @@ const useAuth0 = AUTH0_ENABLED ? require("@auth0/auth0-react").useAuth0 : null; export const MenuLoginPage = () => { const onPlayAsGuestClick = () => { - window.location.href = process.env.GAME_SERVER_URL!; + window.location.href = process.env.GAME_SERVER_URL! + "?guest=true"; }; if (useAuth0) { diff --git a/modules/@creature-chess/networking/handshake.ts b/modules/@creature-chess/networking/handshake.ts new file mode 100644 index 00000000..a8ed31a9 --- /dev/null +++ b/modules/@creature-chess/networking/handshake.ts @@ -0,0 +1,3 @@ +export type HandshakeRequest = + { type: "auth0"; data: { accessToken: string } } + | { type: "guest"; data?: never }; From 2828d6cc616a1465e24de1f47da8f974d08c86c8 Mon Sep 17 00:00:00 2001 From: James Monger Date: Sat, 26 Aug 2023 08:04:07 +0000 Subject: [PATCH 09/16] feat: main guest auth --- apps/server-game/src/game.ts | 4 +- apps/server-game/src/handshake/index.ts | 24 +++- apps/server-game/src/lobby.ts | 9 +- apps/server-game/src/player/socket.ts | 15 ++- apps/server-info/index.ts | 123 ++++++++++++++++++ apps/web-game/src/app.tsx | 63 +++++++-- apps/web-game/src/guest.ts | 10 ++ apps/web-game/src/index.tsx | 22 ++-- .../@creature-chess/auth-server/src/user.ts | 2 +- .../auth-web/guest/provider.tsx | 77 +++++++++++ .../20230825100127_add_guests/migration.sql | 14 ++ .../20230825183847_string_ids/migration.sql | 20 +++ .../@creature-chess/data/prisma/schema.prisma | 15 ++- .../data/src/bot/addGamePlayed.ts | 2 +- .../@creature-chess/data/src/bot/addWin.ts | 2 +- modules/@creature-chess/data/src/bot/index.ts | 4 +- .../data/src/databaseConnection.ts | 2 + .../data/src/user/addGamePlayed.ts | 2 +- .../@creature-chess/data/src/user/addWin.ts | 2 +- .../@creature-chess/data/src/user/getById.ts | 2 +- .../@creature-chess/data/src/user/index.ts | 8 +- .../data/src/user/setProfileInfo.ts | 2 +- .../models/src/lobby-player.ts | 2 +- .../@creature-chess/networking/handshake.ts | 2 +- 24 files changed, 383 insertions(+), 45 deletions(-) create mode 100644 apps/web-game/src/guest.ts create mode 100644 modules/@creature-chess/auth-web/guest/provider.tsx create mode 100644 modules/@creature-chess/data/prisma/migrations/20230825100127_add_guests/migration.sql create mode 100644 modules/@creature-chess/data/prisma/migrations/20230825183847_string_ids/migration.sql diff --git a/apps/server-game/src/game.ts b/apps/server-game/src/game.ts index d60208a2..07eb6e28 100644 --- a/apps/server-game/src/game.ts +++ b/apps/server-game/src/game.ts @@ -62,8 +62,8 @@ export class Game { this.gamemode.start(entities); } - public canJoinGame(playerId: number) { - const existing = this.gamemode.getPlayerById(playerId.toString()); + public canJoinGame(playerId: string) { + const existing = this.gamemode.getPlayerById(playerId); if (!existing) { return false; diff --git a/apps/server-game/src/handshake/index.ts b/apps/server-game/src/handshake/index.ts index 3dd05b56..75dbb07c 100644 --- a/apps/server-game/src/handshake/index.ts +++ b/apps/server-game/src/handshake/index.ts @@ -26,6 +26,20 @@ export const onHandshakeSuccess = ( }); if (request.type === "guest") { + const guest = await database.prisma.guests.findFirst({ + where: { + token: request.data.accessToken, + expires_at: { + gte: new Date(), + }, + } + }); + + if (!guest) { + failHandshake(socket, { error: { type: "authentication" } }); + return; + } + logger.info(`[socket ${socket.id}] Handshake successful for guest`); successHandshake(socket); @@ -33,12 +47,13 @@ export const onHandshakeSuccess = ( const guestSocket = socket as AuthenticatedSocket; guestSocket.data = { - id: -1, - nickname: "Guest", + type: "guest", + id: guest.id, + nickname: `Guest ${guest.id}`, profile: { - picture: 17, + picture: 1, title: null - }, + } }; onReceive(guestSocket); @@ -63,6 +78,7 @@ export const onHandshakeSuccess = ( const authenticatedSocket = socket as AuthenticatedSocket; authenticatedSocket.data = { + type: "player", id: user.id, nickname: user.nickname, profile: user.profile, diff --git a/apps/server-game/src/lobby.ts b/apps/server-game/src/lobby.ts index fbbc12c5..be1f604b 100644 --- a/apps/server-game/src/lobby.ts +++ b/apps/server-game/src/lobby.ts @@ -40,7 +40,7 @@ export class Lobby { return this.options.maxPlayers - this.members.length; } - public isInLobby(playerId: number) { + public isInLobby(playerId: string) { return this.members.some((m) => m.player.id === playerId); } @@ -51,11 +51,16 @@ export class Lobby { if (existing) { existing.socket?.disconnect(true); } else { + const defaultProfile: PlayerProfile = { + picture: 1, + title: null + }; + const newMember = { player: { id: socket.data.id, name: socket.data.nickname!, - profile: socket.data.profile!, + profile: socket.data.profile ?? defaultProfile, }, socket, registry, diff --git a/apps/server-game/src/player/socket.ts b/apps/server-game/src/player/socket.ts index 80423671..da9db012 100644 --- a/apps/server-game/src/player/socket.ts +++ b/apps/server-game/src/player/socket.ts @@ -3,11 +3,22 @@ import { DefaultEventsMap } from "socket.io/dist/typed-events"; import { PlayerProfile } from "@creature-chess/models"; -type AuthenticatedSocketData = { - id: number; +type GuestSocketData = { + type: "guest"; + id: string; + nickname: string; + profile: PlayerProfile; +}; + +type PlayerSocketData = { + type: "player"; + id: string; nickname: string | null; profile: PlayerProfile | null; }; + +type AuthenticatedSocketData = GuestSocketData | PlayerSocketData; + export type AuthenticatedSocket = Socket< DefaultEventsMap, DefaultEventsMap, diff --git a/apps/server-info/index.ts b/apps/server-info/index.ts index fcf9dcd9..445bd472 100644 --- a/apps/server-info/index.ts +++ b/apps/server-info/index.ts @@ -27,6 +27,20 @@ const PORT = 3000; app.use(express.json()); app.use(expressWinston({ winstonInstance: logger })); +app.use((req, res, next) => { + const { headers: { cookie } } = req; + if (cookie) { + const values = cookie.split(";").reduce((acc, item) => { + const data = item.trim().split("="); + return { ...acc, [data[0]]: data[1] }; + }, {}); + res.locals.cookie = values; + } else { + res.locals.cookie = {}; + } + next(); +}); + async function getNicknameUpdate( database: DatabaseConnection, filter: Filter, @@ -81,12 +95,121 @@ async function getPictureUpdate(body: { return { error: null, picture: pictureId }; } +function guestCleanUpProcess(database: DatabaseConnection) { + setInterval(async () => { + const now = new Date(); + await database.prisma.guests.deleteMany({ + where: { + expires_at: { + lte: now, + }, + }, + }); + }, 60000); // Check every minute +} + async function startServer() { const authClient = getManagementClient(); const filter = new Filter(); const database = await createDatabaseConnection(logger); + guestCleanUpProcess(database); + + async function getNewToken() { + let token: string | null = null; + + do { + // TODO make this secure + const newToken = Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15); + + const existing = await database.prisma.guests.findFirst({ + where: { + token: newToken, + expires_at: { + gte: new Date() + } + } + }); + + if (!existing) { + token = newToken; + } + } while (token === null); + + return token; + } + + async function getNewGuestId() { + let id: string | null = null; + + do { + // random between 0001 and 9999 as string with leading zeros + const newId = (Math.floor(Math.random() * 10000) + 1).toString().padStart(4, "0"); + + const existing = await database.prisma.guests.findFirst({ + where: { + id: newId + } + }); + + if (!existing) { + id = newId; + } + } while (id === null); + + return id; + } + + app.get("/guest/session", async (req, res) => { + const token = res.locals.cookie["guest-token"]; + + let account: ({ id: string }) | null = null; + + if (token) { + // check its valid + account = await database.prisma.guests.findFirst({ + where: { + token, + expires_at: { + gte: new Date() + } + } + }); + + account ??= null; + } + + if (!account) { + const id = await getNewGuestId(); + const newToken = await getNewToken(); + + // expire in 1 hour + const expiryDate = new Date(Date.now() + 60 * 60 * 1000); + + // create a new guest user + account = await database.prisma.guests.create({ + data: { + id, + token: newToken, + expires_at: expiryDate + } + }); + + // set cookie "guest-token" to the token + res.cookie("guest-token", newToken, { + expires: expiryDate, + + // TODO improve security here, this is a temporary solution as it's only for guests + httpOnly: false + }); + } + + res.status(200).json({ + id: account.id + }); + }); + app.get("/user/current", async (req, res) => { const { authorization } = req.headers; diff --git a/apps/web-game/src/app.tsx b/apps/web-game/src/app.tsx index e51f28b7..9f7ad1d2 100644 --- a/apps/web-game/src/app.tsx +++ b/apps/web-game/src/app.tsx @@ -5,6 +5,7 @@ import { useDispatch, useSelector } from "react-redux"; import { withErrorBoundary, useErrorBoundary } from "react-use-error-boundary"; import { AUTH0_ENABLED } from "@creature-chess/auth-web/auth0/config"; +import { useLocalPlayer } from "@creature-chess/auth-web/context"; import { LobbyPageContextProvider, LobbyPage, @@ -21,12 +22,6 @@ const useAuth0 = AUTH0_ENABLED ? require("@auth0/auth0-react").useAuth0 : null; ReactModal.setAppElement("#approot"); -function useIsGuestRequest() { - const query = new URLSearchParams(window.location.search); - - return query.get("guest") === "true"; -} - function useOpenAuth0Connection() { const { isAuthenticated, getAccessTokenSilently } = useAuth0(); const dispatch = useDispatch(); @@ -45,29 +40,77 @@ function useOpenAuth0Connection() { }, [isAuthenticated, getAccessTokenSilently, dispatch]); } +/** + * Hook to read a cookie value + * + * TODO move this + */ +function useCookie(cookieName: string) { + const [cookie, setCookie] = React.useState(() => getCookieValue(cookieName)); + + React.useEffect(() => { + function handleCookieChange() { + setCookie(getCookieValue(cookieName)); + } + + window.addEventListener("cookieChange", handleCookieChange); + return () => { + window.removeEventListener("cookieChange", handleCookieChange); + }; + }, [cookieName]); + + return cookie; +} + +function getCookieValue(name: string) { + const value = "; " + document.cookie; + const parts = value.split("; " + name + "="); + if (parts.length === 2) { + return parts.pop()!.split(";").shift(); + } + + return null; +} + function useOpenGuestConnection() { const dispatch = useDispatch(); + // read cookie "guest-token" + const cookie = useCookie("guest-token"); + React.useEffect(() => { + if (!cookie) { + console.error("tries to open guest connection without cookie"); + return; + } + const open = async () => { try { - dispatch(openConnection({ type: "guest" })); + dispatch( + openConnection({ type: "guest", data: { accessToken: cookie } }) + ); } catch (e) { console.log({ error: e }); } }; open(); - }, [dispatch]); + }, [cookie, dispatch]); } function useOpenConnection() { - if (useIsGuestRequest()) { + const localPlayer = useLocalPlayer(); + + if (!localPlayer) { + return Loading; + } + + if (localPlayer.type === "guest") { // eslint-disable-next-line react-hooks/rules-of-hooks return useOpenGuestConnection(); } - if (useAuth0) { + if (localPlayer.type === "user" && useAuth0) { // eslint-disable-next-line react-hooks/rules-of-hooks return useOpenAuth0Connection(); } diff --git a/apps/web-game/src/guest.ts b/apps/web-game/src/guest.ts new file mode 100644 index 00000000..1026b402 --- /dev/null +++ b/apps/web-game/src/guest.ts @@ -0,0 +1,10 @@ +import { useMemo } from "react"; + +export function useGuestMode() { + const queryString = window.location.search; + + return useMemo(() => { + const params = new URLSearchParams(queryString); + return params.get("guest") === "true"; + }, [queryString]); +} diff --git a/apps/web-game/src/index.tsx b/apps/web-game/src/index.tsx index 988206e7..70a91c6a 100644 --- a/apps/web-game/src/index.tsx +++ b/apps/web-game/src/index.tsx @@ -1,13 +1,14 @@ import * as React from "react"; -import { LocalPlayerContextProvider } from "modules/@creature-chess/auth-web/context"; import "pepjs"; import * as ReactDOM from "react-dom"; import { Provider as ReduxProvider } from "react-redux"; import { AUTH0_ENABLED } from "@creature-chess/auth-web/auth0/config"; +import { GuestAuthProvider } from "@creature-chess/auth-web/guest/provider"; import { App } from "./app"; +import { useGuestMode } from "./guest"; import { createAppStore } from "./store"; const Auth0AppRoot = ({ children }: { children: React.ReactNode }) => { @@ -30,6 +31,17 @@ const Auth0AppRoot = ({ children }: { children: React.ReactNode }) => { const AppRoot = () => { const store = createAppStore(); + const guestMode = useGuestMode(); + + if (guestMode) { + return ( + + + + + + ); + } if (AUTH0_ENABLED) { return ( @@ -41,13 +53,7 @@ const AppRoot = () => { ); } - return ( - - - - - - ); + return error: no suitable auth provider; }; ReactDOM.render(, document.getElementById("approot")); diff --git a/modules/@creature-chess/auth-server/src/user.ts b/modules/@creature-chess/auth-server/src/user.ts index e3045931..0b83b67a 100644 --- a/modules/@creature-chess/auth-server/src/user.ts +++ b/modules/@creature-chess/auth-server/src/user.ts @@ -2,7 +2,7 @@ import { DatabaseUser } from "@creature-chess/data"; import { PlayerProfile } from "@creature-chess/models"; export interface UserModel { - id: number; + id: string; authId: string; stats: { gamesPlayed: number; wins: number }; nickname: string | null; diff --git a/modules/@creature-chess/auth-web/guest/provider.tsx b/modules/@creature-chess/auth-web/guest/provider.tsx new file mode 100644 index 00000000..c3ef5afe --- /dev/null +++ b/modules/@creature-chess/auth-web/guest/provider.tsx @@ -0,0 +1,77 @@ +import React, { useEffect, useMemo, useState } from "react"; + +import { LocalPlayerContextProvider } from "../context"; +import { LocalPlayer } from "../player"; + +/** + * Load a guest session from the server + */ +function useGuestSession() { + const [guestId, setGuestId] = useState(null); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + + useEffect(() => { + async function get() { + const response = await fetch( + process.env.API_INFO_URL + "/guest/session", + { + headers: { + "Content-Type": "application/json", + }, + } + ); + + if (!response.ok) { + setLoading(false); + setError("Failed to create guest session"); + return; + } + + const { id } = await response.json(); + setGuestId(id); + setLoading(false); + } + + get(); + }, []); + + const session = useMemo(() => ({ id: guestId }), [guestId]); + + return { + error, + loading, + session, + }; +} + +export function GuestAuthProvider({ children }: { children: React.ReactNode }) { + const { session, loading, error } = useGuestSession(); + + const player: LocalPlayer = useMemo( + () => ({ + type: "guest", + id: session?.id || "", + nickname: "Guest", + }), + [session] + ); + + if (error) { + return

{error}
; + } + + if (loading) { + return
Loading...
; + } + + if (!session) { + return
Failed to create guest session
; + } + + return ( + + {children} + + ); +} diff --git a/modules/@creature-chess/data/prisma/migrations/20230825100127_add_guests/migration.sql b/modules/@creature-chess/data/prisma/migrations/20230825100127_add_guests/migration.sql new file mode 100644 index 00000000..593e9a7c --- /dev/null +++ b/modules/@creature-chess/data/prisma/migrations/20230825100127_add_guests/migration.sql @@ -0,0 +1,14 @@ +-- CreateTable +CREATE TABLE "guests" ( + "id" TEXT NOT NULL, + "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updated_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "token" TEXT NOT NULL, + "expires_at" TIMESTAMP(3) NOT NULL, + "profile_picture" INTEGER, + + CONSTRAINT "guests_pkey" PRIMARY KEY ("id") +); + +-- CreateIndex +CREATE UNIQUE INDEX "guests_token_key" ON "guests"("token"); diff --git a/modules/@creature-chess/data/prisma/migrations/20230825183847_string_ids/migration.sql b/modules/@creature-chess/data/prisma/migrations/20230825183847_string_ids/migration.sql new file mode 100644 index 00000000..b76373d6 --- /dev/null +++ b/modules/@creature-chess/data/prisma/migrations/20230825183847_string_ids/migration.sql @@ -0,0 +1,20 @@ +/* + Warnings: + + - The primary key for the `bots` table will be changed. If it partially fails, the table could be left without primary key constraint. + - The primary key for the `users` table will be changed. If it partially fails, the table could be left without primary key constraint. + +*/ +-- AlterTable +ALTER TABLE "bots" DROP CONSTRAINT "bots_pkey", +ALTER COLUMN "id" DROP DEFAULT, +ALTER COLUMN "id" SET DATA TYPE TEXT, +ADD CONSTRAINT "bots_pkey" PRIMARY KEY ("id"); +DROP SEQUENCE "bots_id_seq"; + +-- AlterTable +ALTER TABLE "users" DROP CONSTRAINT "users_pkey", +ALTER COLUMN "id" DROP DEFAULT, +ALTER COLUMN "id" SET DATA TYPE TEXT, +ADD CONSTRAINT "users_pkey" PRIMARY KEY ("id"); +DROP SEQUENCE "users_id_seq"; diff --git a/modules/@creature-chess/data/prisma/schema.prisma b/modules/@creature-chess/data/prisma/schema.prisma index 02fac8e1..a3f76b7f 100644 --- a/modules/@creature-chess/data/prisma/schema.prisma +++ b/modules/@creature-chess/data/prisma/schema.prisma @@ -8,7 +8,7 @@ datasource db { } model bots { - id Int @id @default(autoincrement()) + id String @id @default(uuid()) nickname String @db.VarChar(20) games_played Int @default(0) wins Int @default(0) @@ -18,7 +18,7 @@ model bots { } model users { - id Int @id @default(autoincrement()) + id String @id @default(uuid()) auth_id String @db.VarChar(64) nickname String? @db.VarChar(20) profile_title Int? @@ -26,3 +26,14 @@ model users { games_played Int @default(0) wins Int @default(0) } + +model guests { + id String @id @default(uuid()) + created_at DateTime @default(now()) + updated_at DateTime @default(now()) @updatedAt + + token String @unique + expires_at DateTime + + profile_picture Int? +} diff --git a/modules/@creature-chess/data/src/bot/addGamePlayed.ts b/modules/@creature-chess/data/src/bot/addGamePlayed.ts index 8028d331..1866b557 100644 --- a/modules/@creature-chess/data/src/bot/addGamePlayed.ts +++ b/modules/@creature-chess/data/src/bot/addGamePlayed.ts @@ -2,7 +2,7 @@ import { PrismaClient } from "@prisma/client"; import { Logger } from "winston"; export const addGamePlayed = - (logger: Logger, client: PrismaClient) => async (id: number) => { + (logger: Logger, client: PrismaClient) => async (id: string) => { try { return await client.bots.update({ where: { diff --git a/modules/@creature-chess/data/src/bot/addWin.ts b/modules/@creature-chess/data/src/bot/addWin.ts index 033d12c2..60464deb 100644 --- a/modules/@creature-chess/data/src/bot/addWin.ts +++ b/modules/@creature-chess/data/src/bot/addWin.ts @@ -2,7 +2,7 @@ import { PrismaClient } from "@prisma/client"; import { Logger } from "winston"; export const addWin = - (logger: Logger, client: PrismaClient) => async (id: number) => { + (logger: Logger, client: PrismaClient) => async (id: string) => { try { return await client.bots.update({ where: { diff --git a/modules/@creature-chess/data/src/bot/index.ts b/modules/@creature-chess/data/src/bot/index.ts index 505b6167..d77cd774 100644 --- a/modules/@creature-chess/data/src/bot/index.ts +++ b/modules/@creature-chess/data/src/bot/index.ts @@ -9,8 +9,8 @@ import { getLeastPlayedBots } from "./getLeastPlayedBots"; export type BotDatabaseFunctions = { getLeastPlayedBots: (count: number) => Promise; - addWin: (id: number) => Promise; - addGamePlayed: (id: number) => Promise; + addWin: (id: string) => Promise; + addGamePlayed: (id: string) => Promise; }; export const botDatabase = ( diff --git a/modules/@creature-chess/data/src/databaseConnection.ts b/modules/@creature-chess/data/src/databaseConnection.ts index af1852af..4f570d93 100644 --- a/modules/@creature-chess/data/src/databaseConnection.ts +++ b/modules/@creature-chess/data/src/databaseConnection.ts @@ -6,6 +6,7 @@ import { setup } from "./setup"; import { userDatabase, UserDatabaseFunctions } from "./user"; export type DatabaseConnection = { + prisma: PrismaClient; user: UserDatabaseFunctions; bot: BotDatabaseFunctions; }; @@ -19,6 +20,7 @@ export const createDatabaseConnection = async ( await setup(logger, prisma); return { + prisma, user: userDatabase(logger, prisma), bot: botDatabase(logger, prisma), }; diff --git a/modules/@creature-chess/data/src/user/addGamePlayed.ts b/modules/@creature-chess/data/src/user/addGamePlayed.ts index d0d98362..931e53f9 100644 --- a/modules/@creature-chess/data/src/user/addGamePlayed.ts +++ b/modules/@creature-chess/data/src/user/addGamePlayed.ts @@ -2,7 +2,7 @@ import { PrismaClient } from "@prisma/client"; import { Logger } from "winston"; export const addGamePlayed = - (logger: Logger, client: PrismaClient) => async (id: number) => { + (logger: Logger, client: PrismaClient) => async (id: string) => { try { return await client.users.update({ where: { diff --git a/modules/@creature-chess/data/src/user/addWin.ts b/modules/@creature-chess/data/src/user/addWin.ts index cf7daedb..acc3765b 100644 --- a/modules/@creature-chess/data/src/user/addWin.ts +++ b/modules/@creature-chess/data/src/user/addWin.ts @@ -2,7 +2,7 @@ import { PrismaClient } from "@prisma/client"; import { Logger } from "winston"; export const addWin = - (logger: Logger, client: PrismaClient) => async (id: number) => { + (logger: Logger, client: PrismaClient) => async (id: string) => { try { return await client.users.update({ where: { diff --git a/modules/@creature-chess/data/src/user/getById.ts b/modules/@creature-chess/data/src/user/getById.ts index 2e366fbf..4be2a596 100644 --- a/modules/@creature-chess/data/src/user/getById.ts +++ b/modules/@creature-chess/data/src/user/getById.ts @@ -2,7 +2,7 @@ import { PrismaClient } from "@prisma/client"; import { Logger } from "winston"; export const getById = - (logger: Logger, client: PrismaClient) => async (id: number) => { + (logger: Logger, client: PrismaClient) => async (id: string) => { try { return await client.users.findUnique({ where: { diff --git a/modules/@creature-chess/data/src/user/index.ts b/modules/@creature-chess/data/src/user/index.ts index 263d897f..7bc024d5 100644 --- a/modules/@creature-chess/data/src/user/index.ts +++ b/modules/@creature-chess/data/src/user/index.ts @@ -15,14 +15,14 @@ export type UserDatabaseFunctions = { getByAuthId: (authId: string) => Promise; - getById: (id: number) => Promise; + getById: (id: string) => Promise; getByNickname: (nickname: string) => Promise; - addWin: (id: number) => Promise; - addGamePlayed: (id: number) => Promise; + addWin: (id: string) => Promise; + addGamePlayed: (id: string) => Promise; setProfileInfo: ( - id: number, + id: string, nickname: string | null, picture: number | null ) => Promise; diff --git a/modules/@creature-chess/data/src/user/setProfileInfo.ts b/modules/@creature-chess/data/src/user/setProfileInfo.ts index 4663aa88..5f399863 100644 --- a/modules/@creature-chess/data/src/user/setProfileInfo.ts +++ b/modules/@creature-chess/data/src/user/setProfileInfo.ts @@ -3,7 +3,7 @@ import { Logger } from "winston"; export const setProfileInfo = (logger: Logger, client: PrismaClient) => - async (id: number, nickname: string | null, picture: number | null) => { + async (id: string, nickname: string | null, picture: number | null) => { try { logger.info(`setProfileInfo for user ${id}`); logger.info(`nickname: ${nickname}`); diff --git a/modules/@creature-chess/models/src/lobby-player.ts b/modules/@creature-chess/models/src/lobby-player.ts index 936e3902..74fe3305 100644 --- a/modules/@creature-chess/models/src/lobby-player.ts +++ b/modules/@creature-chess/models/src/lobby-player.ts @@ -1,7 +1,7 @@ import { PlayerProfile } from "./playerProfile"; export interface LobbyPlayer { - id: number; + id: string; name: string; profile: PlayerProfile; } diff --git a/modules/@creature-chess/networking/handshake.ts b/modules/@creature-chess/networking/handshake.ts index a8ed31a9..45e6a3f9 100644 --- a/modules/@creature-chess/networking/handshake.ts +++ b/modules/@creature-chess/networking/handshake.ts @@ -1,3 +1,3 @@ export type HandshakeRequest = { type: "auth0"; data: { accessToken: string } } - | { type: "guest"; data?: never }; + | { type: "guest"; data: { accessToken: string } }; From 9e8d24931f1a832b040aa51ea8c7932bb880b6a1 Mon Sep 17 00:00:00 2001 From: James Monger Date: Sat, 26 Aug 2023 08:04:27 +0000 Subject: [PATCH 10/16] fix: change socket/registry when reconnecting --- apps/server-game/src/lobby.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/apps/server-game/src/lobby.ts b/apps/server-game/src/lobby.ts index be1f604b..cf5c79e4 100644 --- a/apps/server-game/src/lobby.ts +++ b/apps/server-game/src/lobby.ts @@ -50,6 +50,9 @@ export class Lobby { const existing = this.members.find((m) => m.player.id === socket.data.id); if (existing) { existing.socket?.disconnect(true); + + existing.socket = socket; + existing.registry = registry; } else { const defaultProfile: PlayerProfile = { picture: 1, From bf0da751739c95f1568f1c33895bac8d40781050 Mon Sep 17 00:00:00 2001 From: James Monger Date: Sat, 26 Aug 2023 08:04:40 +0000 Subject: [PATCH 11/16] refactor: query bots through prisma directly --- apps/server-game/src/external/bots.ts | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/apps/server-game/src/external/bots.ts b/apps/server-game/src/external/bots.ts index 80865357..6544de18 100644 --- a/apps/server-game/src/external/bots.ts +++ b/apps/server-game/src/external/bots.ts @@ -4,13 +4,19 @@ import { LobbyPlayer } from "@creature-chess/models"; export const getBots = async (database: DatabaseConnection, count: number) => { const output: { player: LobbyPlayer; personality: BotPersonality }[] = []; - const bots = await database.bot.getLeastPlayedBots(count); - for (const { id, nickname, ambition, composure, vision } of bots!) { + const bots = await database.prisma.bots.findMany({ + take: count, + orderBy: { + games_played: "asc", + }, + }); + + for (const { id, nickname, ambition, composure, vision } of bots) { // get a random picture from one to 20 - temporary const picture = Math.floor(Math.random() * 20) + 1; const player = { - id: (id + 1) * -1, + id: "bot-" + id, name: `[BOT] ${nickname}`, profile: { title: null, From 1004f9a4ccd05e1bb9bbee1141660e9b9ecaa74a Mon Sep 17 00:00:00 2001 From: James Monger Date: Sat, 26 Aug 2023 08:06:04 +0000 Subject: [PATCH 12/16] refactor: rework networking sagas --- apps/web-game/lobby/networking.ts | 39 +++------------- apps/web-game/src/game/networking/saga.ts | 11 ++--- apps/web-game/src/game/sagas/index.ts | 36 +++++---------- apps/web-game/src/networking/events.ts | 18 -------- apps/web-game/src/networking/saga.ts | 44 ++++++++++--------- apps/web-game/src/store/saga.ts | 7 ++- .../gamemode/src/game/roundInfo/state.ts | 6 +-- .../models/src/roundInfoState.ts | 6 +-- 8 files changed, 50 insertions(+), 117 deletions(-) delete mode 100644 apps/web-game/src/networking/events.ts diff --git a/apps/web-game/lobby/networking.ts b/apps/web-game/lobby/networking.ts index 64a4ff25..a1976813 100644 --- a/apps/web-game/lobby/networking.ts +++ b/apps/web-game/lobby/networking.ts @@ -1,25 +1,18 @@ import { Action } from "redux"; import { EventChannel, eventChannel } from "redux-saga"; import { put } from "redux-saga/effects"; -import { call } from "redux-saga/effects"; import { Socket } from "socket.io-client"; -import { cancelled, race, take } from "typed-redux-saga"; +import { cancelled, fork, take } from "typed-redux-saga"; import { IncomingRegistry } from "@shoki/networking"; import { - GameServerToClient, LobbyServerToClient, } from "@creature-chess/networking"; -import { - gameConnectedEvent, - lobbyConnectedEvent, - LobbyConnectedEvent, -} from "../src/networking/events"; import { LobbyCommands } from "./state"; -const readPacketsToActions = function* ( +const readPacketsToActions = function*( registry: IncomingRegistry ) { let channel: EventChannel | null = null; @@ -30,14 +23,6 @@ const readPacketsToActions = function* ( emit(LobbyCommands.updatePlayers({ players })); }); - // todo move this - registry.on( - "gameConnected" as any, - (packet: GameServerToClient.GameConnectionPacket) => { - emit(gameConnectedEvent(packet)); - } - ); - // tslint:disable-next-line:no-empty return () => { // todo registry.off or registry.close @@ -56,12 +41,8 @@ const readPacketsToActions = function* ( } }; -export const lobbyNetworking = function* (socket: Socket) { - const event: LobbyConnectedEvent = yield take( - lobbyConnectedEvent.toString() - ); - - yield put(LobbyCommands.connectToLobby(event.payload)); +export const lobbyNetworking = function*(socket: Socket, payload: LobbyServerToClient.LobbyConnectionPacket) { + yield put(LobbyCommands.connectToLobby(payload)); // todo fix typing const registry = LobbyServerToClient.incoming( @@ -69,15 +50,5 @@ export const lobbyNetworking = function* (socket: Socket) { (opcode, handler) => socket.off(opcode, handler as any) ); - const runForever = call(readPacketsToActions, registry); - const connectedToGame = take(gameConnectedEvent.toString()); - - const result = yield* race({ - runForever, - connectedToGame, - }); - - if (result.connectedToGame) { - yield put(result.connectedToGame as unknown as Action); - } + yield fork(readPacketsToActions, registry); }; diff --git a/apps/web-game/src/game/networking/saga.ts b/apps/web-game/src/game/networking/saga.ts index 5caefa9f..370a30b6 100644 --- a/apps/web-game/src/game/networking/saga.ts +++ b/apps/web-game/src/game/networking/saga.ts @@ -3,18 +3,13 @@ import { Socket } from "socket.io-client"; import { GameEvents } from "@creature-chess/gamemode"; -import { - gameConnectedEvent, - GameConnectedEvent, -} from "../../networking/events"; import { ConnectionStatus } from "../connection-status"; import { setInGameCommand, updateConnectionStatus } from "../ui/actions"; import { incomingGameServerToClient } from "./incoming"; import { outgoingGameServerToClient } from "./outgoing"; +import { GameServerToClient } from "@creature-chess/networking"; -export const gameNetworking = function* (socket: Socket) { - yield take(gameConnectedEvent.toString()); - +export const gameNetworking = function*(socket: Socket, payload: GameServerToClient.GameConnectionPacket) { yield put(setInGameCommand()); yield put(updateConnectionStatus(ConnectionStatus.CONNECTED)); @@ -23,7 +18,7 @@ export const gameNetworking = function* (socket: Socket) { call(outgoingGameServerToClient, socket), call(incomingGameServerToClient, socket), ]), - call(function* () { + call(function*() { yield take(GameEvents.gameFinishEvent.toString()); }), ]); diff --git a/apps/web-game/src/game/sagas/index.ts b/apps/web-game/src/game/sagas/index.ts index 2bdb5111..832ec195 100644 --- a/apps/web-game/src/game/sagas/index.ts +++ b/apps/web-game/src/game/sagas/index.ts @@ -1,12 +1,6 @@ -import { all, call, put } from "redux-saga/effects"; -import { take } from "typed-redux-saga"; +import { all, call } from "redux-saga/effects"; -import { RoundInfoCommands } from "@creature-chess/gamemode"; -import { - gameConnectedEvent, - GameConnectedEvent, -} from "../../networking/events"; import { clickPieceSaga, clickTileSaga } from "../board"; import { PlayerListCommands, closeShopOnFirstBuySaga } from "../module"; import { handleQuickChat } from "../module/chat/sagas"; @@ -15,10 +9,11 @@ import { goToMenuAfterGame } from "./goToMenuAfterGame"; import { preventAccidentalClose } from "./preventAccidentalClose"; import { roundUpdateSaga } from "./roundUpdate"; import { uiSaga } from "./ui"; +import { GameServerToClient } from "@creature-chess/networking"; +import { put } from "typed-redux-saga"; +import { RoundInfoCommands } from "@creature-chess/gamemode"; -export const gameSaga = function* () { - const action = yield* take(gameConnectedEvent.toString()); - +export const gameSaga = function*(payload: GameServerToClient.GameConnectionPacket) { yield all([ call(goToMenuAfterGame), call(preventAccidentalClose), @@ -29,23 +24,12 @@ export const gameSaga = function* () { call(clientBattleSaga), call(uiSaga), call(handleQuickChat), - call(function* () { - if (action.type === "gameConnectedEvent") { - const { - payload: { - players, - game: { phase, phaseStartedAtSeconds }, - }, - } = action; - yield put(PlayerListCommands.updatePlayerListCommand(players)); - - if (!phase || !phaseStartedAtSeconds) { - return; - } + call(function*() { + const { players, game: { phase, phaseStartedAtSeconds } } = payload; + yield put(PlayerListCommands.updatePlayerListCommand(players)); - const update = { phase, startedAt: phaseStartedAtSeconds }; - yield put(RoundInfoCommands.setRoundInfoCommand(update)); - } + const update = { phase, startedAt: phaseStartedAtSeconds }; + yield put(RoundInfoCommands.setRoundInfoCommand(update)); }), ]); }; diff --git a/apps/web-game/src/networking/events.ts b/apps/web-game/src/networking/events.ts deleted file mode 100644 index 21f79276..00000000 --- a/apps/web-game/src/networking/events.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { createAction } from "@reduxjs/toolkit"; - -import { - LobbyServerToClient, - GameServerToClient, -} from "@creature-chess/networking"; - -export type LobbyConnectedEvent = ReturnType; -export const lobbyConnectedEvent = createAction< - LobbyServerToClient.LobbyConnectionPacket, - "lobbyConnectedEvent" ->("lobbyConnectedEvent"); - -export type GameConnectedEvent = ReturnType; -export const gameConnectedEvent = createAction< - GameServerToClient.GameConnectionPacket, - "gameConnectedEvent" ->("gameConnectedEvent"); diff --git a/apps/web-game/src/networking/saga.ts b/apps/web-game/src/networking/saga.ts index 1eb2e440..d511e8fc 100644 --- a/apps/web-game/src/networking/saga.ts +++ b/apps/web-game/src/networking/saga.ts @@ -1,7 +1,7 @@ import { createAction } from "@reduxjs/toolkit"; import { eventChannel } from "redux-saga"; import { Socket } from "socket.io-client"; -import { all, call, put, take } from "typed-redux-saga"; +import { all, call, cancel, fork, take } from "typed-redux-saga"; import { LobbyServerToClient, @@ -10,8 +10,7 @@ import { import { HandshakeRequest } from "@creature-chess/networking/handshake"; import { lobbyNetworking } from "../../lobby"; -import { gameNetworking } from "../game"; -import { lobbyConnectedEvent, gameConnectedEvent } from "./events"; +import { gameNetworking, gameSaga } from "../game"; import { getSocket } from "./socket"; type ConnectionResult = @@ -29,11 +28,13 @@ const listenForConnection = function*(socket: Socket) { const onLobbyConnected = ( payload: LobbyServerToClient.LobbyConnectionPacket ) => { + console.log("Lobby connected"); emit({ type: "lobby", payload }); }; const onGameConnected = ( payload: GameServerToClient.GameConnectionPacket ) => { + console.log("Game connected"); emit({ type: "game", payload }); }; @@ -41,19 +42,30 @@ const listenForConnection = function*(socket: Socket) { socket.on("gameConnected", onGameConnected); return () => { + console.log("Cleaning up listeners"); socket.off("connected", onLobbyConnected); socket.off("gameConnected", onGameConnected); }; }); - const connection = yield* take(channel); + let lobbyTask; - channel.close(); - - return connection; + while (true) { + const connection = yield* take(channel); + if (connection.type === "lobby") { + lobbyTask = yield* fork(lobbyNetworking, socket, connection.payload); + } else if (connection.type === "game") { + if (lobbyTask) { + yield cancel(lobbyTask); // Cancel the lobby networking task + } + yield all([ + call(gameNetworking, socket, connection.payload), + call(gameSaga, connection.payload) + ]); + } + } }; - type OpenConnectionAction = ReturnType; export const openConnection = createAction( "openConnection" @@ -67,23 +79,13 @@ export const networkingSaga = function*() { let socket: Socket; try { + console.log("Getting socket"); socket = yield* call(getSocket, request); } catch (error) { console.error("error getting socket", error); return; } - const connection = yield* call(listenForConnection, socket); - - yield* all([ - call(lobbyNetworking, socket), - call(gameNetworking, socket), - call(function*() { - if (connection.type === "lobby") { - yield put(lobbyConnectedEvent(connection.payload)); - } else if (connection.type === "game") { - yield put(gameConnectedEvent(connection.payload)); - } - }), - ]); + console.log("Listening for connection"); + yield* call(listenForConnection, socket); }; diff --git a/apps/web-game/src/store/saga.ts b/apps/web-game/src/store/saga.ts index 58cffcc3..100732c8 100644 --- a/apps/web-game/src/store/saga.ts +++ b/apps/web-game/src/store/saga.ts @@ -1,8 +1,7 @@ -import { all, call } from "redux-saga/effects"; +import { call } from "redux-saga/effects"; -import { gameSaga } from "../game"; import { networkingSaga } from "../networking"; -export const rootSaga = function* () { - yield all([call(networkingSaga), call(gameSaga)]); +export const rootSaga = function*() { + yield call(networkingSaga); }; diff --git a/modules/@creature-chess/gamemode/src/game/roundInfo/state.ts b/modules/@creature-chess/gamemode/src/game/roundInfo/state.ts index c97f96bc..c38f34e1 100644 --- a/modules/@creature-chess/gamemode/src/game/roundInfo/state.ts +++ b/modules/@creature-chess/gamemode/src/game/roundInfo/state.ts @@ -3,9 +3,9 @@ import { createSlice, PayloadAction } from "@reduxjs/toolkit"; import { GamePhase, RoundInfoState } from "@creature-chess/models"; const initialState: RoundInfoState = { - round: null, - phase: null, - phaseStartedAtSeconds: null, + round: 1, + phase: GamePhase.PREPARING, + phaseStartedAtSeconds: 0, }; export const { diff --git a/modules/@creature-chess/models/src/roundInfoState.ts b/modules/@creature-chess/models/src/roundInfoState.ts index aeaa24ab..69777057 100644 --- a/modules/@creature-chess/models/src/roundInfoState.ts +++ b/modules/@creature-chess/models/src/roundInfoState.ts @@ -1,7 +1,7 @@ import { GamePhase } from "./game-phase"; export type RoundInfoState = { - round: number | null; - phase: GamePhase | null; - phaseStartedAtSeconds: number | null; + round: number; + phase: GamePhase; + phaseStartedAtSeconds: number; }; From 27bf7caeb13ab4fc96bd331ad330ddae2f6f9c3b Mon Sep 17 00:00:00 2001 From: James Monger Date: Sat, 26 Aug 2023 08:06:14 +0000 Subject: [PATCH 13/16] fix: delay before sending connection packets --- apps/server-game/src/lobby.ts | 10 ++++++---- apps/server-game/src/player/index.ts | 6 ++++-- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/apps/server-game/src/lobby.ts b/apps/server-game/src/lobby.ts index cf5c79e4..941eaecf 100644 --- a/apps/server-game/src/lobby.ts +++ b/apps/server-game/src/lobby.ts @@ -73,11 +73,13 @@ export class Lobby { this.notifyOthers(newMember); } - this.sendConnected(registry); + setTimeout(() => { + this.sendConnected(registry); - if (this.members.length === this.options.maxPlayers) { - this.start(); - } + if (this.members.length === this.options.maxPlayers) { + this.start(); + } + }, 500); } private start = () => { diff --git a/apps/server-game/src/player/index.ts b/apps/server-game/src/player/index.ts index 11bf8c82..375a281d 100644 --- a/apps/server-game/src/player/index.ts +++ b/apps/server-game/src/player/index.ts @@ -22,7 +22,7 @@ type Parameters = { getPlayers: () => PlayerListPlayer[]; }; -export const playerNetworking = function* ( +export const playerNetworking = function*( socket: Socket, { getRoundInfo, getPlayers }: Parameters ) { @@ -38,7 +38,7 @@ export const playerNetworking = function* ( yield* setPacketRegistries(registries); - const teardown = function* () { + const teardown = function*() { socket!.removeAllListeners(); socket!.disconnect(); @@ -47,6 +47,8 @@ export const playerNetworking = function* ( yield put(PlayerCommands.setSpectatingIdCommand(null)); + yield delay(500); + registries.outgoing.send("gameConnected", { game: getRoundInfo(), players: getPlayers(), From e20ac4634117d91eaf4c45148d17796fa2e1929a Mon Sep 17 00:00:00 2001 From: James Monger Date: Sat, 26 Aug 2023 08:06:19 +0000 Subject: [PATCH 14/16] docs: update docs --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 818dee0c..e277b71d 100644 --- a/README.md +++ b/README.md @@ -21,11 +21,11 @@ Copy `.env.example` to `.env` in the root of the repo. These will be automatically picked up and used by the game. -### Auth0 Setup +### Auth0 Setup (optional) -You will need to set up an Auth0 tenant in order to run Creature Chess locally. +You can optionally configure Auth0 to manage users and authentication. -See "Environment variables" above for info on how to store them. +You will need to set up an Auth0 tenant and store some environment variables. - Set up a [machine to machine app](https://auth0.com/docs/applications/set-up-an-application/register-machine-to-machine-applications) From 5c9f793b8e150b13bde2654f91f324db61a7a2f4 Mon Sep 17 00:00:00 2001 From: James Monger Date: Sat, 26 Aug 2023 09:03:42 +0000 Subject: [PATCH 15/16] chore: remove build from package.json --- package.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/package.json b/package.json index 6b2e6c3d..d7e95634 100644 --- a/package.json +++ b/package.json @@ -18,8 +18,7 @@ "build-storybook": "build-storybook -o ./output", "lint": "eslint \"apps/**/*.ts\" \"modules/**/*.ts\" \"apps/**/*.tsx\" \"modules/**/*.tsx\"", "prettier": "prettier -w ./", - "prettier-check": "prettier -c ./", - "build": "yarn workspaces foreach run build" + "prettier-check": "prettier -c ./" }, "workspaces": [ "modules/@creature-chess/*", From e1b1ca7640d1d85d9f74175986d1a5b9c4025f94 Mon Sep 17 00:00:00 2001 From: James Monger Date: Sat, 26 Aug 2023 09:08:35 +0000 Subject: [PATCH 16/16] refactor: cr feedback --- modules/@creature-chess/auth-server/src/authenticate.ts | 2 +- modules/@creature-chess/auth-web/guest/provider.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/@creature-chess/auth-server/src/authenticate.ts b/modules/@creature-chess/auth-server/src/authenticate.ts index aa3b4900..8c2d9634 100644 --- a/modules/@creature-chess/auth-server/src/authenticate.ts +++ b/modules/@creature-chess/auth-server/src/authenticate.ts @@ -17,7 +17,7 @@ export const authenticate = async ( token: string ): Promise => { if (!verifyDecodeJwt) { - throw new Error("todo"); + throw new Error("Attempted to use auth0 when it is not enabled"); } const decoded = await verifyDecodeJwt(token); diff --git a/modules/@creature-chess/auth-web/guest/provider.tsx b/modules/@creature-chess/auth-web/guest/provider.tsx index c3ef5afe..4a8db663 100644 --- a/modules/@creature-chess/auth-web/guest/provider.tsx +++ b/modules/@creature-chess/auth-web/guest/provider.tsx @@ -52,7 +52,7 @@ export function GuestAuthProvider({ children }: { children: React.ReactNode }) { () => ({ type: "guest", id: session?.id || "", - nickname: "Guest", + nickname: "Guest " + (session?.id || ""), }), [session] );