openvibers/openvibe.games Commits

menu card: build and serve it with openvibe-sdk/menu (#310)

OP OpenVibers committed 6 days ago · 06e4f0db5d · 6 files, +36 −52

* menu card: build and serve it with openvibe-sdk/menu

Games' 'New worlds' card and route were hand-rolled against
frame.menu-card@1. menuCard() builds the same rows (plain text, six items,
own-origin and openvibe.* links) and menuRoute() answers
/.well-known/openvibe-menu.json with the same 60 s browser cache and CORS
*. A failed build now answers [] (never a 500), as the SDK route specifies;
the in-memory last-good-card cache stays. Pin openvibe-sdk v0.97.0.

* Pin openvibe-sdk v0.97.1 everywhere

The menu handler types accept plain node:http messages under
exactOptionalPropertyTypes. apps/server and packages/persistence both pin
the SDK, and keeping them on different versions gave the server one sql
tag and the persistence layer another; both now use v0.97.1 so a query
built there is the same Sql the server's db understands.

Changes

ddiff --git a/STATUS.json b/STATUS.json
iindex 7d68499..dd6db96 100644
---- a/STATUS.json
++++ b/STATUS.json
@@@ -6,7 +6,7 @@
"runtime": "node 22, TypeScript (tsx), pnpm workspace; Babylon.js (client) + deterministic Rapier physics; PostgreSQL 18 via openvibe-sdk/db (pg, or embedded PGlite in development); Valkey optional",
"port": 8000,
"domain": "openvibe.games, play.openvibe.games",
-- "contracts": "openvibe-contracts v0.320.0, openvibe-sdk v0.94.0, openvibe-shared v4.133.0, openvibe-publishing v1.4.0 (apps/server/package.json)",
++ "contracts": "openvibe-contracts v0.320.0, openvibe-sdk v0.97.1, openvibe-shared v4.133.0, openvibe-publishing v1.4.0 (apps/server/package.json)",
"capabilities": [
"games.mod.manage",
"games.mod.read",
ddiff --git a/apps/server/package.json b/apps/server/package.json
iindex 6ca2589..12e245a 100644
---- a/apps/server/package.json
++++ b/apps/server/package.json
@@@ -23,7 +23,7 @@
"iovalkey": "^0.4.0",
"openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.320.0",
"openvibe-publishing": "https://codeload.github.com/OpenVibers/OpenVibe.Publishing/tar.gz/refs/tags/v1.4.0",
-- "openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.94.0",
++ "openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.97.1",
"openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v4.133.0",
"pg": "^8.23.0",
"quickjs-emscripten": "0.32.0",
ddiff --git a/apps/server/src/net/menuCard.test.ts b/apps/server/src/net/menuCard.test.ts
iindex b536d35..9e6e971 100644
---- a/apps/server/src/net/menuCard.test.ts
++++ b/apps/server/src/net/menuCard.test.ts
@@@ -84,7 +84,7 @@ describe('the menu network card (frame.menu-card@1)', () => {
expect(card.title.length).toBeLessThanOrEqual(60)
for (const item of card.items) {
expect(item.title.length).toBeLessThanOrEqual(80)
-- expect(item.meta.length).toBeLessThanOrEqual(120)
++ expect(item.meta?.length ?? 0).toBeLessThanOrEqual(120)
expect(item.url.startsWith('https://openvibe.games/')).toBe(true)
}
// The menu's contract now allows an item image, but a creator world has no cover of its own: no picture is
@@@ -131,7 +131,7 @@ describe('the menu network card (frame.menu-card@1)', () => {
expect(store.calls(), 'the second request is served from the cache').toBe(1)
})
-- it('answers 503 when the store fails and no good card is cached, and keeps the last good one after', async () => {
++ it('answers [] when the store fails and no good card is cached, and keeps the last good one after', async () => {
let fail = false
let calls = 0
const store = {
@@@ -146,7 +146,9 @@ describe('the menu network card (frame.menu-card@1)', () => {
fail = true
handler(req('/.well-known/openvibe-menu.json'), bad.res)
await bad.ended
-- expect(bad.status()).toBe(503)
++ expect(bad.status()).toBe(200)
++ expect(JSON.parse(bad.body())).toEqual([])
++ expect(bad.headers['cache-control']).toBe('no-store')
fail = false
const good = fakeRes()
ddiff --git a/apps/server/src/net/menuCard.ts b/apps/server/src/net/menuCard.ts
iindex d946081..f990b7e 100644
---- a/apps/server/src/net/menuCard.ts
++++ b/apps/server/src/net/menuCard.ts
@@@ -1,5 +1,7 @@
import type { IncomingMessage, ServerResponse } from 'node:http'
import type { CreatorWorld, CreatorWorldStore } from '@openvibe/persistence'
++import { MENU_PATH, menuCard, menuRoute } from 'openvibe-sdk/menu'
++import type { MenuCard } from 'openvibe-sdk/menu'
/**
* The menu's network card for OpenVibe.Games (frame.menu-card@1): GET /.well-known/openvibe-menu.json.
@@@ -28,7 +30,7 @@ export function plain(value: unknown, max: number): string {
}
/** The card itself, newest published world first (the store's order). */
--export function cardFrom(worlds: CreatorWorld[], at = Date.now()) {
++export function cardFrom(worlds: CreatorWorld[], at = Date.now()): MenuCard {
const items = (Array.isArray(worlds) ? worlds : [])
.filter((w) => w && w.id && w.status === 'published')
.slice(0, 6)
@@@ -37,16 +39,17 @@ export function cardFrom(worlds: CreatorWorld[], at = Date.now()) {
meta: `by ${plain(w.ownerName, 110)}`,
url: `${BASE}/worlds/${encodeURIComponent(w.id)}`,
}))
-- return {
++ return menuCard({
id: CARD_ID,
site: SITE,
title: 'New worlds',
kind: 'rows',
++ origin: BASE,
items,
more: { label: 'All worlds', url: `${BASE}/worlds` },
-- updated_at: new Date(at).toISOString(),
-- ttl_s: CARD_TTL_S,
-- }
++ ttlSeconds: CARD_TTL_S,
++ updatedAt: at,
++ })
}
export interface MenuCardOptions {
@@@ -56,15 +59,15 @@ export interface MenuCardOptions {
/**
* The route. Returns true when it answered the request (the platform handle stops there), false for any other
-- * path. A database failure keeps the last good card; before one exists it answers 503 and Network keeps the
-- * site's previous card for its 30-minute window.
++ * path. A database failure keeps the last good card; before one exists openvibe-sdk/menu answers [] (never a
++ * 500), and Network keeps the site's previous card for its 30-minute window.
*/
export function createMenuCardHandler({ store, now = Date.now }: MenuCardOptions) {
-- let cached: ReturnType<typeof cardFrom> | null = null
++ let cached: MenuCard | null = null
let cachedAt = 0
-- let pending: Promise<ReturnType<typeof cardFrom>> | null = null
++ let pending: Promise<MenuCard> | null = null
-- async function card(): Promise<ReturnType<typeof cardFrom>> {
++ async function card(): Promise<MenuCard> {
const at = now()
if (cached && at - cachedAt < CACHE_MS) return cached
if (pending) return pending
@@@ -75,39 +78,18 @@ export function createMenuCardHandler({ store, now = Date.now }: MenuCardOptions
} catch (err) {
if (!cached) throw err
}
-- return cached as ReturnType<typeof cardFrom>
++ return cached as MenuCard
})().finally(() => {
pending = null
})
return pending
}
++ const route = menuRoute(async () => [await card()], { maxAgeSeconds: 60 })
return (req: IncomingMessage, res: ServerResponse): boolean => {
const path = (req.url ?? '/').split('?')[0] ?? '/'
-- if (path !== '/.well-known/openvibe-menu.json') return false
-- if (req.method !== 'GET' && req.method !== 'HEAD') {
-- res.writeHead(405, { 'content-type': 'application/json', allow: 'GET, HEAD' })
-- res.end('{"error":"method_not_allowed"}')
-- return true
-- }
-- void card()
-- .then((body) => {
-- res.writeHead(200, {
-- 'content-type': 'application/json; charset=utf-8',
-- 'cache-control': 'public, max-age=60',
-- 'access-control-allow-origin': '*',
-- 'cross-origin-resource-policy': 'cross-origin',
-- })
-- res.end(req.method === 'HEAD' ? undefined : JSON.stringify([body]))
-- })
-- .catch(() => {
-- res.writeHead(503, {
-- 'content-type': 'application/json',
-- 'cache-control': 'no-store',
-- 'access-control-allow-origin': '*',
-- })
-- res.end('{"error":"menu_unavailable"}')
-- })
++ if (path !== MENU_PATH) return false
++ void route(req, res)
return true
}
}
ddiff --git a/packages/persistence/package.json b/packages/persistence/package.json
iindex b5a1613..5e50770 100644
---- a/packages/persistence/package.json
++++ b/packages/persistence/package.json
@@@ -15,6 +15,6 @@
"@openvibe/gameplay": "workspace:*",
"@openvibe/protocol": "workspace:*",
"@openvibe/shared": "workspace:*",
-- "openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.94.0"
++ "openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.97.1"
}
}
ddiff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
iindex 806d4ff..6c9d280 100644
---- a/pnpm-lock.yaml
++++ b/pnpm-lock.yaml
@@@ -128,10 +128,10 @@ importers:
version: https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.320.0
openvibe-publishing:
specifier: https://codeload.github.com/OpenVibers/OpenVibe.Publishing/tar.gz/refs/tags/v1.4.0
-- version: https://codeload.github.com/OpenVibers/OpenVibe.Publishing/tar.gz/refs/tags/v1.4.0(openvibe-sdk@https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.94.0(@electric-sql/[email protected])([email protected])([email protected]))(openvibe-shared@https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v4.133.0)
++ version: https://codeload.github.com/OpenVibers/OpenVibe.Publishing/tar.gz/refs/tags/v1.4.0(openvibe-sdk@https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.97.1(@electric-sql/[email protected])([email protected])([email protected]))(openvibe-shared@https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v4.133.0)
openvibe-sdk:
-- specifier: https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.94.0
-- version: https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.94.0(@electric-sql/[email protected])([email protected])([email protected])
++ specifier: https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.97.1
++ version: https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.97.1(@electric-sql/[email protected])([email protected])([email protected])
openvibe-shared:
specifier: https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v4.133.0
version: https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v4.133.0
@@@ -198,8 +198,8 @@ importers:
specifier: workspace:*
version: link:../shared
openvibe-sdk:
-- specifier: https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.94.0
-- version: https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.94.0(@electric-sql/[email protected])([email protected])([email protected])
++ specifier: https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.97.1
++ version: https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.97.1(@electric-sql/[email protected])([email protected])([email protected])
packages/physics:
dependencies:
@@@ -1432,9 +1432,9 @@ packages:
openvibe-shared:
optional: true
-- openvibe-sdk@https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.94.0:
-- resolution: {tarball: https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.94.0}
-- version: 0.94.0
++ openvibe-sdk@https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.97.1:
++ resolution: {tarball: https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.97.1}
++ version: 0.97.1
engines: {node: '>=20'}
hasBin: true
peerDependencies:
@@@ -2935,13 +2935,13 @@ snapshots:
ajv: 8.20.0
ajv-formats: 3.0.1([email protected])
-- openvibe-publishing@https://codeload.github.com/OpenVibers/OpenVibe.Publishing/tar.gz/refs/tags/v1.4.0(openvibe-sdk@https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.94.0(@electric-sql/[email protected])([email protected])([email protected]))(openvibe-shared@https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v4.133.0):
++ openvibe-publishing@https://codeload.github.com/OpenVibers/OpenVibe.Publishing/tar.gz/refs/tags/v1.4.0(openvibe-sdk@https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.97.1(@electric-sql/[email protected])([email protected])([email protected]))(openvibe-shared@https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v4.133.0):
dependencies:
-- openvibe-sdk: https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.94.0(@electric-sql/[email protected])([email protected])([email protected])
++ openvibe-sdk: https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.97.1(@electric-sql/[email protected])([email protected])([email protected])
optionalDependencies:
openvibe-shared: https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v4.133.0
-- openvibe-sdk@https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.94.0(@electric-sql/[email protected])([email protected])([email protected]):
++ openvibe-sdk@https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.97.1(@electric-sql/[email protected])([email protected])([email protected]):
optionalDependencies:
'@electric-sql/pglite': 0.5.8
iovalkey: 0.4.0