Skip to content

Latest commit

 

History

History
322 lines (258 loc) · 8.54 KB

File metadata and controls

322 lines (258 loc) · 8.54 KB

OpenSpatialWorld — API Documentation

This document describes the HTTP and WebSocket API exposed by the wow-backend service and how authors can interact with it (both server-side and from the browser client).

Summary

  • HTTP base: http://<host>:3000 (server listens on port 3000 by default).
  • WebSocket events endpoint: ws://<host>:3000/events.
  • Frontend client uses openapi-fetch (see packages/wow-frontend/src/api/client.ts).

HTTP Endpoints

  • GET /wow/world
    • Returns the current World object (see @wow/spec for shape).
    • Example (frontend): await fetchWorld() from packages/wow-frontend/src/api/world.ts.
    • cURL:
curl http://localhost:3000/wow/world
  • GET /wow/scene/node/{nodeId}
    • Returns a single Node by id.
    • Example (frontend): await fetchNode(nodeId) from packages/wow-frontend/src/api/node.ts.
    • cURL:
curl http://localhost:3000/wow/scene/node/0
  • POST /wow/scene/node/{nodeId}
    • Creates one or more child nodes under the given parent node id.
    • Request body: array of Node objects (without id — server assigns ids).
    • Response: created nodes (with assigned id and parent).
    • Example (frontend): createNode(parentId, node) calls this endpoint.
    • cURL:
curl -X POST http://localhost:3000/wow/scene/node/0 \
  -H "Content-Type: application/json" \
  -d '[{"label":"Lamp","children":[],"spatialAssetURI":"https://example.com/assets/lamp.glb"}]'
  • PUT /wow/scene/node/{nodeId}
    • Updates an existing node. Body should contain partial or full Node properties to be merged.
    • Response: updated Node.
    • cURL:
curl -X PUT http://localhost:3000/wow/scene/node/1 \
  -H "Content-Type: application/json" \
  -d '{"label":"Renamed Lamp"}'
  • DELETE /wow/scene/node/{nodeId}
    • Deletes the given node and its subtree (cannot delete root id 0).
    • cURL:
curl -X DELETE http://localhost:3000/wow/scene/node/1
  • GET /wow/scene/portal/{portalId}
    • Returns portal metadata.
    • cURL:
curl http://localhost:3000/wow/portal/1
  • GET /wow/view/{viewId}
    • Returns a View object.
    • cURL:
curl http://localhost:3000/wow/view/1
  • GET /wow/user/{userId}
    • Retrieve a user record.
    • cURL:
curl http://localhost:3000/wow/user/1
  • DELETE /wow/user/{userId}
    • Delete a user record.
    • cURL:
curl -X DELETE http://localhost:3000/wow/user/1
  • Root web app: GET / serves a minimal HTML app (see packages/wow-backend/src/controllers/app.ts).
    • cURL:
curl http://localhost:3000/

Schema examples

  • World
{
  "content": {
    "label": "OpenSpatialWorld",
    "age_restriction": 0,
    "license": "CC-BY-4.0",
    "cost": "free",
    "version": 1,
    "duration": 3600
  },
  "geoPose": {
    "position": { "lat": 47.6, "lan": -122.3, "h": 10 },
    "angles": { "yaw": 0, "pitch": 0, "roll": 0 }
  },
  "presence": {
    "avatar": "headset",
    "navigation": "teleport",
    "gravity": "earth"
  },
  "technology": {
    "webXR-immersive": "required",
    "runtime": "WebGL2"
  },
  "users": {
    "active_user_count": 1,
    "total_user_count": 1
  },
  "views": {
    "view_count": 3
  },
  "portals": {
    "portal_count": 2
  }
}
  • User
{
  "id": 1,
  "name": "Guest-1",
  "AvatarURI": "https://example.com/avatar.glb",
  "geoPose": {
    "position": { "lat": 0, "lan": 0, "h": 1.8 },
    "angles": { "yaw": 180, "pitch": 0, "roll": 0 }
  }
}
  • View
{
  "id": 5,
  "geoPose": {
    "position": { "lat": 47.6, "lan": -122.3, "h": 5 },
    "angles": { "yaw": 90, "pitch": 0, "roll": 0 }
  }
}
  • Portal
{
  "id": 2,
  "geoPose": {
    "position": { "lat": 47.6, "lan": -122.3, "h": 0 },
    "angles": { "yaw": 270, "pitch": 0, "roll": 0 }
  }
}
  • Node
{
  "id": 10,
  "label": "Tree",
  "names": ["Oak", "ForestObject"],
  "parent": 0,
  "children": [11, 12],
  "localTransform": [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 1],
  "spatialAssetURI": "https://example.com/assets/tree.glb",
  "appearanceURI": "https://example.com/appearance/tree.mat"
}
  • POST /wow/scene/node/{nodeId} request body example
