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]
);