diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 0000000..37f8642 --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1 @@ +* @cpsiaki @LinaBell @liebeskind \ No newline at end of file diff --git a/client/src/components/DottedLoader.tsx b/client/src/components/DottedLoader.tsx index dcda45e..b2a4ce0 100644 --- a/client/src/components/DottedLoader.tsx +++ b/client/src/components/DottedLoader.tsx @@ -1,7 +1,11 @@ export const DottedLoader: React.FC = () => { return ( -
- +
+ Loading
); }; diff --git a/client/src/components/Header.tsx b/client/src/components/Header.tsx index 9f56fea..914bb34 100644 --- a/client/src/components/Header.tsx +++ b/client/src/components/Header.tsx @@ -1,5 +1,3 @@ -import React from "react"; - const Header = () => { return ( <> diff --git a/client/src/pages/Home.tsx b/client/src/pages/Home.tsx index cc6155b..d6e120e 100644 --- a/client/src/pages/Home.tsx +++ b/client/src/pages/Home.tsx @@ -12,7 +12,7 @@ import Header from "@/components/Header"; const Home: React.FC = () => { const dispatch = useContext(GlobalDispatchContext); - const { hasInteractiveParams, isAdmin, backendAPI, initLoading, sessionData } = useContext(GlobalStateContext); + const { isAdmin, backendAPI, initLoading, sessionData } = useContext(GlobalStateContext); const [endLoading, setEndLoading] = useState(false); diff --git a/client/src/pages/Instructions.tsx b/client/src/pages/Instructions.tsx index 0792424..965b0d0 100644 --- a/client/src/pages/Instructions.tsx +++ b/client/src/pages/Instructions.tsx @@ -1,5 +1,4 @@ import Header from "@/components/Header"; -import React from "react"; import { Link } from "react-router-dom"; const Instructions = () => { diff --git a/client/src/utils/backendAPI.ts b/client/src/utils/backendAPI.ts index 5e3b4e3..3262f74 100644 --- a/client/src/utils/backendAPI.ts +++ b/client/src/utils/backendAPI.ts @@ -1,5 +1,5 @@ -import axios from 'axios'; -import { InteractiveParams } from '../context/types'; +import axios, { InternalAxiosRequestConfig } from "axios"; +import { InteractiveParams } from "../context/types"; const setupBackendAPI = async (interactiveParams: InteractiveParams) => { const backendAPI = axios.create({ @@ -11,7 +11,7 @@ const setupBackendAPI = async (interactiveParams: InteractiveParams) => { // Only do this if have interactive nonce. if (interactiveParams.assetId) { - backendAPI.interceptors.request.use((config: any) => { + backendAPI.interceptors.request.use((config: InternalAxiosRequestConfig) => { if (!config?.params) config.params = {}; config.params = { ...config.params }; config.params["assetId"] = interactiveParams.assetId; diff --git a/package-lock.json b/package-lock.json index 0563151..c3be1e5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1057,9 +1057,9 @@ ] }, "node_modules/@rtsdk/topia": { - "version": "0.12.6", - "resolved": "https://registry.npmjs.org/@rtsdk/topia/-/topia-0.12.6.tgz", - "integrity": "sha512-YNMoz0Y/xZ1vZwFs+C+aXg8Qd7Z/QR32i5tIdYzbCUSxC173WgcyMmTW6cAy1RDFNxZQgPmH2H4ehieIpUxlJA==" + "version": "0.15.7", + "resolved": "https://registry.npmjs.org/@rtsdk/topia/-/topia-0.15.7.tgz", + "integrity": "sha512-n63tuxROfbP8Y53IipAshbE9iytNAwRphCylP4GjauyJkmOfzBlorvWJIwbqAjXqxEyd2t70DrzYPtMcOdzz4A==" }, "node_modules/@swc/core": { "version": "1.4.11", @@ -5610,7 +5610,7 @@ "name": "@breakout/server", "version": "0.0.0", "dependencies": { - "@rtsdk/topia": "^0.12.6", + "@rtsdk/topia": "^0.15.7", "axios": "^1.6.8", "cors": "^2.8.5", "dotenv": "^16.4.5", diff --git a/server/controllers/session/handleGetDataObject.ts b/server/controllers/session/handleGetDataObject.ts index e328fa0..a8c9d89 100644 --- a/server/controllers/session/handleGetDataObject.ts +++ b/server/controllers/session/handleGetDataObject.ts @@ -1,4 +1,4 @@ -import { World, WorldActivity, errorHandler, getCredentials, getDroppedAsset } from "../../utils/index.js"; +import { WorldActivity, errorHandler, getCredentials, getDroppedAsset } from "../../utils/index.js"; import { Request, Response } from "express"; export default async function handleGetDataObject(req: Request, res: Response) { @@ -10,13 +10,7 @@ export default async function handleGetDataObject(req: Request, res: Response) { return res.status(404).json({ message: "Asset not found" }); } - const worldActivity = WorldActivity.create(credentials.urlSlug, { - credentials: { - interactiveNonce: credentials.interactiveNonce, - interactivePublicKey: credentials.interactivePublicKey, - visitorId: credentials.visitorId, - }, - }); + const worldActivity = WorldActivity.create(credentials.urlSlug, { credentials }); const visitors = await worldActivity.fetchVisitorsInZone({ droppedAssetId: keyAsset.dataObject.landmarkZoneId }); const visitorProfileIds = Object.values(visitors).map((visitor) => visitor.profileId); diff --git a/server/controllers/session/handleGetParticipantsInZone.ts b/server/controllers/session/handleGetParticipantsInZone.ts index 4031111..dfd15f0 100644 --- a/server/controllers/session/handleGetParticipantsInZone.ts +++ b/server/controllers/session/handleGetParticipantsInZone.ts @@ -9,13 +9,7 @@ export default async function handleGetParticipantsInZone(req: Request, res: Res return res.status(404).json({ message: "Asset not found" }); } - const worldActivity = WorldActivity.create(credentials.urlSlug, { - credentials: { - interactiveNonce: credentials.interactiveNonce, - interactivePublicKey: credentials.interactivePublicKey, - visitorId: credentials.visitorId, - }, - }); + const worldActivity = WorldActivity.create(credentials.urlSlug, { credentials }); const visitors = await worldActivity.fetchVisitorsInZone({ droppedAssetId: keyAsset.dataObject.landmarkZoneId }); const participants = Object.values(visitors).map(({ profileId, username }) => { return { diff --git a/server/controllers/session/handleResetSession.ts b/server/controllers/session/handleResetSession.ts index 72b7b3c..a0a0ac8 100644 --- a/server/controllers/session/handleResetSession.ts +++ b/server/controllers/session/handleResetSession.ts @@ -1,4 +1,3 @@ -import { Credentials } from "../../types/index.js"; import { WorldActivity, defaultDataObject, errorHandler, getCredentials, getDroppedAsset } from "../../utils/index.js"; import { Request, Response } from "express"; import { endBreakout } from "./handleSetBreakoutConfig.js"; @@ -7,13 +6,7 @@ import closeIframeForVisitors from "../../utils/session/closeIframeForVisitors.j export default async function handleResetSession(req: Request, res: Response) { try { const credentials = getCredentials(req.query); - const worldActivity = WorldActivity.create(credentials.urlSlug, { - credentials: { - interactiveNonce: credentials.interactiveNonce, - interactivePublicKey: credentials.interactivePublicKey, - visitorId: credentials.visitorId, - }, - }); + const worldActivity = WorldActivity.create(credentials.urlSlug, { credentials }); const keyAsset = await getDroppedAsset(credentials); const visitors = await worldActivity.fetchVisitorsInZone({ droppedAssetId: keyAsset.dataObject.landmarkZoneId }); diff --git a/server/controllers/session/handleSetBreakoutConfig.ts b/server/controllers/session/handleSetBreakoutConfig.ts index 8b991f8..f3662f6 100644 --- a/server/controllers/session/handleSetBreakoutConfig.ts +++ b/server/controllers/session/handleSetBreakoutConfig.ts @@ -1,4 +1,4 @@ -import { DroppedAsset, WorldActivity as IWorldActivity, Visitor } from "@rtsdk/topia"; +import { DroppedAsset, DroppedAssetInterface, Visitor, VisitorInterface, WorldActivityType } from "@rtsdk/topia"; import { AnalyticType, Credentials } from "../../types/index.js"; import { getDroppedAssetsBySceneDropId } from "../../utils/droppedAssets/getDroppedAssetsBySceneDropId.js"; import { World, WorldActivity, errorHandler, getCredentials, getDroppedAsset } from "../../utils/index.js"; @@ -41,13 +41,14 @@ export const endBreakout = (key: string) => { }; export const updateAdminCredentials = (credentials: Credentials) => { - const session = Object.entries(breakouts).find(([_, data]) => data.landmarkZoneId === credentials.assetId); - if (session && session[1].adminProfileId === credentials.profileId) { + const { assetId, profileId, interactiveNonce } = credentials; + const session = Object.entries(breakouts).find(([_, data]) => data.landmarkZoneId === assetId); + if (session && session[1].adminProfileId === profileId) { const [key, _] = session as [string, Breakouts[string]]; if ( - breakouts[key].adminProfileId === credentials.profileId && - breakouts[key].adminOriginalInteractiveNonce !== credentials.interactiveNonce + breakouts[key].adminProfileId === profileId && + breakouts[key].adminOriginalInteractiveNonce !== interactiveNonce ) { breakouts[key].adminCredentials = { ...credentials, assetId: key }; } @@ -77,7 +78,7 @@ const getAnalytics = (includedVisitors: Visitor[], matches: string[][], urlSlug: uniqueKey: visitor.profileId as string, }; }); - + const groupSizeAnalytics: { [key: string]: AnalyticType } = {}; matches.forEach((match) => { const analyticName = `groupsOf${match.length}`; @@ -98,6 +99,7 @@ const getAnalytics = (includedVisitors: Visitor[], matches: string[][], urlSlug: export default async function handleSetBreakoutConfig(req: Request, res: Response) { try { const credentials = getCredentials(req.query); + const { assetId, profileId, interactiveNonce, sceneDropId, urlSlug } = credentials; const numOfGroups = Math.min(parseInt(req.body.numOfGroups), 16); const numOfRounds = Math.min(parseInt(req.body.numOfRounds), 25); @@ -115,28 +117,22 @@ export default async function handleSetBreakoutConfig(req: Request, res: Respons numOfGroups < 1 || numOfRounds < 1 ) { - console.log(`Invalid configuration for ${credentials.assetId}`); + console.log(`Invalid configuration for ${assetId}`); return res.status(400).json({ message: "Invalid configuration" }); } const [keyAsset, breakoutScene]: [IDroppedAsset, DroppedAsset[]] = await Promise.all([ getDroppedAsset(credentials), - getDroppedAssetsBySceneDropId(credentials, credentials.sceneDropId), + getDroppedAssetsBySceneDropId(credentials, sceneDropId), ]); const privateZonesAtStart = breakoutScene.filter( - (droppedAsset: DroppedAsset) => droppedAsset.isPrivateZone, + (droppedAsset: DroppedAssetInterface) => droppedAsset.isPrivateZone, ) as DroppedAsset[]; const landmarkZone = breakoutScene.find( - (droppedAsset: DroppedAsset) => droppedAsset.isLandmarkZoneEnabled, + (droppedAsset: DroppedAssetInterface) => droppedAsset.isLandmarkZoneEnabled, ) as DroppedAsset; - const worldActivityAtStart = WorldActivity.create(credentials.urlSlug, { - credentials: { - interactiveNonce: credentials.interactiveNonce, - interactivePublicKey: credentials.interactivePublicKey, - visitorId: credentials.visitorId, - }, - }); + const worldActivityAtStart = WorldActivity.create(urlSlug, { credentials }); const timeFactor = new Date(Math.round(new Date().getTime() / 10000) * 10000); const lockId = `${keyAsset.id!}_${timeFactor}`; @@ -146,10 +142,8 @@ export default async function handleSetBreakoutConfig(req: Request, res: Respons droppedAssetId: keyAsset.dataObject!.landmarkZoneId, shouldIncludeAdminPermissions: true, }); - const includedVisitors = Object.values(visitorsObj).filter((visitor) => { - if (!includeAdmins) { - return !visitor.isAdmin; - } + const includedVisitors = Object.values(visitorsObj).filter((visitor: VisitorInterface) => { + if (!includeAdmins) return !visitor.isAdmin; return true; }); const participants = includedVisitors.map((visitor) => visitor.profileId) as string[]; @@ -173,11 +167,11 @@ export default async function handleSetBreakoutConfig(req: Request, res: Respons analytics: [ { analyticName: "starts", - urlSlug: credentials.urlSlug, + urlSlug: urlSlug, }, { analyticName: `groupConfigOf${numOfGroups}`, - urlSlug: credentials.urlSlug, + urlSlug: urlSlug, }, { analyticName: "rounds", @@ -203,19 +197,13 @@ export default async function handleSetBreakoutConfig(req: Request, res: Respons breakouts[keyAsset.id!].adminOriginalInteractiveNonce !== breakouts[keyAsset.id!].adminCredentials.interactiveNonce ) { - worldActivity = WorldActivity.create(credentials.urlSlug, { - credentials: { - interactiveNonce: breakouts[keyAsset.id!].adminCredentials.interactiveNonce, - interactivePublicKey: credentials.interactivePublicKey, - visitorId: breakouts[keyAsset.id!].adminCredentials.visitorId, - }, - }); + worldActivity = WorldActivity.create(urlSlug, { credentials }); const breakoutScene: DroppedAsset[] = await getDroppedAssetsBySceneDropId( breakouts[keyAsset.id!].adminCredentials, - credentials.sceneDropId, + sceneDropId, ); privateZones = breakoutScene.filter( - (droppedAsset: DroppedAsset) => droppedAsset.isPrivateZone, + (droppedAsset: DroppedAssetInterface) => droppedAsset.isPrivateZone, ) as DroppedAsset[]; } @@ -224,10 +212,8 @@ export default async function handleSetBreakoutConfig(req: Request, res: Respons shouldIncludeAdminPermissions: true, }); - const includedVisitors = Object.values(visitorsObj).filter((visitor) => { - if (!includeAdmins) { - return !visitor.isAdmin; - } + const includedVisitors = Object.values(visitorsObj).filter((visitor: VisitorInterface) => { + if (!includeAdmins) return !visitor.isAdmin; return true; }); const participants = includedVisitors.map((visitor) => visitor.profileId) as string[]; @@ -242,7 +228,7 @@ export default async function handleSetBreakoutConfig(req: Request, res: Respons ); const timeout = setTimeout(() => { - const world = World.create(credentials.urlSlug, { credentials }); + const world = World.create(urlSlug, { credentials }); world .triggerParticle({ name: "pastelConfetti_fall", @@ -257,11 +243,7 @@ export default async function handleSetBreakoutConfig(req: Request, res: Respons breakouts[keyAsset.id!].timeouts.push(timeout); - const { participantsAnalytics, groupSizeAnalytics } = getAnalytics( - includedVisitors, - matches, - credentials.urlSlug, - ); + const { participantsAnalytics, groupSizeAnalytics } = getAnalytics(includedVisitors, matches, urlSlug); keyAsset .updateDataObject( @@ -300,13 +282,7 @@ export default async function handleSetBreakoutConfig(req: Request, res: Respons breakouts[keyAsset.id!].adminOriginalInteractiveNonce !== breakouts[keyAsset.id!].adminCredentials.interactiveNonce ) { - worldActivity = WorldActivity.create(credentials.urlSlug, { - credentials: { - interactiveNonce: breakouts[keyAsset.id!].adminCredentials.interactiveNonce, - interactivePublicKey: credentials.interactivePublicKey, - visitorId: breakouts[keyAsset.id!].adminCredentials.visitorId, - }, - }); + worldActivity = WorldActivity.create(urlSlug, { credentials }); } const visitorsObj = await worldActivity.fetchVisitorsInZone({ @@ -314,8 +290,9 @@ export default async function handleSetBreakoutConfig(req: Request, res: Respons shouldIncludeAdminPermissions: true, }); if (!includeAdmins) { - Object.values(visitorsObj).forEach((visitor) => { + Object.values(visitorsObj).forEach((visitor: VisitorInterface) => { if (visitor.isAdmin) { + // @ts-ignore delete visitorsObj[visitor.visitorId]; } }); @@ -346,8 +323,8 @@ export default async function handleSetBreakoutConfig(req: Request, res: Respons breakouts[keyAsset.id!] = { interval: interval, timeouts: [], - adminProfileId: credentials.profileId, - adminOriginalInteractiveNonce: credentials.interactiveNonce, + adminProfileId: profileId, + adminOriginalInteractiveNonce: interactiveNonce, adminCredentials: credentials, landmarkZoneId: keyAsset.dataObject!.landmarkZoneId, data: { @@ -363,7 +340,7 @@ export default async function handleSetBreakoutConfig(req: Request, res: Respons const matches = getMatches(true, keyAsset.id!, participants, breakouts); const timeout = setTimeout(() => { - const world = World.create(credentials.urlSlug, { credentials }); + const world = World.create(urlSlug, { credentials }); world .triggerParticle({ @@ -374,12 +351,14 @@ export default async function handleSetBreakoutConfig(req: Request, res: Respons .then() .catch(() => console.error("Error: Cannot trigger particle")); + world.triggerActivity({ type: WorldActivityType.GAME_ON, assetId }); + placeVisitors(matches, visitorsObj, participants, keyAsset.id!, breakouts, privateZonesAtStart); }, countdown * 1000); breakouts[keyAsset.id!].timeouts.push(timeout); - const { participantsAnalytics, groupSizeAnalytics } = getAnalytics(includedVisitors, matches, credentials.urlSlug); + const { participantsAnalytics, groupSizeAnalytics } = getAnalytics(includedVisitors, matches, urlSlug); keyAsset .updateDataObject( diff --git a/server/package.json b/server/package.json index de85057..c8aab6d 100644 --- a/server/package.json +++ b/server/package.json @@ -11,7 +11,7 @@ "ts-check": "tsc --noEmit" }, "dependencies": { - "@rtsdk/topia": "^0.12.6", + "@rtsdk/topia": "^0.15.7", "axios": "^1.6.8", "cors": "^2.8.5", "dotenv": "^16.4.5", diff --git a/server/utils/droppedAssets/getDroppedAssetsBySceneDropId.ts b/server/utils/droppedAssets/getDroppedAssetsBySceneDropId.ts index 8ef9d6b..baa8cd5 100644 --- a/server/utils/droppedAssets/getDroppedAssetsBySceneDropId.ts +++ b/server/utils/droppedAssets/getDroppedAssetsBySceneDropId.ts @@ -2,24 +2,15 @@ import { Credentials } from "../../types/Credentials.js"; import { IDroppedAsset } from "../../types/DroppedAssetInterface.js"; import { World, errorHandler } from "../index.js"; -export const getDroppedAssetsBySceneDropId = async ( - credentials: Credentials, - sceneDropId: string, -) => { +export const getDroppedAssetsBySceneDropId = async (credentials: Credentials, sceneDropId: string) => { try { - const { interactivePublicKey, interactiveNonce, urlSlug, visitorId } = credentials; + const { urlSlug } = credentials; - const world = World.create(urlSlug, { - credentials: { - interactiveNonce, - interactivePublicKey, - visitorId, - }, - }); + const world = World.create(urlSlug, { credentials }); - const droppedAssets = await world.fetchDroppedAssetsBySceneDropId({ + const droppedAssets = (await world.fetchDroppedAssetsBySceneDropId({ sceneDropId, - }) as IDroppedAsset[]; + })) as IDroppedAsset[]; return droppedAssets; } catch (error) { diff --git a/server/utils/topiaInit.ts b/server/utils/topiaInit.ts index 0baa21a..c895509 100644 --- a/server/utils/topiaInit.ts +++ b/server/utils/topiaInit.ts @@ -1,11 +1,18 @@ import dotenv from "dotenv"; dotenv.config({ path: "../.env" }); -import { Topia, AssetFactory, DroppedAssetFactory, UserFactory, VisitorFactory, WorldFactory, WorldActivityFactory } from "@rtsdk/topia"; +import { + Topia, + AssetFactory, + DroppedAssetFactory, + UserFactory, + VisitorFactory, + WorldFactory, + WorldActivityFactory, +} from "@rtsdk/topia"; const config = { apiDomain: process.env.INSTANCE_DOMAIN || "api.topia.io", - // apiKey: process.env.API_KEY, apiProtocol: process.env.INSTANCE_PROTOCOL || "https", interactiveKey: process.env.INTERACTIVE_KEY, interactiveSecret: process.env.INTERACTIVE_SECRET, diff --git a/server/utils/visitors/getVisitor.ts b/server/utils/visitors/getVisitor.ts index 0e03221..5add1d1 100644 --- a/server/utils/visitors/getVisitor.ts +++ b/server/utils/visitors/getVisitor.ts @@ -1,18 +1,13 @@ -import { Visitor } from "../topiaInit.js" -import { errorHandler } from "../errorHandler.js" +import { Visitor } from "../topiaInit.js"; +import { errorHandler } from "../errorHandler.js"; import { Credentials } from "../../types/Credentials.js"; +import { VisitorInterface } from "@rtsdk/topia"; export const getVisitor = async (credentials: Credentials) => { try { - const { interactivePublicKey, interactiveNonce, urlSlug, visitorId } = credentials; + const { urlSlug, visitorId } = credentials; - const visitor = await Visitor.get(visitorId, urlSlug, { - credentials: { - interactiveNonce, - interactivePublicKey, - visitorId, - }, - }); + const visitor: VisitorInterface = await Visitor.get(visitorId, urlSlug, { credentials }); if (!visitor || !visitor.username) throw "Not in world";