Skip to content
NewiPhone Duo, live in your browser. Try it →

Reference JavaScript SDK

JavaScript Device SDK Reference

Drive an iOS, Android, Apple TV, Android TV or Roku session from code: start sessions, send taps and gestures, and react to stream events with the JS SDK.

The VibeView SDK is a JavaScript/TypeScript library for driving a live iOS or Android simulator session programmatically from your own application code — starting sessions, sending taps and gestures, and reacting to stream events.

If you just want to drop a live device into a web page (your marketing site, docs, or a demo page) with no application code of your own, use Embedding instead — it only requires pasting an iframe snippet.

Overview

The SDK handles the full lifecycle of a simulator session: creating it, connecting to the video stream, capturing user interactions, and cleaning up resources when done. It supports two usage patterns:

  • React component — A <VibeView /> wrapper for React applications (available via the @scriptx-com/vibeview-sdk/react subpath).
  • Vanilla JS client — The VibeViewClient class for any JavaScript environment.

Both approaches use the same underlying client and support the same configuration options.

Installation

The SDK is distributed as @scriptx-com/vibeview-sdk, a private package. It is not openly downloadable — access is granted to your organization, and support sends you the one-time setup for your package manager along with it.

Contact support@vibeview.io to request access.

The SDK requires Node.js 14+ and works in modern browsers with WebCodecs or Media Source Extensions support (Chrome 94+, Edge 94+, Safari 16.4+).

React Usage

Import the component from the @scriptx-com/vibeview-sdk/react subpath:

import { VibeView } from '@scriptx-com/vibeview-sdk/react';

function SimulatorDemo() {
  return (
    <VibeView
      deviceType="ios"
      token="your-auth-token"
      autoStart={true}
      style={{ width: 375, height: 812 }}
    />
  );
}

React is an optional peer dependency. The SDK works without React installed — the core @scriptx-com/vibeview-sdk import has no React dependency.

Vanilla JS Usage

Use the VibeViewClient class directly when working outside React:

import { VibeViewClient } from '@scriptx-com/vibeview-sdk';

const client = new VibeViewClient('#simulator-container', {
  apiUrl: 'https://vibeview.io',
  deviceType: 'ios',
  token: 'your-auth-token',
  autoStart: true,
});

await client.ready();

client.on('session:started', (session) => {
  console.log('Session started:', session.sessionId);
});

The first argument is a CSS selector string or an HTMLElement reference where the simulator will be rendered.

There is also a convenience helper that creates the client and waits for it to be ready in one call:

import { vibeview } from '@scriptx-com/vibeview-sdk';

const client = await vibeview('#container', {
  deviceType: 'android',
  autoStart: true,
});

Configuration

Pass a configuration object when creating a client. All properties are optional.

PropertyTypeDefaultDescription
apiUrlstringhttps://vibeview.ioBackend API URL.
wsUrlstringwss://vibeview.ioWebSocket URL for streaming.
tokenstring—Bearer token for authentication.
sessionIdstring—Connect to an existing session instead of creating a new one.
deviceType'ios' | 'android' | 'roku'—Device platform to launch. For TV, combine with deviceCategory: 'tv' (Apple TV = 'ios' + 'tv'; Android TV = 'android' + 'tv'). 'roku' streams a Roku device.
deviceCategory'phone' | 'tablet' | 'tv'omitted = any handheldDevice form factor. Omit to allocate any handheld (any phone or tablet); pass 'tv' for Apple TV / Android TV sessions, or 'phone'/'tablet' to narrow.
deviceIdstring—Request a specific device by ID.
autoStartbooleanfalseStart the session immediately on initialization.
scalenumber | 'auto''auto'Device scale factor (10—100), or 'auto' to fit the container.
screenOnlybooleanfalseShow only the screen without device chrome.
deviceColor'black' | 'white''black'Device chrome color.
orientation'portrait' | 'landscape''portrait'Initial device orientation.
displayRotation0 | 90 | 180 | 2700Visual rotation (degrees clockwise) the host applies to the canvas. The SDK inverse-transforms taps so they still land on the correct device pixels. Useful when displaying a landscape-locked app upright inside a portrait canvas.
readOnlybooleanfalseDisable user interactions (view-only mode).
decoder'webcodecs' | 'mse''webcodecs'Video decoder. WebCodecs has lower latency; MSE is more broadly compatible.
enableAdaptiveQualitybooleantrueAutomatically adjust bitrate/FPS quality tiers to match network conditions.
debugbooleanfalseEnable debug logging.
classNamestring—Custom CSS class for the container element.
stylePartial<CSSStyleDeclaration>—Inline styles for the container element.

Canvas Options

