Skip to content

Commit 24d32c0

Browse files
author
DavidQ
committed
Continued with the next enforcement chunk.
Implemented: - Tightened Workspace-only game launch validation: - [games/shared/workspaceGameLaunchGuard.js](C:\Users\davidq\Documents\GitHub\HTML-JavaScript-Gaming\games\shared\workspaceGameLaunchGuard.js) - Now requires: - `hosted=1` - `hostToolId=workspace-manager` - valid `hostContextId` - shared context with `hostMode="game"` and matching `gameId` - On success, stores validated context on `window.__WORKSPACE_GAME_CONTEXT__`. - Enriched Workspace-generated game context payload: - [tools/Workspace Manager/main.js](C:\Users\davidq\Documents\GitHub\HTML-JavaScript-Gaming\tools\Workspace Manager\main.js) - Hosted game context now includes: `level`, `status`, `description`, `classValues`, `tags`, flags, and `hostedAt`. - Added shared runtime context reader for games: - [games/shared/workspaceGameRuntimeContext.js](C:\Users\davidq\Documents\GitHub\HTML-JavaScript-Gaming\games\shared\workspaceGameRuntimeContext.js) - Exposes `getWorkspaceGameRuntimeContext()` with normalized payload. - Kept Games Hub primary launch workspace-only: - [games/index.render.js](C:\Users\davidq\Documents\GitHub\HTML-JavaScript-Gaming\games\index.render.js) Validation reports: - [samples2tools_batch_42_validation.txt](C:\Users\davidq\Documents\GitHub\HTML-JavaScript-Gaming\docs\dev\reports\samples2tools_batch_42_validation.txt) (`PASS`) - [samples2tools_batch_42_summary.txt](C:\Users\davidq\Documents\GitHub\HTML-JavaScript-Gaming\docs\dev\reports\samples2tools_batch_42_summary.txt) - [samples2tools_batch_43_validation.txt](C:\Users\davidq\Documents\GitHub\HTML-JavaScript-Gaming\docs\dev\reports\samples2tools_batch_43_validation.txt) (`PASS`) - [samples2tools_batch_43_summary.txt](C:\Users\davidq\Documents\GitHub\HTML-JavaScript-Gaming\docs\dev\reports\samples2tools_batch_43_summary.txt) Commit comment: `Enforce strict Workspace-originated game execution by requiring valid hostContextId + game-mode context in launch guard, enrich Workspace game host payload with full metadata/timestamp, and add shared runtime context reader for hosted games (batches 42–43 PASS).`
1 parent fb4a418 commit 24d32c0

8 files changed

Lines changed: 123 additions & 5 deletions
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
Samples2Tools batch 42 summary
2+
Generated: 2026-04-24T22:34:32.900Z
3+
Purpose: require valid Workspace host context for game execution and remove remaining card-level standalone launch fallback.
4+
5+
Completed this batch:
6+
- Added strict hostContext validation in shared game launch guard
7+
- Bound hosted game run approval to hostMode=game plus matching gameId from shared context
8+
- Exposed validated workspace game context to runtime via window.__WORKSPACE_GAME_CONTEXT__
9+
- Removed Games Hub launch fallback to direct href and kept Workspace-only launch URLs
10+
11+
Primary validation artifact:
12+
- docs/dev/reports/samples2tools_batch_42_validation.txt (PASS)
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
Samples2Tools batch 42 validation
2+
Generated: 2026-04-24T22:34:32.900Z
3+
Scope: enforce Workspace-originated game runtime context for launchable games.
4+
5+
- [PASS] Guard imports tool-host shared-context reader :: games/shared/workspaceGameLaunchGuard.js
6+
- [PASS] Guard requires hosted workspace + hostContextId before allowing run :: games/shared/workspaceGameLaunchGuard.js
7+
- [PASS] Guard validates hostMode=game and matching gameId :: games/shared/workspaceGameLaunchGuard.js
8+
- [PASS] Guard exposes workspace context to game runtime :: games/shared/workspaceGameLaunchGuard.js
9+
- [PASS] Games Hub launch href is workspace-only (no row.href fallback) :: games/index.render.js
10+
- [PASS] Workspace manager writes game host context state :: tools/Workspace Manager/main.js
11+
- [PASS] All launchable game pages include guard module :: launchable=11, issues=0
12+
13+
Checks: 7
14+
Failures: 0
15+
Result: PASS
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
Samples2Tools batch 43 summary
2+
Generated: 2026-04-24T22:35:41.329Z
3+
Purpose: move game runtime metadata ownership into Workspace-generated host context.
4+
5+
Completed this batch:
6+
- Expanded Workspace Manager game host context state with game metadata and lifecycle timestamp
7+
- Added shared game runtime context utility for consuming validated hosted context payloads
8+
9+
Primary validation artifact:
10+
- docs/dev/reports/samples2tools_batch_43_validation.txt (PASS)
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
Samples2Tools batch 43 validation
2+
Generated: 2026-04-24T22:35:41.329Z
3+
Scope: expand Workspace-generated game runtime context payload and expose shared runtime context reader.
4+
5+
- [PASS] Workspace game context state includes metadata fields (level/status/description/classValues/tags) :: tools/Workspace Manager/main.js
6+
- [PASS] Workspace game context state includes lifecycle timestamp hostedAt :: tools/Workspace Manager/main.js
7+
- [PASS] Runtime context helper exists for games :: games/shared/workspaceGameRuntimeContext.js
8+
- [PASS] Runtime context helper returns normalized game payload :: games/shared/workspaceGameRuntimeContext.js
9+
10+
Checks: 4
11+
Failures: 0
12+
Result: PASS

