npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@browser-ui/react-native

v0.2.0

Published

Native browser stream UI for agent-browser sessions.

Downloads

25

Readme

@browser-ui/react-native

React Native and Expo primitives for displaying an agent-browser-compatible JPEG WebSocket stream. The package is intentionally transport-thin: it renders frames, reports lifecycle events, and can forward input. It does not create browser sessions, store credentials, or grant control.

Install

pnpm add @browser-ui/core @browser-ui/react-native

react and react-native are peer dependencies. No native module or config plugin is required.

Inline stream

import { AgentBrowserView } from "@browser-ui/react-native";

export function BrowserPreview({ streamUrl }: { streamUrl: string }) {
  return (
    <AgentBrowserView
      streamUrl={streamUrl}
      style={{ aspectRatio: 16 / 10 }}
      onStatusChange={(status, error) => {
        console.log(status, error?.message);
      }}
      onUrlChange={(url) => console.log("remote page", url)}
    />
  );
}

The view uses Image with resizeMode="contain". Touches in letterboxed space are ignored rather than being clamped onto the remote page. Input is off by default.

Set interactive and pass host-projected access only after your application has acquired control of the session:

<AgentBrowserView
  access={session.access}
  streamUrl={streamUrl}
  interactive
/>

Touch taps become mouse clicks. Touch drags become coalesced wheel input. Mouse and trackpad pointer events are forwarded as mouse input on platforms that expose React Native pointer events.

Stream controller

Use the hook when the host needs to own the connection or render custom chrome:

const stream = useAgentBrowserStream({
  streamUrl,
  protocols: ["browser-stream", shortLivedTicket],
  reconnect: { maxAttempts: 6 },
});

stream.send({
  type: "input_mouse",
  eventType: "mouseMoved",
  x: 240,
  y: 180,
  button: "none",
  clickCount: 0,
  modifiers: 0,
});

Only the newest frame received in a native render interval is committed. Reconnects use bounded exponential backoff, and the retry budget resets only after a stable connection. The socket and pending reconnects are closed while the app is in the background. Call reconnect() to explicitly reset an exhausted retry budget.

Browser sheet

BrowserSheet is a host-controlled Modal. It uses React Native core Animated, PanResponder, Pressable, and SafeAreaView; there is no sheet dependency.

<BrowserSheet
  visible={browserOpen}
  onRequestClose={() => setBrowserOpen(false)}
  title="Research browser"
  displayUrl={pageUrl ?? "Connecting"}
  status={status}
>
  <AgentBrowserView
    streamUrl={browserOpen ? streamUrl : null}
    interactive={hasControlLease}
    onStatusChange={setStatus}
    onUrlChange={setPageUrl}
    style={{ flex: 1 }}
  />
</BrowserSheet>

The host owns visible; backdrop taps, the close button, Android back, and a downward swipe call onRequestClose. displayUrl is separate from streamUrl so browser chrome never accidentally exposes a gateway ticket.

Reachable authenticated WSS

A stream URL must be reachable from the device. localhost, 127.0.0.1, and ::1 refer to the phone, simulator, or emulator, not a development server elsewhere on your network. Use a routable gateway with a certificate trusted by the device:

wss://browser-gateway.example.com/v1/streams/session-id

Recommended gateway behavior:

  • Authenticate the app before creating a browser session.
  • Mint a short-lived, audience-bound, single-use stream ticket when a cookie is not available.
  • Validate the ticket during the WebSocket upgrade, then proxy the agent-browser protocol.
  • Expire the stream promptly when the session or user authorization ends.
  • Rate-limit upgrades and input independently.

Do not put a durable secret in EXPO_PUBLIC_BROWSER_STREAM_URL. Expo public environment variables are embedded in the application bundle. Standard WebSocket clients do not provide a portable custom Authorization header API; prefer a secure same-origin cookie or negotiated WebSocket subprotocol. Use a short-lived single-use URL ticket only when deployment constraints require it, and keep it out of logs and relay events.

Exclusive control leases

interactive is a presentation switch, not an authorization boundary. For pair browsing, enforce a lease on the gateway:

  • Allow any authorized viewer to receive frames.
  • Grant at most one controller a short-TTL lease for a browser session.
  • Accept input only from the socket that owns the current lease.
  • Renew with heartbeats and revoke on disconnect, background timeout, user action, or agent takeover.
  • Surface lease ownership in the host app before setting interactive.

This prevents two clients, or a human and agent, from issuing conflicting input. Hiding controls in React Native does not.

Expo demo

The workspace demo reads EXPO_PUBLIC_BROWSER_STREAM_URL:

EXPO_PUBLIC_BROWSER_STREAM_URL='wss://gateway.example.com/stream?ticket=...' \
  pnpm --filter @browser-ui/expo-demo start

It rejects missing, non-WSS, and loopback configuration with an explicit unavailable state.