basic MP functionality working

This commit is contained in:
Shaheed Azaad
2025-07-17 10:09:20 +02:00
parent 02734040cb
commit e9565471cb
8 changed files with 805 additions and 23 deletions

View File

@@ -21,6 +21,18 @@ services:
command: server --console-address ":9001" /data command: server --console-address ":9001" /data
volumes: volumes:
- minio-data:/data - minio-data:/data
chrome:
image: browserless/chrome:latest
ports:
- 3000:3000
environment:
- PREBOOT_CHROME=true
- CONNECTION_TIMEOUT=60000
- MAX_CONCURRENT_SESSIONS=10
- CHROME_REFRESH_TIME=2000
- DEBUG=*
extra_hosts:
- "host.docker.internal:host-gateway"
volumes: volumes:
pgdata: pgdata:
minio-data: minio-data:

View File

@@ -6,3 +6,6 @@ S3_ACCESS_KEY="minioadmin"
S3_SECRET_KEY="minioadmin" S3_SECRET_KEY="minioadmin"
S3_BUCKET="cog-socket" S3_BUCKET="cog-socket"
BASE_URL="http://localhost:5173" BASE_URL="http://localhost:5173"
# Chrome for multiplayer functionality
CHROME_HOST="localhost"
CHROME_PORT="9222"

764
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -31,6 +31,7 @@
"@sveltejs/vite-plugin-svelte": "^6.0.0", "@sveltejs/vite-plugin-svelte": "^6.0.0",
"@tailwindcss/vite": "^4.0.0", "@tailwindcss/vite": "^4.0.0",
"@types/node": "^22", "@types/node": "^22",
"@types/ws": "^8.18.1",
"@vitest/browser": "^3.2.3", "@vitest/browser": "^3.2.3",
"drizzle-kit": "^0.30.2", "drizzle-kit": "^0.30.2",
"eslint": "^9.18.0", "eslint": "^9.18.0",
@@ -68,10 +69,13 @@
"drizzle-orm": "^0.40.0", "drizzle-orm": "^0.40.0",
"lucia": "^3.2.2", "lucia": "^3.2.2",
"mime-types": "^3.0.1", "mime-types": "^3.0.1",
"morphdom": "^2.7.5",
"oslo": "^1.2.1", "oslo": "^1.2.1",
"postgres": "^3.4.5", "postgres": "^3.4.5",
"puppeteer-core": "^24.14.0",
"svelte-sonner": "^1.0.5", "svelte-sonner": "^1.0.5",
"tailwind-merge": "^3.3.1", "tailwind-merge": "^3.3.1",
"ws": "^8.18.3",
"zod": "^3.25.76" "zod": "^3.25.76"
} }
} }

View File