[
  {
    "label": "Lamp",
    "children": [],
    "localTransform": [1,0,0,0,0,1,0,0,0,0,1,0,0,2,0,1],
    "spatialAssetURI": "https://example.com/assets/lamp.glb",
    "appearanceURI": "https://example.com/appearance/lamp.mat"
  }
]

WebSocket Events (/events)

  • The server accepts WebSocket connections at /events and registers each socket.

  • On connect, the server creates a transient User (Guest-X) and broadcasts a user_joined message.

  • On disconnect, the server broadcasts a user_left message.

  • Server also broadcasts node events when nodes are created/updated/deleted:

    • node_created — payload: { type: 'node_created', node: <Node> }
    • node_updated — payload: { type: 'node_updated', node: <Node> }
    • node_deleted — payload: { type: 'node_deleted', nodeId: <number> }
  • The backend sends JSON messages by calling broadcast('/events', message) (see packages/wow-backend/src/services/websocket.ts).

Browser-side helpers

  • API client: packages/wow-frontend/src/api/client.ts creates an openapi-fetch client with baseUrl: '/'.

    • Usage examples in packages/wow-frontend/src/api/node.ts and world.ts show how to call GET, POST, PUT, DELETE with path params and body.
  • 3D viewer: packages/wow-frontend/src/scene.ts renders the resolved node tree as an X3DOM scene. Each Node.localTransform becomes an X3D <MatrixTransform>; Node.spatialAssetURI becomes an <Inline> reference to that asset (glTF or X3D), and nodes without an asset URI render as a placeholder sphere.

  • Events: use connectEvents(onMessage, onStatusChange) in packages/wow-frontend/src/events.ts.

    • Example:
import { connectEvents } from './events';
connectEvents(
  (msg) => console.log('event:', msg),
  (online) => console.log('events online:', online)
);

Author Examples

  • Fetch world from browser:
import { fetchWorld } from './api/world';
const world = await fetchWorld();
console.log(world);
  • Create a node under parent 5:
import { createNode } from './api/node';
await createNode(5, { label: 'My Object', children: [] });
  • Update a node:
import { client } from './api/client';
const { data } = await client.PUT('/wow/scene/node/{nodeId}', {
  params: { path: { nodeId: 10 } },
  body: { label: 'Renamed' },
});
  • Listen for node events:
connectEvents((msg) => {
  const parsed = JSON.parse(msg);
  if (parsed.type === 'node_created') {
    // use parsed.node
  }
}, (online) => console.log('ws online', online));

Data persistence & startup

  • The server persists world state to packages/wow-backend/data/world_state.json using store() and restore() in packages/wow-backend/src/services/store.ts.
  • On startup the server restores state (if available) and seeds a root node with id 0.

Quickstart

  1. Install dependencies from the repository root:
npm install
  1. Build the workspace packages:
npm run build
  1. Start the backend server:
npm run start:backend
  1. Open the frontend in your browser:
  • Visit http://localhost:3000
  1. Development mode (optional):
npm run dev

This will run nodemon from the repo root, rebuilding on source changes and restarting the backend automatically.

Notes and tips for authors

  • Refer to the OpenAPI spec package @wow/spec (README, schema at packages/wow-spec/src/schema.yaml) for concrete schema definitions and types. The frontend client is typed against that spec, and the backend validates every request/response against it via express-openapi-validator.
  • Use the provided client helpers in packages/wow-frontend/src/api to avoid hand-constructing requests.
  • WebSocket messages are raw JSON strings; parse them and switch on type.
  • The server auto-assigns numeric ids for nodes and users.

Relevant files