The canvas property accepts an object for fine-tuning rendering:

PropertyTypeDefaultDescription
acceleratedbooleantrueUse hardware acceleration.
imageSmoothingEnabledbooleantrueEnable image smoothing when scaling.

Events

The client emits events you can listen to with client.on():

client.on('session:status', (status) => {
  /* pending, starting, active, stopped, failed */
});
client.on('session:started', (session) => {
  /* session info with sessionId, deviceType, etc. On iPhone and iPad
     sessions, session.displayMetrics gives the device's screen size in
     points, its corner radii and the frame style the player uses. On a
     foldable it also carries both screens (panels.cover, panels.inner) and
     posture_ranges: which posture each hinge angle reads as on that device. */
});
client.on('session:failed', (error) => {
  /* Error object */
});
client.on('session:ended', (info) => {
  /* session stopped; info?.reasonCode says why (e.g. 'inactivity_timeout', 'stopped' when it was stopped from another tab, the CLI or the API, 'stopped_by_admin' when VibeView support ended it, 'worker_lost' when the device stopped responding, 'device_crashed' when the device stopped unexpectedly, 'device_turned_off' or 'device_restarted' when someone turned the device off or restarted it from its own menu, or 'device_storage_limit' when the device ran out of its storage allowance). Treat an unknown code as a plain end. */
});
client.on('timeout_warning', (secondsRemaining, reason) => {
  /* the session is about to end. reason is 'inactivity' (unused for the
     idle timeout), 'session_length_limit', 'usage_credit_exhausted',
     'overage_disabled' or 'device_storage_limit'; it may be missing, which
     means inactivity. It repeats
     with an updated secondsRemaining while the warning stands */
});
client.on('session:timeout-cleared', (reason) => {
  /* a timeout_warning was withdrawn. reason names which one: missing or
     'inactivity' when the session was used again, or 'usage_credit_exhausted' /
     'overage_disabled' when enough usage credit arrived to cancel that cutoff,
     or 'device_storage_limit' when files were deleted from the device in time.
     Any other pending warning still stands. A 'session_length_limit' warning
     is never withdrawn */
});
client.on('stream:started', () => {
  /* video frames arriving */
});
client.on('stream:stopped', () => {
  /* video stream ended */
});
client.on('stream:frame', (frameCount) => {
  /* a video frame was received (for custom rendering) */
});
client.on('stream:dimensions', ({ width, height }) => {
  /* video dimensions changed, e.g. after rotation, or when an Android
     foldable switches between its cover and inner screens */
});
client.on('connection:state', (state) => {
  /* connecting, connected, disconnected, reconnecting, error */
});
client.on('connection:failed', () => {
  /* reconnection gave up after all retry attempts */
});
client.on('error', (error) => {
  /* general error */
});
client.on('interaction', (action) => {
  /* user tap, swipe, etc. */
});
client.on('interaction:enhanced', (action) => {
  /* recorded action enriched with element context */
});
client.on('device:orientation', (orientation) => {
  /* 'portrait' | 'portrait_upside_down' | 'landscape_left' | 'landscape_right' — iOS only */
});
client.on('device:posture', ({ posture, angle, display, waypoint }) => {
  /* 'closed' | 'partial' | 'open', the hinge angle in degrees, and the
     resulting display size — foldable devices only, fires on every fold
     or unfold regardless of what triggered it (this client, another
     client on the same session, the AI agent, or the `posture=` launch
     param). `waypoint` is true when the device switched screens partway
     through a fold that is still moving; the fold's final report follows */
});
client.on('device:posture-error', ({ message }) => {
  /* fires when the device could not reach the requested posture; carries a
     `message` you can show */
});
client.on('deep-link:refused', (message, reason) => {
  /* openDeepLink did not open the link. `message` explains why and can be
     shown as-is; `reason` is 'refused' (the link was not allowed) or
     'failed' (the device could not open it) */
});
client.on('device:settings', ({ settings }) => {
  /* a setDeviceSettings / setAppearance change was applied on the device */
});
client.on('device:settings-error', ({ message, settings }) => {
  /* the device did not apply it; `message` can be shown as-is and
     `settings` is the change it answers */
});

The React component takes the two timeout events as props: onTimeoutWarning(secondsRemaining, reason) and onTimeoutCleared(reason).

Device Control

The client exposes programmatic device control alongside direct user interaction. All methods require an active session.

