Skip to main content
Web SDK

HappyOyster Web SDK Integration Guide

This document is intended for frontend / full-stack developers and explains how to integrate the HappyOyster Web SDK reliably in production across its adventure, directing, and acting modes.

This document is intended for frontend and full-stack developers. It explains how to integrate the SDK reliably in production. For individual APIs, see the API Reference.

0. Minimal Integration Example

If your backend has already returned a QwenCloud temporary API-key token and a Travel ticket, the frontend SDK flow looks like this:
import { HappyOysterEngine, isSdkError } from "@happy-oyster/js-sdk";

const engine = new HappyOysterEngine({
  APIHost: "open-platform.example.com",
  model: "happyoyster-1.0-adventure", // Required; Adventure model shown as an example
  logLevel: "warn",
});

engine.updateToken(token);

const videoElement = document.querySelector("video") as HTMLVideoElement;
const travel = engine.createTravel({
  ticket,
  videoElement,
  maxExperienceTimeSec: 90, // Optional, Adventure only: 60 / 90 / 120
});

const offStatus = travel.on("statusChanged", (status) => {
  // Use status to drive loading states, disabled buttons, and player UI.
});

const offError = travel.onError((error) => {
  // Usually log, show a toast, and refresh the token if it has expired.
  if (isSdkError(error)) console.error(error.code, error.message);
});

try {
  await travel.start();
  // Enable controls or Directing input with travel.can(action) after the session is running.
} finally {
  offStatus();
  offError();
  await travel.end();
}
This snippet covers only the SDK side. Creating the world, waiting until it is ready, requesting the ticket, and requesting the QwenCloud temporary API-key token should all happen on, or through, your application backend.

1. End-to-End Flow Overview

A complete experience mainly involves the following participants.

1.1 Participants

Participant

Responsibility

Application frontend

UI, <video>, session orchestration, and SDK calls

Application backend

Stores the primary API Key, calls Open Platform world APIs on behalf of the client, and issues temporary QwenCloud credentials

HappyOyster OpenAPI

Exposes the APIs required by the HappyOyster world model.

HappyOyster SDK

The SDK used by the application frontend. It primarily manages Travel sessions and video stream access on the client.

happy-oyster-overview.png

1.2 Experience Flow

Stage

Actor

Action

Description

① Create a world

Application backend → HappyOyster OpenAPI

POST /openapi/v1/worlds

Returns encryptedWorldId, status, and firstFrame

② Wait until ready

Application backend → HappyOyster OpenAPI

GET /openapi/v1/worlds/build-status

Poll until status === ready (skip this step if the world is already ready when created)

③ Obtain a Travel credential

Application backend → HappyOyster OpenAPI

POST /openapi/v1/worlds/get-travel-credential

Returns a one-time ticket for enter-travel

④ Request a temporary API-key token

Application backend → HappyOyster OpenAPI

POST /api/v1/tokens?expire_in_seconds=1800

Returns a temporary API-key token. Do not set an expiry that is too short, or the token may expire before the experience completes and require frequent updates.

⑤ Deliver the temporary API-key token and Travel credential

Application frontend → application backend → application frontend

Request a temporary API key and Travel credential

After the frontend receives token and ticket, it starts the SDK flow

⑥ Enter the experience

Application frontend → HappyOyster SDK

See "SDK startup subflow" below

new HappyOysterEngine/updateTokencreateTravelstart()

1.3 SDK Interaction Subflow (Expanded Stage ⑥)

1.3.1 Start a Travel

travel-start-flow.png
Key call-order requirements

Step

API

Description

1

new HappyOysterEngine()

Reuse an Engine for the same APIHost + model

2

updateToken()

Complete this before start()

3

createTravel()

videoElement is required

4

travel.on(...)

Register listeners before start()

5

await travel.start()

Enters running after a successful start

1.3.2 Play in Adventure mode

adventure-command-flow.png

1.3.3 Play in Directing / Acting mode

adventure-control-flow.png
Use this table as the quick mental model for Adventure, Directing, and Acting:

Mode

Main APIs

Typical use

Watch out for

Adventure (1)

sendCommand()

Keyboard, joystick, and button-based real-time control

Send only after running; maxExperienceTimeSec applies only to this mode

Directing (2)

sendInstruct(), pause(), resume(), rewind()

Natural-language Directing and playback control

Pause, resume, and rewind depend on session capability; check travel.can(action) first

Acting (3)

sendInstruct(), pause(), resume()

Performance-oriented experiences and aspect-ratio configuration

Pause and resume depend on session capability; rewind() is unavailable and scriptlist cannot use sendInstruct()

