@urth/metatell-sdk - v0.1.0
    Preparing search index...

    Room Objects

    @urth/metatell-sdk/objects lets a plugin read, spawn, move, update, pin, and remove managed room objects (media and models) without touching A-Frame or NAF directly.

    Experimental. This API was added in 0.0.24 and requires the metatell host to install its room-object bridge. Without it, reads return empty results and writes reject with an ObjectOperationError whose code is "host_unavailable". Use waitUntilReady("objects") when you need to check support first.

    list() and get() are synchronous snapshots. Each returned object is a copy, so mutating it does not change the room.

    import { get, list } from "@urth/metatell-sdk/objects";

    const all = list(); // RoomObject[] ([] when the host is not ready)
    const one = get("object-id"); // RoomObject | undefined
    interface RoomObject {
    id: string;
    type: "image" | "video" | "audio" | "pdf" | "model" | "primitive" | "unknown";
    src?: string;
    pdfIndex?: number;
    transform: ObjectTransform;
    pinned: boolean;
    ownerId?: string;
    mine: boolean; // true when the current client owns the object
    }

    interface ObjectTransform {
    position: { x: number; y: number; z: number };
    quaternion: { x: number; y: number; z: number; w: number };
    scale: { x: number; y: number; z: number };
    }

    ObjectTransform always carries scale and has no Euler rotation, unlike the TransformData used by the avatar and camera streams.

    Each subscription returns an Unsubscribe function; call it on teardown.

    import {
    onAdded,
    onMoved,
    onRemoved,
    onUpdated,
    } from "@urth/metatell-sdk/objects";

    const offs = [
    onAdded((object) => console.log("added", object.id, object.type)),
    onUpdated((object) => console.log("updated", object.id, object.pinned)),
    onMoved(({ id, transform }) => console.log("moved", id, transform.position)),
    onRemoved(({ id }) => console.log("removed", id)),
    ];

    function teardown() {
    for (const off of offs) off();
    }
    • onAdded / onRemoved — an object enters or leaves the room.
    • onUpdated — a property other than the transform changes (for example src, pdfIndex, or pinned).
    • onMoved — the object's transform changes.

    spawn() resolves with the created RoomObject.

    import { spawn } from "@urth/metatell-sdk/objects";

    const image = await spawn({
    kind: "media",
    src: "https://example.com/poster.png",
    position: { x: 0, y: 1.5, z: -2 },
    pinned: true,
    });

    EntitySpec is a union discriminated by kind. All kinds accept the optional position, quaternion, scale, and pinned fields.

    kind Fields
    media src: string | File, optional contentType
    model src: string | File, optional lipSync: { sessionId }
    primitive shape: "box" | "sphere" | "cylinder" | "plane", optional color, size

    The current metatell host does not support primitive yet and rejects it with SpawnRejectedError.

    import { move, pin, remove, setProperty } from "@urth/metatell-sdk/objects";

    await move(image.id, { position: { x: 1, y: 1.5, z: -2 } });
    await setProperty(image.id, { src: "https://example.com/next.png" });
    await pin(image.id, false);
    await remove(image.id);
    • move(id, transform) accepts a Partial<ObjectTransform>; omitted parts are left unchanged.
    • setProperty(id, props) sets replicated properties: src, pdfIndex (PDF objects only), and lipSync.
    • pin(id, pinned) pins or unpins an object so it persists across sessions.

    Before move, setProperty, pin, or remove, the host checks the room's permissions and takes network ownership of the object. It rejects with PermissionDeniedError or NotOwnerError when either step fails.

    Writes resolve once the host confirms the operation. The resulting change is delivered separately through onUpdated / onMoved / onRemoved.

    Every write accepts an optional last argument:

    interface WriteOptions {
    signal?: AbortSignal; // abort the wait; rejects with signal.reason
    timeoutMs?: number; // default 15000
    }

    Aborting or timing out stops waiting for the result. It does not undo an operation the host has already applied.

    lipSync binds a VRM model's mouth (the aa expression) to a participant's voice. sessionId is that participant's session id, as returned by getSessionId() in /user or RemoteUser.sessionId in /users. The binding replicates to all clients.

    import { setProperty, spawn } from "@urth/metatell-sdk/objects";
    import { getSessionId } from "@urth/metatell-sdk/user";

    const sessionId = getSessionId();
    if (sessionId) {
    const avatar = await spawn({
    kind: "model",
    src: "https://example.com/guide.vrm",
    lipSync: { sessionId },
    });

    // later: remove the binding
    await setProperty(avatar.id, { lipSync: null });
    }

    Writes reject with error classes exported from @urth/metatell-sdk/objects, so instanceof checks work inside your plugin bundle:

    Error Meaning
    PermissionDeniedError The room's permissions do not allow the operation.
    ObjectNotFoundError No object with that id (error.id).
    NotOwnerError The host could not take ownership of the object (error.id).
    SpawnRejectedError The host rejected the spec or property (for example, an unsupported kind).
    ObjectOperationError Any other failure. error.code is "host_unavailable" when no host bridge is installed and "unknown" for timeouts and other failures.
    import {
    NotOwnerError,
    PermissionDeniedError,
    move,
    } from "@urth/metatell-sdk/objects";

    try {
    await move("object-id", { scale: { x: 2, y: 2, z: 2 } });
    } catch (e) {
    if (e instanceof PermissionDeniedError) {
    // the user lacks the required room permission
    } else if (e instanceof NotOwnerError) {
    // someone else controls the object
    }
    }
    • Triggers for firing Spoke triggers placed in the scene.
    • Runtime to check whether the objects domain is installed.
    • Events for the underlying objects:* events.