await client.tap(160, 320);
await client.swipe(160, 600, 160, 200, 300);
await client.typeText('hello@example.com');
MethodDescription
tap(x, y)Tap at coordinates (pixels in the streamed video).
swipe(x1, y1, x2, y2, duration?)Swipe from one point to another; duration in milliseconds (default 500).
typeText(text)Type a full string as a single command.
type(text)Legacy variant: sends the string character by character.
pressKey(keycode)Press a hardware key (Android/TV), e.g. 'BACK', 'HOME', 'DPAD_UP', 'DPAD_CENTER'.
pressButton(button)Press an iOS hardware button, e.g. 'HOME', 'LOCK'.
rotate(degrees?)Rotate the device; 90, 180, or 270 (default 90). On the iPhone Duo it works in any posture and turns the device clockwise by degrees (270 turns it back a quarter); an Android foldable rotates like a phone, in any posture. On a foldable, device:posture reports how the device is now held as deviceOrientation.
setLocation(lat, lon)Set the simulated GPS location.
setAppearance(mode)Switch the device between 'light' and 'dark' appearance.
setDeviceSettings(settings)Change several device settings at once, e.g. { mode: 'dark', textSize: 9, reduceMotion: true }. Only the settings you pass change; check session.deviceCapabilities for what the device supports. Resolves once the request is sent — listen for 'device:settings' (applied) or 'device:settings-error' (not applied). When several changes arrive faster than the device applies them, it ends on the latest value of each setting.
setContentSize(size)Set the system text size: 'extra-small', 'small', 'medium', 'large', or 'extra-large'.
setIncreaseContrast(enabled)Turn the system’s higher-contrast rendering on or off (iOS only).
setStatusBar(preset)'clean' shows the App Store screenshot status bar (9:41, full battery, full signal); 'default' restores the device’s own (iOS phones and tablets only).
setAnimations(enabled)Turn the device’s system animations and transitions on or off (Android only). Sessions start with them off.
setPosture({ posture? , angle? })Fold or unfold a foldable device. Pass a preset posture ('closed', 'partial', 'open'), an exact angle in degrees (0–180), or both — angle wins when both are given. No-op with an error on a non-foldable device. The posture a device reports for an angle follows its own device-defined ranges, which session.displayMetrics.posture_ranges lists (see Foldable devices). Fire-and-forget, like the other setters above; listen for 'device:posture' on success or 'device:posture-error' if the device could not get there.
setHingeAngle(angle)Move a foldable device’s hinge toward angle (0–180 degrees) while the user drags a slider. The hinge follows each angle as it arrives, so you can call this as often as every animation frame. Nothing is acknowledged and no event fires for it: when the drag ends, call setPosture({ angle }) once with the final angle. That call settles the device and reports 'device:posture' or 'device:posture-error'. While the drag is in progress, 'device:posture' can still fire if the device switches screens. An angle outside 0–180, or one that is not a finite number, throws a RangeError. The call is ignored before connect, on a read-only client, and on a non-foldable device.
openDeepLink(url)Open a link the app under test registers a URL scheme for, e.g. 'atlas://home'. An app must be installed; links it does not handle, and links that would leave it, are refused. Listen for 'deep-link:refused' to learn why.
screenshot(options?)Capture the current frame; returns a data-URL string. Options: format ('png' default, 'jpeg', 'webp'), quality (0–1, default 0.92) and frame ('rendered' default: upright, turned as the viewer sees a rotated device; 'source': the stream’s own frame).
installApp(options)Install an app into the session. Pass file (a File/Blob) or url (downloaded first), plus optional launchAfterInstall (default false).

Custom presentation

If you want to present the stream yourself — for example painting it onto a 3D model — the client hands you its live picture and takes touches back.

MethodDescription
getRenderSurface()The live element the device picture is drawn into, ready to use as a texture or copy source. null before the session connects and after destroy(). It stays the client’s to manage — read from it, don’t detach or resize it.
injectPointer(kind, x, y, options?)Drive a touch as though the user had pointed at that surface: 'down', then any number of 'move's, then 'up' (or 'cancel' to abandon the gesture, which lifts at the last position and ignores x/y). x and y are pixels within the surface, measured as it is displayed — after any displayRotation. If you draw the picture yourself and never rotate the element, pass { frame: 'source' } so the position is taken in the picture’s own orientation instead. Ignored before connect, after destroy(), on a read-only client, and on TV sessions.

TV devices

On TV sessions (Apple TV / Android TV), interaction is focus-based — the platform walks the d-pad focus to the target element instead of tapping coordinates:

MethodDescription
tapFocusedTv(locator)Move d-pad focus to the element matching the locator and press SELECT to activate it.
focusElementTv(locator)Move d-pad focus to the element without activating it.

Both take a locator object with any of accessibilityId, resourceId, text, and rect ({ x, y, width, height }). Both are fire-and-forget: they resolve when the command is sent, not when the walk completes.

Live keyboard