1.4 Application Backend Practices (Summary)

On the backend side, keep the following rules simple and strict:
  • Keep the primary API Key on the application backend only. The browser should receive only a short-lived token (Demo: POST /server-api/temp-api-key).
  • Request Travel credentials through the backend. Authorize the world and user before issuing ticket; do not hard-code or cache it long-term in the frontend.
  • Do not issue a credential until the world is ready. Otherwise, enter-travel may fail or provide an incomplete experience.
  • Point the application backend and the SDK to the same Open Platform environment. The backend may use a full OpenAPI URL; the SDK's APIHost must be only the bare host, such as open-platform.example.com.

2. Core SDK Concepts

In the flow described in §1, the SDK is responsible only for "enter Travel → play → control → end." There are two core objects:

Object

Purpose

HappyOysterEngine

Configures APIHost and model, manages the QwenCloud temporary API-key token (updateToken), and creates Travel sessions

Travel

Represents one session: start connects to RTC; sendCommand, sendInstruct, and lifecycle methods control the session

Session states (statusChanged):
idle ──start()──► prepare ──video playable──► running ⇄ paused ──end()──► idle
Three constraints (explained later):
  • Each Engine may have only one active Travel at a time.
  • videoElement must be provided to createTravel.
  • The QwenCloud temporary API-key token and Travel ticket serve different purposes (see stages ④–⑤ in §1.2 and §3.2).

3. Initialization and Token Management

3.1 Engine Instantiation

Required
  • Configure APIHost as the Open Platform bare host, without https://, a scheme, or a path, such as open-platform.example.com.
  • Reuse a HappyOysterEngine for the same APIHost + model. Before switching the host or model, end the active Travel and use an Engine configured for the new target.
Recommended
  • Use logLevel: 'debug' in development and 'warn' or 'none' in production.
  • Create the Engine in the session orchestration layer instead of constructing a new one for every start.
Use this quick table for the common config fields:

Field

Required

Guidance

APIHost

Yes

Pass only the bare host, such as open-platform.example.com; do not include https:// or a path

model

Yes

Required, with no default; keep it aligned with the backend model used to create the world and issue its ticket. It is fixed for the Engine and is not inferred from mode or the ticket

token

No

You can pass it in the constructor or set it later with updateToken(token); refresh expired tokens with updateToken as well

logLevel

No

Use debug in development and warn or none in production

streamReadyTimeout

No

Increase it if network or rendering setup is slow; provide a retry path after timeout

Avoid
  • Repeatedly calling new HappyOysterEngine() during rendering or on a hot path.
  • Passing a full URL, such as one with https://, or calling SDK backend APIs after omitting APIHost.

3.2 Dual-Credential Model

Corresponding to stages ③–⑤ in §1.2:

Credential

Source

How to set it

Purpose

QwenCloud temporary API-key token

Short-lived key issued by the application backend

sdk.updateToken(token)

Authenticates SDK requests to Open Platform

Travel ticket

Travel credential requested by the application backend

createTravel({ ticket })

One-time credential for a Travel session

Required
  • Prepare both credentials before travel.start().
  • The SDK does not persist or automatically refresh the token.
Recommended
  • If the token expires or a heartbeat error is received through onError, request a new token through the application backend and call updateToken.
Avoid
  • Mixing the two credentials.
  • Exposing the primary API Key to the application frontend.

4. Travel Lifecycle

4.1 Create and Start

Required
  • Call createTravel({ ticket, videoElement }); ticket is the one-time Travel credential, and videoElement is provided at this point.
  • Register statusChanged and onError before await travel.start() so that errors and state changes are always observed.
Recommended
  • Show a connecting UI during prepare to prevent duplicate clicks. Use travelInfoReady to read session metadata early, and use firstFrameGenerated as a first-frame placeholder when available.
Avoid
  • Creating a Travel before the DOM element is ready.

4.2 While Running

  • Drive the UI from statusChanged: prepare means waiting for video, while running means interaction is available.
  • Observe the use cases and preconditions for pause, resume, and rewind (they depend on session capability; rewind also requires the session to be paused and the stream to have stopped).

4.3 End and Recreate

Required
  • End the session with await travel.end().
  • Ensure the previous Travel has ended before creating a new one.
  • Call travel.end() during cleanup when the page unloads or the route changes.
Recommended
  • After a failure, use "end → remove listeners → retry from stage ③ or ⑤ in §1.2."
  • Request a new Travel credential when retrying if the credential is one-time use.
Avoid
  • Calling createTravel a second time without ending the first Travel; this throws synchronously.
  • Leaving the page without cleanup, which leaks RTC resources.