@@ -5,6 +5,11 @@ import * as schema from '$lib/server/db/schema';
import { eq } from 'drizzle-orm'; import { eq } from 'drizzle-orm';
import argon2 from '@node-rs/argon2'; import argon2 from '@node-rs/argon2';
import { dev } from '$app/environment'; import { dev } from '$app/environment';
import { sessionManager } from '$lib/server/multiplayer/sessionManager';
// Initialize session manager (this starts the WebSocket server)
console.log('Initializing multiplayer session manager...');
// The sessionManager is initialized when imported
const ensureDefaultAdmin = async () => { const ensureDefaultAdmin = async () => {
if (!dev) return; if (!dev) return;

View File

@@ -165,7 +165,9 @@ export async function GET({ params, cookies, getClientAddress, request }) {
} }
} }
const s3Prefix = `experiments/${experimentId}/`; // Files are stored in the user's experiment_files directory
const createdBy = experiment.createdBy;
const s3Prefix = `${createdBy}/${experimentId}/experiment_files/`;
const filePath = path === '' ? 'index.html' : path; const filePath = path === '' ? 'index.html' : path;
const key = `${s3Prefix}${filePath}`; const key = `${s3Prefix}${filePath}`;
@@ -199,15 +201,15 @@ export async function GET({ params, cookies, getClientAddress, request }) {
// Get all files in the experiment directory to create a resource manifest // Get all files in the experiment directory to create a resource manifest
const listCommand = new ListObjectsV2Command({ const listCommand = new ListObjectsV2Command({
Bucket: S3_BUCKET, Bucket: S3_BUCKET,
Prefix: `experiments/${experimentId}/` Prefix: s3Prefix
}); });
const listResponse = await s3.send(listCommand); const listResponse = await s3.send(listCommand);
// Create resource manifest for PsychoJS // Create resource manifest for PsychoJS
const resources = (listResponse.Contents || []) const resources = (listResponse.Contents || [])
.filter(obj => obj.Key && obj.Key !== `experiments/${experimentId}/index.html`) .filter(obj => obj.Key && obj.Key !== `${s3Prefix}index.html`)
.map(obj => { .map(obj => {
const relativePath = obj.Key!.replace(`experiments/${experimentId}/`, ''); const relativePath = obj.Key!.replace(s3Prefix, '');
return { return {
name: relativePath, name: relativePath,
path: relativePath path: relativePath

View File

@@ -31,6 +31,15 @@ const vendorFileRules = [
export async function GET({ params, cookies, getClientAddress, request }) { export async function GET({ params, cookies, getClientAddress, request }) {
const { experimentId, path } = params; const { experimentId, path } = params;
// Check if the experiment exists
const experiment = await db.query.experiment.findFirst({
where: eq(schema.experiment.id, experimentId)
});
if (!experiment) {
throw error(404, 'Experiment not found');
}
// Map of requested CSS files to their location in the static directory. // Map of requested CSS files to their location in the static directory.
const staticCssMap: Record<string, string> = { const staticCssMap: Record<string, string> = {
'lib/vendors/survey.widgets.css': 'static/lib/psychoJS/surveyJS/survey.widgets.css', 'lib/vendors/survey.widgets.css': 'static/lib/psychoJS/surveyJS/survey.widgets.css',
@@ -151,7 +160,9 @@ export async function GET({ params, cookies, getClientAddress, request }) {
} }
} }
const s3Prefix = `experiments/${experimentId}/`; // Files are stored in the user's experiment_files directory
const createdBy = experiment.createdBy;
const s3Prefix = `${createdBy}/${experimentId}/experiment_files/`;
const filePath = path === '' ? 'index.html' : path; const filePath = path === '' ? 'index.html' : path;
const key = `${s3Prefix}${filePath}`; const key = `${s3Prefix}${filePath}`;
@@ -180,20 +191,22 @@ export async function GET({ params, cookies, getClientAddress, request }) {
// For PsychoJS experiments, we need to set the base href to the experiment root // For PsychoJS experiments, we need to set the base href to the experiment root
// so that relative paths like "stimuli/image.jpg" resolve correctly // so that relative paths like "stimuli/image.jpg" resolve correctly
const basePath = `/public/run/${experimentId}/`; // Use the request URL to determine if this is multiplayer or single player
const isMultiplayer = request.url.includes('/multiplayer/');
const basePath = isMultiplayer ? `/public/multiplayer/run/${experimentId}/` : `/public/run/${experimentId}/`;
// Get all files in the experiment directory to create a resource manifest // Get all files in the experiment directory to create a resource manifest
const listCommand = new ListObjectsV2Command({ const listCommand = new ListObjectsV2Command({
Bucket: S3_BUCKET, Bucket: S3_BUCKET,
Prefix: `experiments/${experimentId}/` Prefix: s3Prefix
}); });
const listResponse = await s3.send(listCommand); const listResponse = await s3.send(listCommand);
// Create resource manifest for PsychoJS // Create resource manifest for PsychoJS
const resources = (listResponse.Contents || []) const resources = (listResponse.Contents || [])
.filter(obj => obj.Key && obj.Key !== `experiments/${experimentId}/index.html`) .filter(obj => obj.Key && obj.Key !== `${s3Prefix}index.html`)
.map(obj => { .map(obj => {
const relativePath = obj.Key!.replace(`experiments/${experimentId}/`, ''); const relativePath = obj.Key!.replace(s3Prefix, '');
return { return {
name: relativePath, name: relativePath,
path: relativePath path: relativePath

View File

@@ -5,6 +5,11 @@ import { defineConfig } from 'vite';
export default defineConfig({ export default defineConfig({
plugins: [tailwindcss(), sveltekit(), devtoolsJson()], plugins: [tailwindcss(), sveltekit(), devtoolsJson()],
server: {
host: '0.0.0.0', // Listen on all interfaces so Docker containers can access
port: 5173,
allowedHosts: ['host.docker.internal'] // Allow Docker containers to access via host.docker.internal
},
test: { test: {
projects: [ projects: [
{ {