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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/CODEOWNERS
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
* @cpsiaki @LinaBell @liebeskind
8 changes: 6 additions & 2 deletions client/src/components/DottedLoader.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
export const DottedLoader: React.FC = () => {
return (
<div className="p-6 flex w-full h-full items-center justify-center">
<img width={200} src="https://sdk-style.s3.amazonaws.com/icons/loading.svg" />
<div className="container my-6">
<img
alt="Loading"
src="https://sdk-style.s3.amazonaws.com/icons/loading.svg"
style={{ margin: "auto", width: 50, height: 50 }}
/>
</div>
);
};
Expand Down
2 changes: 0 additions & 2 deletions client/src/components/Header.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import React from "react";

const Header = () => {
return (
<>
Expand Down
2 changes: 1 addition & 1 deletion client/src/pages/Home.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down
1 change: 0 additions & 1 deletion client/src/pages/Instructions.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import Header from "@/components/Header";
import React from "react";
import { Link } from "react-router-dom";

const Instructions = () => {
Expand Down
6 changes: 3 additions & 3 deletions client/src/utils/backendAPI.ts
Original file line number Diff line number Diff line change
@@ -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({
Expand All @@ -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;
Expand Down
8 changes: 4 additions & 4 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 2 additions & 8 deletions server/controllers/session/handleGetDataObject.ts
Original file line number Diff line number Diff line change
@@ -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) {
Expand All @@ -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);
Expand Down
8 changes: 1 addition & 7 deletions server/controllers/session/handleGetParticipantsInZone.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
9 changes: 1 addition & 8 deletions server/controllers/session/handleResetSession.ts
Original file line number Diff line number Diff line change
@@ -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";
Expand All @@ -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 });
Expand Down
87 changes: 33 additions & 54 deletions server/controllers/session/handleSetBreakoutConfig.ts
Original file line number Diff line number Diff line change
@@ -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";
Expand Down Expand Up @@ -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 };
}
Expand Down Expand Up @@ -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}`;
Expand All @@ -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);
Expand All @@ -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}`;
Expand All @@ -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[];
Expand All @@ -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",
Expand All @@ -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[];
}

Expand All @@ -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[];
Expand All @@ -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",
Expand All @@ -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(
Expand Down Expand Up @@ -300,22 +282,17 @@ 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({
droppedAssetId: keyAsset.dataObject!.landmarkZoneId,
shouldIncludeAdminPermissions: true,
});
if (!includeAdmins) {
Object.values(visitorsObj).forEach((visitor) => {
Object.values(visitorsObj).forEach((visitor: VisitorInterface) => {
if (visitor.isAdmin) {
// @ts-ignore
delete visitorsObj[visitor.visitorId];
}
});
Expand Down Expand Up @@ -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: {
Expand All @@ -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({
Expand All @@ -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(
Expand Down
2 changes: 1 addition & 1 deletion server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
19 changes: 5 additions & 14 deletions server/utils/droppedAssets/getDroppedAssetsBySceneDropId.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
Loading
Loading