For live typing from a physical keyboard (as opposed to programmatic typeText), the client provides buffer-aware keystroke methods. Keystrokes are injected to the device immediately; while recording, consecutive characters are aggregated into a single type_text action instead of one action per keypress:

MethodDescription
liveType(char)Inject a single printable character. While recording, it accumulates in a pending buffer and fires a recording:buffer event so UIs can show in-flight text.
liveKey(keycode)Inject 'RETURN' or 'BACKSPACE'. RETURN flushes the pending buffer as one type_text action, then records a discrete key press; BACKSPACE pops the last pending character (or records a discrete key press if the buffer is empty).
liveKeyCombo(key, modifiers)iPhone/iPad on iOS 18 or later: press an editing or caret-movement key combination, for example liveKeyCombo('V', ['meta']) or liveKeyCombo('ARROW_LEFT', ['shift', 'alt']). Keys: A, C, V, X, Z, ARROW_LEFT/RIGHT/UP/DOWN, TAB, ESCAPE, BACKSPACE, FORWARD_DELETE; modifiers: meta, shift, alt. Combinations outside this editing set are refused by the device. Not recorded, and not sent while recording.
flushKeyboardBuffer()Commit any pending buffered text as a single aggregated type_text action. Idempotent — an empty buffer is a no-op. Called automatically when another action interrupts typing and when recording stops.

Enhanced Recording

Enhanced recording captures user interactions together with the UI element under each interaction (accessibility ID, text, bounds). The resulting action list is what powers the Recorder’s Generate with AI on the Devices page — the AI turns a manual session into a repeatable test — and replay in Smart runs.

await client.startEnhancedRecording();

// ... user interacts with the device ...

const actions = await client.finalizeEnhancedRecording();
console.log(client.exportEnhancedRecording()); // formatted JSON for debugging
MethodDescription
startEnhancedRecording()Begin recording with element context. Async — it pre-warms the element lookup so the first tap is captured accurately; disable your record button until it resolves.
stopEnhancedRecording()Stop recording and return the captured EnhancedRecordedAction[].
finalizeEnhancedRecording(settleMs?)Stop recording, wait for the screen to settle (default 300 ms), capture a final end-state screenshot, and return the actions. Prefer this over stopEnhancedRecording() when the recording will be used for AI test generation or visual comparison.
exportEnhancedRecording()Return the current recording as a formatted JSON string — handy for debugging or sharing what was recorded.

To let users delete a step from a recording (e.g. in a review UI), use the standalone removeActionAt(actions, index) export — it removes the action at index (including the whole gesture, when the action is part of one) and returns a new array, so it works with React state or imported recordings.

Stream Stats

Inspect stream health and negotiated quality:

MethodDescription
getStreamStats()Current decoder statistics: { fps, bitrateKbps, framesDecoded, framesDropped, decodeErrors, decoderState, decoderType }, or null before the stream starts. bitrateKbps covers the last second and is null when there is nothing in that second to measure. Works with both WebCodecs and MSE decoders.
getPreferredCodec()The video codec in use. Always 'h264'.
getQualityTier()The name of the current adaptive-quality tier.

Cleanup

Call destroy() when you are done with a client instance:

client.destroy();

The destroy() method is idempotent — it checks an internal this.destroyed flag and returns immediately if the client has already been destroyed. This means it is safe to call destroy() multiple times, for example in both a cleanup handler and a component unmount.

Internally, destroy() closes the WebSocket connection, stops video decoding, removes all event listeners, and clears the container element.

Browser Compatibility

Check whether the current browser supports the SDK before rendering:

import { isSupported, getCompatibilityInfo } from '@scriptx-com/vibeview-sdk';

if (!isSupported()) {
  const info = getCompatibilityInfo();
  console.warn(info.recommendation);
}

The SDK requires WebCodecs (or MSE as a fallback), WebSocket, and Canvas support.

Backward Compatibility

The SDK maintains strict backward compatibility. Published exports are never removed or renamed. If a type or function needs to change, a new export is added alongside the existing one. This ensures that applications built against earlier versions of the SDK continue to work after upgrades.

Tips

  • Use autoStart: true for demos where you want the simulator to appear immediately.
  • Use readOnly: true for presentation or documentation contexts where viewers should not interact with the device.
  • Use screenOnly: true to remove device chrome and show just the simulator screen, which is useful for tight layouts.
  • Set decoder: 'mse' if you need to support older browsers that lack WebCodecs.
  • The sessionId option lets you connect multiple clients to the same running session, which is useful for shared viewing scenarios.
  • After changing SDK source types locally, rebuild with cd sdk && yarn build before consuming the updated package.

Last updated 15 Oct 2026 Something wrong on this page? Tell us

Search the docs