dreamboard 0.1.21 → 0.1.23
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-EYYWGWTO.js → chunk-2RCUHMGL.js} +343 -1911
- package/dist/chunk-2RCUHMGL.js.map +1 -0
- package/dist/{chunk-MOVHYB6E.js → chunk-GN7232BY.js} +1025 -695
- package/dist/chunk-GN7232BY.js.map +1 -0
- package/dist/{chunk-BMYC6772.js → chunk-H3O43F5P.js} +9765 -3068
- package/dist/chunk-H3O43F5P.js.map +1 -0
- package/dist/dev-host/dev-api-proxy-plugin.ts +330 -0
- package/dist/dev-host/dev-diagnostics.ts +62 -0
- package/dist/dev-host/dev-fallback-stylesheet.ts +50 -0
- package/dist/dev-host/dev-host-controller.ts +686 -0
- package/dist/dev-host/dev-host-player-query.ts +17 -0
- package/dist/dev-host/dev-host-session-transport.ts +52 -0
- package/dist/dev-host/dev-host-storage.ts +56 -0
- package/dist/dev-host/dev-log-relay-plugin.ts +469 -0
- package/dist/dev-host/dev-runtime-config.ts +14 -0
- package/dist/dev-host/dev-runtime-platform.ts +419 -0
- package/dist/dev-host/dev-virtual-modules-plugin.ts +63 -0
- package/dist/dev-host/host-main.css +182 -0
- package/dist/dev-host/host-main.tsx +754 -0
- package/dist/dev-host/index.html +56 -0
- package/dist/dev-host/plugin-main.ts +55 -0
- package/dist/dev-host/plugin.html +24 -0
- package/dist/dev-host/start-dev-server.ts +138 -0
- package/dist/dev-host/virtual-modules.d.ts +27 -0
- package/dist/dist-FEPN3BDN.js +16543 -0
- package/dist/dist-FEPN3BDN.js.map +1 -0
- package/dist/{dist-WJRJNFLI.js → dist-FRURQI7Q.js} +4 -2
- package/dist/index.js +3 -3
- package/dist/internal.js +159 -124
- package/dist/internal.js.map +1 -1
- package/dist/runtime-packages/tailwind-config/shared-styles.css +146 -0
- package/dist/runtime-packages/ui/src/components/.gitkeep +0 -0
- package/dist/runtime-packages/ui/src/components/accordion.tsx +66 -0
- package/dist/runtime-packages/ui/src/components/ai-elements/actions.tsx +65 -0
- package/dist/runtime-packages/ui/src/components/ai-elements/artifact.tsx +147 -0
- package/dist/runtime-packages/ui/src/components/ai-elements/branch.tsx +215 -0
- package/dist/runtime-packages/ui/src/components/ai-elements/canvas.tsx +22 -0
- package/dist/runtime-packages/ui/src/components/ai-elements/chain-of-thought.tsx +228 -0
- package/dist/runtime-packages/ui/src/components/ai-elements/code-block.tsx +179 -0
- package/dist/runtime-packages/ui/src/components/ai-elements/confirmation.tsx +158 -0
- package/dist/runtime-packages/ui/src/components/ai-elements/connection.tsx +28 -0
- package/dist/runtime-packages/ui/src/components/ai-elements/context.tsx +408 -0
- package/dist/runtime-packages/ui/src/components/ai-elements/controls.tsx +18 -0
- package/dist/runtime-packages/ui/src/components/ai-elements/conversation.tsx +97 -0
- package/dist/runtime-packages/ui/src/components/ai-elements/edge.tsx +140 -0
- package/dist/runtime-packages/ui/src/components/ai-elements/image.tsx +24 -0
- package/dist/runtime-packages/ui/src/components/ai-elements/inline-citation.tsx +287 -0
- package/dist/runtime-packages/ui/src/components/ai-elements/loader.tsx +96 -0
- package/dist/runtime-packages/ui/src/components/ai-elements/message.tsx +463 -0
- package/dist/runtime-packages/ui/src/components/ai-elements/node.tsx +71 -0
- package/dist/runtime-packages/ui/src/components/ai-elements/open-in-chat.tsx +365 -0
- package/dist/runtime-packages/ui/src/components/ai-elements/panel.tsx +15 -0
- package/dist/runtime-packages/ui/src/components/ai-elements/plan.tsx +142 -0
- package/dist/runtime-packages/ui/src/components/ai-elements/prompt-input.tsx +1380 -0
- package/dist/runtime-packages/ui/src/components/ai-elements/queue.tsx +274 -0
- package/dist/runtime-packages/ui/src/components/ai-elements/reasoning.tsx +182 -0
- package/dist/runtime-packages/ui/src/components/ai-elements/response.tsx +22 -0
- package/dist/runtime-packages/ui/src/components/ai-elements/shimmer.tsx +64 -0
- package/dist/runtime-packages/ui/src/components/ai-elements/sources.tsx +77 -0
- package/dist/runtime-packages/ui/src/components/ai-elements/suggestion.tsx +53 -0
- package/dist/runtime-packages/ui/src/components/ai-elements/task.tsx +87 -0
- package/dist/runtime-packages/ui/src/components/ai-elements/tool.tsx +165 -0
- package/dist/runtime-packages/ui/src/components/ai-elements/toolbar.tsx +16 -0
- package/dist/runtime-packages/ui/src/components/ai-elements/web-preview.tsx +263 -0
- package/dist/runtime-packages/ui/src/components/alert-dialog.tsx +157 -0
- package/dist/runtime-packages/ui/src/components/alert.tsx +66 -0
- package/dist/runtime-packages/ui/src/components/avatar.tsx +53 -0
- package/dist/runtime-packages/ui/src/components/badge.tsx +46 -0
- package/dist/runtime-packages/ui/src/components/button-group.tsx +83 -0
- package/dist/runtime-packages/ui/src/components/button.tsx +65 -0
- package/dist/runtime-packages/ui/src/components/card.tsx +92 -0
- package/dist/runtime-packages/ui/src/components/carousel.tsx +241 -0
- package/dist/runtime-packages/ui/src/components/collapsible.tsx +33 -0
- package/dist/runtime-packages/ui/src/components/command.tsx +184 -0
- package/dist/runtime-packages/ui/src/components/context-menu.tsx +252 -0
- package/dist/runtime-packages/ui/src/components/counter.tsx +11 -0
- package/dist/runtime-packages/ui/src/components/dialog.tsx +146 -0
- package/dist/runtime-packages/ui/src/components/drawer.tsx +132 -0
- package/dist/runtime-packages/ui/src/components/dropdown-menu.tsx +257 -0
- package/dist/runtime-packages/ui/src/components/form.tsx +168 -0
- package/dist/runtime-packages/ui/src/components/header.tsx +13 -0
- package/dist/runtime-packages/ui/src/components/hover-card.tsx +44 -0
- package/dist/runtime-packages/ui/src/components/input-group.tsx +178 -0
- package/dist/runtime-packages/ui/src/components/input.tsx +21 -0
- package/dist/runtime-packages/ui/src/components/item.tsx +193 -0
- package/dist/runtime-packages/ui/src/components/label.tsx +24 -0
- package/dist/runtime-packages/ui/src/components/navigation-menu.tsx +168 -0
- package/dist/runtime-packages/ui/src/components/popover.tsx +55 -0
- package/dist/runtime-packages/ui/src/components/progress.tsx +31 -0
- package/dist/runtime-packages/ui/src/components/radio-group.tsx +45 -0
- package/dist/runtime-packages/ui/src/components/resizable.tsx +56 -0
- package/dist/runtime-packages/ui/src/components/scroll-area.tsx +58 -0
- package/dist/runtime-packages/ui/src/components/select.tsx +187 -0
- package/dist/runtime-packages/ui/src/components/separator.tsx +28 -0
- package/dist/runtime-packages/ui/src/components/skeleton.tsx +13 -0
- package/dist/runtime-packages/ui/src/components/sonner.tsx +25 -0
- package/dist/runtime-packages/ui/src/components/spinner.tsx +16 -0
- package/dist/runtime-packages/ui/src/components/switch.tsx +31 -0
- package/dist/runtime-packages/ui/src/components/table.tsx +137 -0
- package/dist/runtime-packages/ui/src/components/tabs.tsx +74 -0
- package/dist/runtime-packages/ui/src/components/textarea.tsx +23 -0
- package/dist/runtime-packages/ui/src/components/theme-provider.tsx +73 -0
- package/dist/runtime-packages/ui/src/components/tooltip.tsx +61 -0
- package/dist/runtime-packages/ui/src/components/ui/file-upload.tsx +195 -0
- package/dist/runtime-packages/ui/src/hooks/.gitkeep +0 -0
- package/dist/runtime-packages/ui/src/index.ts +68 -0
- package/dist/runtime-packages/ui/src/lib/utils.ts +6 -0
- package/dist/runtime-packages/ui-host-runtime/src/actor-principal.ts +68 -0
- package/dist/runtime-packages/ui-host-runtime/src/components/host-controls.tsx +359 -0
- package/dist/runtime-packages/ui-host-runtime/src/components/host-feedback-toaster.tsx +274 -0
- package/dist/runtime-packages/ui-host-runtime/src/components/host-feedback.tsx +214 -0
- package/dist/runtime-packages/ui-host-runtime/src/components/host-session-metadata.tsx +135 -0
- package/dist/runtime-packages/ui-host-runtime/src/components/index.ts +5 -0
- package/dist/runtime-packages/ui-host-runtime/src/components/perf-overlay.tsx +194 -0
- package/dist/runtime-packages/ui-host-runtime/src/host-controls.tsx +1 -0
- package/dist/runtime-packages/ui-host-runtime/src/host-feedback.tsx +1 -0
- package/dist/runtime-packages/ui-host-runtime/src/host-session-transport.ts +162 -0
- package/dist/runtime-packages/ui-host-runtime/src/index.ts +2 -0
- package/dist/runtime-packages/ui-host-runtime/src/logger.ts +11 -0
- package/dist/runtime-packages/ui-host-runtime/src/perf.ts +253 -0
- package/dist/runtime-packages/ui-host-runtime/src/plugin-bridge.ts +195 -0
- package/dist/runtime-packages/ui-host-runtime/src/plugin-health-check.ts +138 -0
- package/dist/runtime-packages/ui-host-runtime/src/plugin-messages.ts +159 -0
- package/dist/runtime-packages/ui-host-runtime/src/plugin-session-gateway.ts +524 -0
- package/dist/runtime-packages/ui-host-runtime/src/runtime/index.ts +12 -0
- package/dist/runtime-packages/ui-host-runtime/src/screenshot/projection-to-snapshot.ts +122 -0
- package/dist/runtime-packages/ui-host-runtime/src/screenshot/static-store-api.ts +26 -0
- package/dist/runtime-packages/ui-host-runtime/src/session-ingress-controller.ts +477 -0
- package/dist/runtime-packages/ui-host-runtime/src/session-ingress.ts +209 -0
- package/dist/runtime-packages/ui-host-runtime/src/session-live-runtime.ts +112 -0
- package/dist/runtime-packages/ui-host-runtime/src/session-model.ts +318 -0
- package/dist/runtime-packages/ui-host-runtime/src/session-projection.ts +211 -0
- package/dist/runtime-packages/ui-host-runtime/src/session-state-reducer.ts +814 -0
- package/dist/runtime-packages/ui-host-runtime/src/sse-manager.ts +334 -0
- package/dist/runtime-packages/ui-host-runtime/src/unified-session-store.ts +180 -0
- package/dist/scaffold/assets/static/app/tsconfig.framework.json +3 -1
- package/dist/scaffold/assets/static/ui/tsconfig.framework.json +7 -19
- package/package.json +52 -17
- package/skills/dreamboard/SKILL.md +41 -75
- package/LICENSE +0 -89
- package/NOTICE +0 -1
- package/dist/chunk-BMYC6772.js.map +0 -1
- package/dist/chunk-EYYWGWTO.js.map +0 -1
- package/dist/chunk-MOVHYB6E.js.map +0 -1
- package/dist/scaffold/assets/static/ui/App.tsx +0 -22
- package/dist/src-CUL7EGGG.js +0 -634
- package/dist/src-CUL7EGGG.js.map +0 -1
- package/skills/dreamboard/references/authoring-lifecycle.md +0 -102
- package/skills/dreamboard/references/board-surfaces.md +0 -36
- package/skills/dreamboard/references/board-topology.md +0 -443
- package/skills/dreamboard/references/boards-and-topology.md +0 -100
- package/skills/dreamboard/references/card-actions.md +0 -107
- package/skills/dreamboard/references/cli.md +0 -120
- package/skills/dreamboard/references/core-concepts.md +0 -514
- package/skills/dreamboard/references/custom-renderers.md +0 -26
- package/skills/dreamboard/references/derived-values.md +0 -55
- package/skills/dreamboard/references/effects.md +0 -111
- package/skills/dreamboard/references/game-contract.md +0 -89
- package/skills/dreamboard/references/game-definition.md +0 -89
- package/skills/dreamboard/references/game-shell.md +0 -80
- package/skills/dreamboard/references/hand-surfaces.md +0 -33
- package/skills/dreamboard/references/index.md +0 -112
- package/skills/dreamboard/references/inputs-and-targets.md +0 -160
- package/skills/dreamboard/references/interactions.md +0 -158
- package/skills/dreamboard/references/manifest-fields.md +0 -137
- package/skills/dreamboard/references/manifest.md +0 -81
- package/skills/dreamboard/references/package-surfaces.md +0 -69
- package/skills/dreamboard/references/phases.md +0 -145
- package/skills/dreamboard/references/prompts-and-choices.md +0 -55
- package/skills/dreamboard/references/quickstart.md +0 -71
- package/skills/dreamboard/references/rule-authoring.md +0 -144
- package/skills/dreamboard/references/setup-bootstrap.md +0 -78
- package/skills/dreamboard/references/stages-and-zones.md +0 -59
- package/skills/dreamboard/references/static-views.md +0 -67
- package/skills/dreamboard/references/table-queries-and-ops.md +0 -103
- package/skills/dreamboard/references/testing-bases.md +0 -147
- package/skills/dreamboard/references/testing-generated-contracts.md +0 -142
- package/skills/dreamboard/references/testing-runtime-assertions.md +0 -124
- package/skills/dreamboard/references/testing-scenarios.md +0 -148
- package/skills/dreamboard/references/testing-ui-tests.md +0 -174
- package/skills/dreamboard/references/testing.md +0 -161
- package/skills/dreamboard/references/ui-architecture.md +0 -137
- package/skills/dreamboard/references/ui-components.md +0 -34
- package/skills/dreamboard/references/views.md +0 -72
- package/skills/dreamboard/references/workspace-layout.md +0 -136
- /package/dist/{dist-WJRJNFLI.js.map → dist-FRURQI7Q.js.map} +0 -0
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import {
|
|
2
|
+
defaultHostSessionTransport,
|
|
3
|
+
type HostSessionTransport,
|
|
4
|
+
} from "@dreamboard/ui-host-runtime/runtime";
|
|
5
|
+
|
|
6
|
+
type HostSessionSnapshot = Awaited<
|
|
7
|
+
ReturnType<HostSessionTransport["loadSessionSnapshot"]>
|
|
8
|
+
>;
|
|
9
|
+
|
|
10
|
+
type DevHostSessionSnapshot = HostSessionSnapshot & {
|
|
11
|
+
context: HostSessionSnapshot["context"] & { seed?: number | null };
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
async function requestSnapshot(
|
|
15
|
+
path: string,
|
|
16
|
+
init: RequestInit,
|
|
17
|
+
): Promise<DevHostSessionSnapshot> {
|
|
18
|
+
const response = await fetch(path, init);
|
|
19
|
+
const contentType = response.headers.get("content-type") ?? "";
|
|
20
|
+
const payload = contentType.includes("application/json")
|
|
21
|
+
? await response.json()
|
|
22
|
+
: await response.text();
|
|
23
|
+
if (!response.ok) {
|
|
24
|
+
throw payload;
|
|
25
|
+
}
|
|
26
|
+
return payload as DevHostSessionSnapshot;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function createDevHostSessionTransport(): HostSessionTransport {
|
|
30
|
+
return {
|
|
31
|
+
...defaultHostSessionTransport,
|
|
32
|
+
loadSessionSnapshot: async (input) =>
|
|
33
|
+
requestSnapshot(
|
|
34
|
+
input.requestedPlayerId?.trim()
|
|
35
|
+
? `/__dreamboard_dev/session/snapshot?playerId=${encodeURIComponent(input.requestedPlayerId.trim())}`
|
|
36
|
+
: "/__dreamboard_dev/session/snapshot",
|
|
37
|
+
{ method: "GET" },
|
|
38
|
+
),
|
|
39
|
+
startSession: async () =>
|
|
40
|
+
requestSnapshot("/__dreamboard_dev/session/start", {
|
|
41
|
+
method: "POST",
|
|
42
|
+
body: undefined,
|
|
43
|
+
headers: undefined,
|
|
44
|
+
}),
|
|
45
|
+
createDevSessionSnapshot: async (input) =>
|
|
46
|
+
requestSnapshot("/__dreamboard_dev/session/new", {
|
|
47
|
+
method: "POST",
|
|
48
|
+
body: JSON.stringify({ seed: input.seed }),
|
|
49
|
+
headers: { "content-type": "application/json" },
|
|
50
|
+
}),
|
|
51
|
+
};
|
|
52
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
export type ActiveSession = {
|
|
2
|
+
sessionId: string;
|
|
3
|
+
shortCode: string;
|
|
4
|
+
gameId: string;
|
|
5
|
+
seed: number | null;
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
export interface DevHostStorage {
|
|
9
|
+
loadSidebarOpen(): boolean;
|
|
10
|
+
persistSidebarOpen(open: boolean): void;
|
|
11
|
+
loadPreferredPlayerId(): string | null;
|
|
12
|
+
persistPreferredPlayerId(playerId: string | null): void;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const SIDEBAR_STORAGE_KEY = "dreamboard-dev-sidebar";
|
|
16
|
+
const PREFERRED_PLAYER_STORAGE_KEY = "dreamboard-dev-preferred-player";
|
|
17
|
+
|
|
18
|
+
export class SessionStorageDevHostStorage implements DevHostStorage {
|
|
19
|
+
constructor(private readonly storage: Storage) {}
|
|
20
|
+
|
|
21
|
+
loadSidebarOpen(): boolean {
|
|
22
|
+
try {
|
|
23
|
+
return this.storage.getItem(SIDEBAR_STORAGE_KEY) === "true";
|
|
24
|
+
} catch {
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
persistSidebarOpen(open: boolean): void {
|
|
30
|
+
try {
|
|
31
|
+
this.storage.setItem(SIDEBAR_STORAGE_KEY, String(open));
|
|
32
|
+
} catch {
|
|
33
|
+
// Ignore persistence failures in locked-down browser contexts.
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
loadPreferredPlayerId(): string | null {
|
|
38
|
+
try {
|
|
39
|
+
return this.storage.getItem(PREFERRED_PLAYER_STORAGE_KEY)?.trim() || null;
|
|
40
|
+
} catch {
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
persistPreferredPlayerId(playerId: string | null): void {
|
|
46
|
+
try {
|
|
47
|
+
if (playerId?.trim()) {
|
|
48
|
+
this.storage.setItem(PREFERRED_PLAYER_STORAGE_KEY, playerId.trim());
|
|
49
|
+
} else {
|
|
50
|
+
this.storage.removeItem(PREFERRED_PLAYER_STORAGE_KEY);
|
|
51
|
+
}
|
|
52
|
+
} catch {
|
|
53
|
+
// Ignore persistence failures in locked-down browser contexts.
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
@@ -0,0 +1,469 @@
|
|
|
1
|
+
import type { IncomingMessage, ServerResponse } from "node:http";
|
|
2
|
+
import consola from "consola";
|
|
3
|
+
import type { Plugin } from "vite";
|
|
4
|
+
import type { ResolvedConfig } from "../types.js";
|
|
5
|
+
import { createPersistedDevSession } from "../utils/dev-session.js";
|
|
6
|
+
import { exists, readJsonFile, writeJsonFile } from "../utils/fs.js";
|
|
7
|
+
import { resolveDevBearer } from "./dev-api-proxy-plugin.js";
|
|
8
|
+
import {
|
|
9
|
+
shouldRelayDevLog,
|
|
10
|
+
type DevDiagnosticsLevel,
|
|
11
|
+
type DevLogEnvelope,
|
|
12
|
+
} from "./dev-diagnostics.js";
|
|
13
|
+
import type { DreamboardDevRuntimeConfig } from "./dev-runtime-config.js";
|
|
14
|
+
import type { ActiveSession } from "./dev-host-storage.js";
|
|
15
|
+
|
|
16
|
+
export function createDevLogRelayPlugin(options: {
|
|
17
|
+
sessionFilePath: string;
|
|
18
|
+
runtimeConfig: DreamboardDevRuntimeConfig;
|
|
19
|
+
config: ResolvedConfig;
|
|
20
|
+
diagnosticsLevel: DevDiagnosticsLevel;
|
|
21
|
+
}): Plugin {
|
|
22
|
+
return {
|
|
23
|
+
name: "dreamboard-dev-log-relay",
|
|
24
|
+
configureServer(server) {
|
|
25
|
+
server.middlewares.use("/__dreamboard_dev/log", (req, res) => {
|
|
26
|
+
if (req.method !== "POST") {
|
|
27
|
+
res.statusCode = 405;
|
|
28
|
+
res.setHeader("content-type", "application/json");
|
|
29
|
+
res.end(JSON.stringify({ error: "Method not allowed" }));
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const chunks: Buffer[] = [];
|
|
34
|
+
req.on("data", (chunk) => {
|
|
35
|
+
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
|
36
|
+
});
|
|
37
|
+
req.on("end", () => {
|
|
38
|
+
try {
|
|
39
|
+
const body = Buffer.concat(chunks).toString("utf8");
|
|
40
|
+
const payload = JSON.parse(body) as Partial<DevLogEnvelope>;
|
|
41
|
+
const normalizedPayload = {
|
|
42
|
+
source: coerceLogSource(payload.source),
|
|
43
|
+
level: coerceLogLevel(payload.level),
|
|
44
|
+
message:
|
|
45
|
+
typeof payload.message === "string"
|
|
46
|
+
? payload.message
|
|
47
|
+
: "Missing dev log message",
|
|
48
|
+
} satisfies DevLogEnvelope;
|
|
49
|
+
if (
|
|
50
|
+
shouldRelayDevLog(options.diagnosticsLevel, normalizedPayload)
|
|
51
|
+
) {
|
|
52
|
+
relayDevLog(normalizedPayload);
|
|
53
|
+
}
|
|
54
|
+
res.statusCode = 204;
|
|
55
|
+
res.end();
|
|
56
|
+
} catch (error) {
|
|
57
|
+
consola.warn(
|
|
58
|
+
`[dev-host] Failed to decode browser log payload: ${formatUnknown(error)}`,
|
|
59
|
+
);
|
|
60
|
+
res.statusCode = 400;
|
|
61
|
+
res.setHeader("content-type", "application/json");
|
|
62
|
+
res.end(JSON.stringify({ error: "Invalid dev log payload" }));
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
server.middlewares.use(
|
|
68
|
+
"/__dreamboard_dev/session/snapshot",
|
|
69
|
+
createSnapshotSessionHandler({
|
|
70
|
+
sessionFilePath: options.sessionFilePath,
|
|
71
|
+
runtimeConfig: options.runtimeConfig,
|
|
72
|
+
config: options.config,
|
|
73
|
+
}),
|
|
74
|
+
);
|
|
75
|
+
server.middlewares.use(
|
|
76
|
+
"/__dreamboard_dev/session/new",
|
|
77
|
+
createNewSessionHandler({
|
|
78
|
+
sessionFilePath: options.sessionFilePath,
|
|
79
|
+
runtimeConfig: options.runtimeConfig,
|
|
80
|
+
config: options.config,
|
|
81
|
+
}),
|
|
82
|
+
);
|
|
83
|
+
server.middlewares.use(
|
|
84
|
+
"/__dreamboard_dev/session/start",
|
|
85
|
+
createStartSessionHandler({
|
|
86
|
+
sessionFilePath: options.sessionFilePath,
|
|
87
|
+
runtimeConfig: options.runtimeConfig,
|
|
88
|
+
config: options.config,
|
|
89
|
+
}),
|
|
90
|
+
);
|
|
91
|
+
},
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export function createSnapshotSessionHandler(options: {
|
|
96
|
+
sessionFilePath: string;
|
|
97
|
+
runtimeConfig: DreamboardDevRuntimeConfig;
|
|
98
|
+
config: ResolvedConfig;
|
|
99
|
+
}): (req: IncomingMessage, res: ServerResponse) => void {
|
|
100
|
+
return (req, res) => {
|
|
101
|
+
void handleSnapshotSessionRequest(req, res, options);
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export function createNewSessionHandler(options: {
|
|
106
|
+
sessionFilePath: string;
|
|
107
|
+
runtimeConfig: DreamboardDevRuntimeConfig;
|
|
108
|
+
config: ResolvedConfig;
|
|
109
|
+
}): (req: IncomingMessage, res: ServerResponse) => void {
|
|
110
|
+
return (req, res) => {
|
|
111
|
+
void handleNewSessionRequest(req, res, options);
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export function createStartSessionHandler(options: {
|
|
116
|
+
sessionFilePath: string;
|
|
117
|
+
runtimeConfig: DreamboardDevRuntimeConfig;
|
|
118
|
+
config: ResolvedConfig;
|
|
119
|
+
}): (req: IncomingMessage, res: ServerResponse) => void {
|
|
120
|
+
return (req, res) => {
|
|
121
|
+
void handleStartSessionRequest(req, res, options);
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
async function handleSnapshotSessionRequest(
|
|
126
|
+
req: IncomingMessage,
|
|
127
|
+
res: ServerResponse,
|
|
128
|
+
options: {
|
|
129
|
+
sessionFilePath: string;
|
|
130
|
+
runtimeConfig: DreamboardDevRuntimeConfig;
|
|
131
|
+
config: ResolvedConfig;
|
|
132
|
+
},
|
|
133
|
+
): Promise<void> {
|
|
134
|
+
if (req.method !== "GET") {
|
|
135
|
+
respondJson(res, 405, { error: "Method not allowed" });
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
try {
|
|
140
|
+
const session = await loadCurrentSession(options);
|
|
141
|
+
const requestedPlayerId = extractQueryParam(req, "playerId");
|
|
142
|
+
let snapshot = await fetchBackendJson(
|
|
143
|
+
options.config,
|
|
144
|
+
appendQuery(`/api/sessions/${session.sessionId}/snapshot`, {
|
|
145
|
+
playerId: requestedPlayerId,
|
|
146
|
+
}),
|
|
147
|
+
);
|
|
148
|
+
if (
|
|
149
|
+
options.runtimeConfig.autoStartGame &&
|
|
150
|
+
isStartableLobbySnapshot(snapshot)
|
|
151
|
+
) {
|
|
152
|
+
snapshot = await fetchBackendJson(
|
|
153
|
+
options.config,
|
|
154
|
+
`/api/sessions/${session.sessionId}/start`,
|
|
155
|
+
{ method: "POST" },
|
|
156
|
+
);
|
|
157
|
+
if (requestedPlayerId) {
|
|
158
|
+
snapshot = await fetchBackendJson(
|
|
159
|
+
options.config,
|
|
160
|
+
appendQuery(`/api/sessions/${session.sessionId}/snapshot`, {
|
|
161
|
+
playerId: requestedPlayerId,
|
|
162
|
+
}),
|
|
163
|
+
);
|
|
164
|
+
}
|
|
165
|
+
await persistSessionId(options.sessionFilePath, session.sessionId);
|
|
166
|
+
}
|
|
167
|
+
respondJson(res, 200, attachLocalSeed(snapshot, session.seed ?? null));
|
|
168
|
+
} catch (error) {
|
|
169
|
+
respondJson(res, statusForError(error), { error: formatUnknown(error) });
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
async function handleNewSessionRequest(
|
|
174
|
+
req: IncomingMessage,
|
|
175
|
+
res: ServerResponse,
|
|
176
|
+
options: {
|
|
177
|
+
sessionFilePath: string;
|
|
178
|
+
runtimeConfig: DreamboardDevRuntimeConfig;
|
|
179
|
+
config: ResolvedConfig;
|
|
180
|
+
},
|
|
181
|
+
): Promise<void> {
|
|
182
|
+
if (req.method !== "POST") {
|
|
183
|
+
respondJson(res, 405, { error: "Method not allowed" });
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
try {
|
|
188
|
+
const body = await readJsonBody(req);
|
|
189
|
+
const seed = Number.parseInt(String(body.seed ?? ""), 10);
|
|
190
|
+
if (!Number.isSafeInteger(seed)) {
|
|
191
|
+
throw new Error("Seed must be a safe integer.");
|
|
192
|
+
}
|
|
193
|
+
const created = await fetchBackendJson(
|
|
194
|
+
options.config,
|
|
195
|
+
`/api/games/${options.runtimeConfig.gameId}/sessions`,
|
|
196
|
+
{
|
|
197
|
+
method: "POST",
|
|
198
|
+
body: {
|
|
199
|
+
compiledResultId: options.runtimeConfig.compiledResultId,
|
|
200
|
+
seed,
|
|
201
|
+
playerCount: options.runtimeConfig.playerCount,
|
|
202
|
+
autoAssignSeats: true,
|
|
203
|
+
setupProfileId: options.runtimeConfig.setupProfileId ?? undefined,
|
|
204
|
+
},
|
|
205
|
+
},
|
|
206
|
+
);
|
|
207
|
+
const sessionId = requireString(
|
|
208
|
+
(created as { sessionId?: unknown }).sessionId,
|
|
209
|
+
"sessionId",
|
|
210
|
+
);
|
|
211
|
+
let snapshot = await fetchBackendJson(
|
|
212
|
+
options.config,
|
|
213
|
+
`/api/sessions/${sessionId}/snapshot`,
|
|
214
|
+
);
|
|
215
|
+
if (
|
|
216
|
+
options.runtimeConfig.autoStartGame &&
|
|
217
|
+
isStartableLobbySnapshot(snapshot)
|
|
218
|
+
) {
|
|
219
|
+
snapshot = await fetchBackendJson(
|
|
220
|
+
options.config,
|
|
221
|
+
`/api/sessions/${sessionId}/start`,
|
|
222
|
+
{
|
|
223
|
+
method: "POST",
|
|
224
|
+
},
|
|
225
|
+
);
|
|
226
|
+
}
|
|
227
|
+
await persistSessionId(options.sessionFilePath, sessionId);
|
|
228
|
+
respondJson(res, 200, attachLocalSeed(snapshot, seed));
|
|
229
|
+
} catch (error) {
|
|
230
|
+
respondJson(res, statusForError(error), { error: formatUnknown(error) });
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
async function handleStartSessionRequest(
|
|
235
|
+
req: IncomingMessage,
|
|
236
|
+
res: ServerResponse,
|
|
237
|
+
options: {
|
|
238
|
+
sessionFilePath: string;
|
|
239
|
+
runtimeConfig: DreamboardDevRuntimeConfig;
|
|
240
|
+
config: ResolvedConfig;
|
|
241
|
+
},
|
|
242
|
+
): Promise<void> {
|
|
243
|
+
if (req.method !== "POST") {
|
|
244
|
+
respondJson(res, 405, { error: "Method not allowed" });
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
try {
|
|
249
|
+
const session = await loadCurrentSession(options);
|
|
250
|
+
const snapshot = await fetchBackendJson(
|
|
251
|
+
options.config,
|
|
252
|
+
`/api/sessions/${session.sessionId}/start`,
|
|
253
|
+
{ method: "POST" },
|
|
254
|
+
);
|
|
255
|
+
await persistSessionId(options.sessionFilePath, session.sessionId);
|
|
256
|
+
respondJson(res, 200, attachLocalSeed(snapshot, session.seed ?? null));
|
|
257
|
+
} catch (error) {
|
|
258
|
+
respondJson(res, statusForError(error), { error: formatUnknown(error) });
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
async function readJsonBody(
|
|
263
|
+
req: IncomingMessage,
|
|
264
|
+
): Promise<Record<string, unknown>> {
|
|
265
|
+
const chunks: Buffer[] = [];
|
|
266
|
+
await new Promise<void>((resolve, reject) => {
|
|
267
|
+
req.on("data", (chunk) => {
|
|
268
|
+
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
|
269
|
+
});
|
|
270
|
+
req.on("end", () => resolve());
|
|
271
|
+
req.on("error", reject);
|
|
272
|
+
});
|
|
273
|
+
const text = Buffer.concat(chunks).toString("utf8");
|
|
274
|
+
return text ? (JSON.parse(text) as Record<string, unknown>) : {};
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
function requireString(value: unknown, field: string): string {
|
|
278
|
+
if (typeof value !== "string" || value.length === 0) {
|
|
279
|
+
throw new Error(`Missing required field: ${field}`);
|
|
280
|
+
}
|
|
281
|
+
return value;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
function extractQueryParam(req: IncomingMessage, name: string): string | null {
|
|
285
|
+
const rawUrl = req.url ?? "";
|
|
286
|
+
const value = new URL(rawUrl, "http://dreamboard.local").searchParams.get(
|
|
287
|
+
name,
|
|
288
|
+
);
|
|
289
|
+
return value?.trim() || null;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
function appendQuery(
|
|
293
|
+
path: string,
|
|
294
|
+
query: Record<string, string | null | undefined>,
|
|
295
|
+
): string {
|
|
296
|
+
const params = new URLSearchParams();
|
|
297
|
+
for (const [key, value] of Object.entries(query)) {
|
|
298
|
+
if (value) {
|
|
299
|
+
params.set(key, value);
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
const serialized = params.toString();
|
|
303
|
+
return serialized ? `${path}?${serialized}` : path;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
async function loadCurrentSession(options: {
|
|
307
|
+
sessionFilePath: string;
|
|
308
|
+
runtimeConfig: DreamboardDevRuntimeConfig;
|
|
309
|
+
}): Promise<ActiveSession> {
|
|
310
|
+
if (!(await exists(options.sessionFilePath))) {
|
|
311
|
+
return options.runtimeConfig.initialSession;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
const payload = await readJsonFile<unknown>(options.sessionFilePath);
|
|
315
|
+
const session = parsePersistedSessionPointer(payload, options.runtimeConfig);
|
|
316
|
+
if (!session) {
|
|
317
|
+
throw new Error("Session file did not contain a valid session pointer.");
|
|
318
|
+
}
|
|
319
|
+
return session;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
async function persistSessionId(
|
|
323
|
+
sessionFilePath: string,
|
|
324
|
+
sessionId: string,
|
|
325
|
+
): Promise<void> {
|
|
326
|
+
await writeJsonFile(
|
|
327
|
+
sessionFilePath,
|
|
328
|
+
createPersistedDevSession({ sessionId }),
|
|
329
|
+
);
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
async function fetchBackendJson(
|
|
333
|
+
config: ResolvedConfig,
|
|
334
|
+
path: string,
|
|
335
|
+
options: {
|
|
336
|
+
method?: "GET" | "POST";
|
|
337
|
+
body?: Record<string, unknown>;
|
|
338
|
+
} = {},
|
|
339
|
+
): Promise<unknown> {
|
|
340
|
+
const bearer = await resolveDevBearer(config);
|
|
341
|
+
if (bearer.kind === "permanent_invalid") {
|
|
342
|
+
throw new HttpError(401, bearer.message);
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
const response = await fetch(`${config.apiBaseUrl}${path}`, {
|
|
346
|
+
method: options.method ?? "GET",
|
|
347
|
+
headers: {
|
|
348
|
+
...(bearer.token ? { authorization: `Bearer ${bearer.token}` } : {}),
|
|
349
|
+
...(options.body ? { "content-type": "application/json" } : {}),
|
|
350
|
+
},
|
|
351
|
+
body: options.body ? JSON.stringify(options.body) : undefined,
|
|
352
|
+
});
|
|
353
|
+
if (!response.ok) {
|
|
354
|
+
const text = await response.text().catch(() => "");
|
|
355
|
+
throw new HttpError(
|
|
356
|
+
response.status,
|
|
357
|
+
text || `Backend request failed with ${response.status}`,
|
|
358
|
+
);
|
|
359
|
+
}
|
|
360
|
+
return response.json();
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
function attachLocalSeed(snapshot: unknown, seed: number | null): unknown {
|
|
364
|
+
if (!snapshot || typeof snapshot !== "object") {
|
|
365
|
+
return snapshot;
|
|
366
|
+
}
|
|
367
|
+
const context = (snapshot as { context?: unknown }).context;
|
|
368
|
+
if (!context || typeof context !== "object") {
|
|
369
|
+
return snapshot;
|
|
370
|
+
}
|
|
371
|
+
return {
|
|
372
|
+
...(snapshot as Record<string, unknown>),
|
|
373
|
+
context: {
|
|
374
|
+
...(context as Record<string, unknown>),
|
|
375
|
+
seed,
|
|
376
|
+
},
|
|
377
|
+
};
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
function isStartableLobbySnapshot(snapshot: unknown): boolean {
|
|
381
|
+
if (!snapshot || typeof snapshot !== "object") {
|
|
382
|
+
return false;
|
|
383
|
+
}
|
|
384
|
+
const context = (snapshot as { context?: { phase?: unknown } }).context;
|
|
385
|
+
const lobby = (snapshot as { lobby?: { canStart?: unknown } }).lobby;
|
|
386
|
+
return context?.phase === "lobby" && lobby?.canStart === true;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
function respondJson(
|
|
390
|
+
res: ServerResponse,
|
|
391
|
+
statusCode: number,
|
|
392
|
+
payload: unknown,
|
|
393
|
+
): void {
|
|
394
|
+
res.statusCode = statusCode;
|
|
395
|
+
res.setHeader("content-type", "application/json");
|
|
396
|
+
res.end(JSON.stringify(payload));
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
function statusForError(error: unknown): number {
|
|
400
|
+
return error instanceof HttpError ? error.statusCode : 500;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
class HttpError extends Error {
|
|
404
|
+
constructor(
|
|
405
|
+
readonly statusCode: number,
|
|
406
|
+
message: string,
|
|
407
|
+
) {
|
|
408
|
+
super(message);
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
function parsePersistedSessionPointer(
|
|
413
|
+
value: unknown,
|
|
414
|
+
runtimeConfig: DreamboardDevRuntimeConfig,
|
|
415
|
+
): DreamboardDevRuntimeConfig["initialSession"] | null {
|
|
416
|
+
if (!value || typeof value !== "object") {
|
|
417
|
+
return null;
|
|
418
|
+
}
|
|
419
|
+
const sessionId = (value as { sessionId?: unknown }).sessionId;
|
|
420
|
+
if (typeof sessionId !== "string" || sessionId.length === 0) {
|
|
421
|
+
return null;
|
|
422
|
+
}
|
|
423
|
+
return {
|
|
424
|
+
sessionId,
|
|
425
|
+
shortCode: "Unknown",
|
|
426
|
+
gameId: runtimeConfig.gameId,
|
|
427
|
+
seed: null,
|
|
428
|
+
};
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
function relayDevLog(payload: DevLogEnvelope): void {
|
|
432
|
+
const formatted = `[dev:${payload.source}] ${payload.message}`;
|
|
433
|
+
switch (payload.level) {
|
|
434
|
+
case "error":
|
|
435
|
+
consola.error(formatted);
|
|
436
|
+
break;
|
|
437
|
+
case "warn":
|
|
438
|
+
consola.warn(formatted);
|
|
439
|
+
break;
|
|
440
|
+
case "info":
|
|
441
|
+
consola.info(formatted);
|
|
442
|
+
break;
|
|
443
|
+
default:
|
|
444
|
+
consola.log(formatted);
|
|
445
|
+
break;
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
function coerceLogSource(value: unknown): DevLogEnvelope["source"] {
|
|
450
|
+
return value === "host" || value === "plugin" || value === "sse"
|
|
451
|
+
? value
|
|
452
|
+
: "host";
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
function coerceLogLevel(value: unknown): DevLogEnvelope["level"] {
|
|
456
|
+
return value === "warn" ||
|
|
457
|
+
value === "error" ||
|
|
458
|
+
value === "info" ||
|
|
459
|
+
value === "log"
|
|
460
|
+
? value
|
|
461
|
+
: "log";
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
function formatUnknown(value: unknown): string {
|
|
465
|
+
if (value instanceof Error) {
|
|
466
|
+
return value.stack ?? value.message;
|
|
467
|
+
}
|
|
468
|
+
return typeof value === "string" ? value : JSON.stringify(value);
|
|
469
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { ActiveSession } from "./dev-host-storage.js";
|
|
2
|
+
|
|
3
|
+
export interface DreamboardDevRuntimeConfig {
|
|
4
|
+
apiBaseUrl: string;
|
|
5
|
+
userId: string | null;
|
|
6
|
+
gameId: string;
|
|
7
|
+
compiledResultId: string;
|
|
8
|
+
setupProfileId: string | null;
|
|
9
|
+
playerCount: number;
|
|
10
|
+
debug: boolean;
|
|
11
|
+
slug: string;
|
|
12
|
+
autoStartGame: boolean;
|
|
13
|
+
initialSession: ActiveSession;
|
|
14
|
+
}
|