games/index.render.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,7 @@ function renderCard(row, instanceKey = "main") {
164164
article.className = "card-link game-card";
165165
article.dataset.gameId = row.id;
166166

167-
const launchHref = row.workspaceHref || row.href || "";
167+
const launchHref = row.workspaceHref || "";
168168
const previewHtml = row.preview
169169
? `<a class="game-preview-link" href="${escapeHtml(launchHref || "#")}" ${launchHref ? "" : "aria-disabled=\"true\""}><img class="game-thumb" loading="lazy" decoding="async" alt="${escapeHtml(row.title)} thumbnail" src="${escapeHtml(row.preview)}"></a>`
170170
: `<span class="game-preview-link game-preview-missing">No Preview</span>`;

games/shared/workspaceGameLaunchGuard.js

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { readToolHostSharedContextFromLocation } from "/tools/shared/toolHostSharedContext.js";
2+
13
function normalizeGameId(value) {
24
return typeof value === "string" ? value.trim() : "";
35
}
@@ -15,8 +17,15 @@ export function enforceWorkspaceGameLaunch(gameId) {
1517
const params = url.searchParams;
1618
const hosted = params.get("hosted") === "1";
1719
const hostToolId = (params.get("hostToolId") || "").trim().toLowerCase();
18-
if (hosted && hostToolId === "workspace-manager") {
19-
return;
20+
const hostContextId = (params.get("hostContextId") || "").trim();
21+
if (hosted && hostToolId === "workspace-manager" && hostContextId) {
22+
const hostContext = readToolHostSharedContextFromLocation(window.location);
23+
const contextGameId = normalizeGameId(hostContext?.sharedContext?.gameId);
24+
const hostMode = normalizeGameId(hostContext?.sharedContext?.hostMode).toLowerCase();
25+
if (hostMode === "game" && contextGameId.toLowerCase() === normalizedGameId.toLowerCase()) {
26+
window.__WORKSPACE_GAME_CONTEXT__ = hostContext;
27+
return;
28+
}
2029
}
2130

2231
const redirectUrl = new URL("/tools/Workspace%20Manager/index.html", window.location.origin);
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
function readContextFromWindow() {
2+
if (typeof window === "undefined") {
3+
return null;
4+
}
5+
const context = window.__WORKSPACE_GAME_CONTEXT__;
6+
return context && typeof context === "object" ? context : null;
7+
}
8+
9+
export function getWorkspaceGameRuntimeContext() {
10+
const context = readContextFromWindow();
11+
if (!context) {
12+
return null;
13+
}
14+
const game = context?.state?.game;
15+
if (!game || typeof game !== "object") {
16+
return null;
17+
}
18+
return {
19+
contextId: typeof context.contextId === "string" ? context.contextId : "",
20+
requestedAt: typeof context.requestedAt === "string" ? context.requestedAt : "",
21+
source: typeof context.source === "string" ? context.source : "",
22+
game: {
23+
id: typeof game.id === "string" ? game.id : "",
24+
title: typeof game.title === "string" ? game.title : "",
25+
href: typeof game.href === "string" ? game.href : "",
26+
level: typeof game.level === "string" ? game.level : "",
27+
status: typeof game.status === "string" ? game.status : "",
28+
description: typeof game.description === "string" ? game.description : "",
29+
classValues: Array.isArray(game.classValues) ? game.classValues.slice() : [],
30+
tags: Array.isArray(game.tags) ? game.tags.slice() : [],
31+
sampleTrack: game.sampleTrack === true,
32+
debugShowcase: game.debugShowcase === true,
33+
requiresService: game.requiresService === true,
34+
hostedAt: typeof game.hostedAt === "string" ? game.hostedAt : ""
35+
}
36+
};
37+
}

tools/Workspace Manager/main.js

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -177,10 +177,24 @@ async function readGameEntryById(gameId) {
177177
if (!href) {
178178
return null;
179179
}
180+
const classValues = Array.isArray(entry.classValues)
181+
? entry.classValues.map((value) => String(value || "").trim()).filter(Boolean)
182+
: [];
183+
const tags = Array.isArray(entry.tags)
184+
? entry.tags.map((value) => String(value || "").trim()).filter(Boolean)
185+
: [];
180186
return {
181187
id: String(entry.id || "").trim(),
182188
title: String(entry.title || entry.id || "Game").trim(),
183-
href
189+
href,
190+
level: String(entry.level || "").trim(),
191+
status: String(entry.status || "").trim(),
192+
description: String(entry.description || "").trim(),
193+
classValues,
194+
tags,
195+
sampleTrack: entry.sampleTrack === true,
196+
debugShowcase: entry.debugShowcase === true,
197+
requiresService: entry.requiresService === true
184198
};
185199
} catch {
186200
return null;
@@ -227,7 +241,16 @@ function mountGameFrame(gameEntry) {
227241
game: {
228242
id: gameEntry.id,
229243
title: gameEntry.title,
230-
href: gameEntry.href
244+
href: gameEntry.href,
245+
level: gameEntry.level,
246+
status: gameEntry.status,
247+
description: gameEntry.description,
248+
classValues: gameEntry.classValues,
249+
tags: gameEntry.tags,
250+
sampleTrack: gameEntry.sampleTrack,
251+
debugShowcase: gameEntry.debugShowcase,
252+
requiresService: gameEntry.requiresService,
253+
hostedAt: new Date().toISOString()
231254
}
232255
}
233256
});

0 commit comments

Comments
 (0)