5. Video Element and Browser Policies

Required
  • Set playsInline on <video> (required on iOS).
  • Understand how browser autoplay policies affect autoPlay and muted.
Recommended
  • During prepare, show the first-frame image with an overlay; hide it after the session enters running.
  • Configure streamReadyTimeout as needed (default: 15,000 ms), and provide a retry action after a timeout.
  • Reserve a fixed aspect ratio for the player container.
Avoid
  • Passing a video element reference before the element is attached to the DOM.
  • Leaving the UI interactive while the session is paused or the stream is disconnected.

6. Event Subscriptions and Error Handling

6.1 Event Subscriptions

Required
  • Events belong to Travel, not HappyOysterEngine.
  • Save the unsubscribe functions returned by travel.on(...) and travel.onError(...).
Recommended
  • Register and clean up listeners in one place. Remove old listeners before switching Travel sessions.

Event

Purpose

statusChanged

Display status, detect video readiness (running), and enable or disable buttons

travelInfoReady

Read encryptedTravelId, mode, first frame, experience duration, and Acting aspect ratio before RTC connects; travel.getInfo() returns the same data

firstFrameGenerated

When a non-empty first frame exists, it follows travelInfoReady; use it as a placeholder during prepare

error (via onError)

Logging, toast notifications, and triggering token refresh

6.2 Error Layers

  • Startup failures: travel.start() rejects (enter-travel / RTC / timeout).
  • Runtime errors: reported through travel.onError (RTC errors, heartbeat authentication failures, and so on).
  • Application backend and Open Platform errors in stages ①–④ of §1.2 are handled by the application layer and do not enter the SDK.
Avoid
  • Catching only start() without listening to onError.
  • Registering listeners repeatedly without unsubscribing.

7. Sending Instructions: Command and Instruct

7.1 sendCommand (Real-Time Control)

sendCommand() provides real-time control in Adventure mode (1). It sends commands through the RTC DataChannel and is available only while the session is running. The SDK sends at most one command approximately every 42 ms (about 24 FPS). If one cycle has elapsed since the previous send, it sends the new command immediately. If sendCommand() is called multiple times within the same cycle, it sends only the complete command from the last call. translation, rotation, and interaction are independent control dimensions. Omitted fields are sent as None; the SDK does not merge fields across calls, periodically repeat the previous command, or automatically send None. One-off actions Call sendCommand() once for one-off actions such as jumping or attacking:
await travel.sendCommand({ interaction: "Jump" });
Continuous actions Keep calling sendCommand() while a movement or continuous view rotation control remains pressed. The frontend application does not need to throttle calls to 42 ms; the SDK coalesces high-frequency calls into an output rate of about 24 FPS. On release, explicitly send None to reset the control:
// Keep calling while the control is pressed
void travel.sendCommand({ translation: "Front" });

// Stop moving on release
void travel.sendCommand({ translation: "None" });
Avoid
  • Sending many commands during prepare.
  • Using sendCommand for natural-language Directing instructions.

7.2 sendInstruct (Directing Instructions)

Required
  • a Directing session (2) or an Acting session (3) that is not scriptlist; call await travel.sendInstruct({ content }).
Recommended
  • Verify that content is not empty before sending and prevent duplicate submissions.
  • Provide user feedback based on returned fields such as accepted.

8. Troubleshooting

Symptom

Common cause

What to do

SDK initialization fails with an argument error

APIHost was passed as a full URL, for example with https:// or a path

Pass only the bare host, such as open-platform.example.com

travel.start() fails because the ticket is invalid or already used

ticket is one-time use; it may be expired, reused, or issued before the world is ready

Request a new ticket from the backend; make sure the world is ready before entering

onError reports an auth error after the session has been running

The QwenCloud temporary API-key token expired

Ask your backend for a new token, then call engine.updateToken(newToken)

The player keeps loading or start() times out

Remote stream is not ready, network is slow, or streamReadyTimeout is too short

Provide a retry action; increase streamReadyTimeout if needed; check backend and RTC logs

A second createTravel() call fails because an active Travel already exists

The previous Travel was not ended

Call await travel.end() and clean up listeners when leaving, retrying, or switching sessions

Controls or instructions do nothing

The session is not running, or the wrong API is used for the current Adventure, Directing, or Acting mode

Drive button availability from statusChanged; call travel.can(action) before mode-sensitive actions

When debugging, log three things together: SDK ErrorCode, current TravelStatus, and backend request logs. That usually tells you whether the issue is credential-related, state-related, network-related, or rendering-related.