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

@bambuser/react-native-commerce-sdk

v1.0.0-beta.1

Published

React Native wrapper for Bambuser Commerce SDK

Readme

@bambuser/react-native-commerce-sdk

React Native wrapper for the Bambuser Commerce SDK. Embed live video shopping experiences in your iOS and Android React Native apps with a single declarative component.

The package wraps Bambuser's native iOS and Android SDKs (versions 3.0.0 and 2.6.0 respectively) and exposes them through a React component plus an imperative ref-based API. No native code required in your app.

Feature Overview

| Feature | iOS | Android | |---|---|---| | Live video playback | ✅ | ✅ | | Play / pause / mute / unmute | ✅ | ✅ | | invoke / notify (call into player & respond to callbacks) | ✅ | ✅ | | Picture-in-Picture | ✅ | ✅ | | Player state events (onStatus, onProgress, onError) | ✅ | ✅ | | Custom events (onEvent) | ✅ | ✅ | | Thumbnail tap callback (onThumbnailTapped) | ✅ | – | | Safe-area edge control | ✅ | ✅ | | Analytics tracking (BambuserSDK.track) | ✅ | ✅ |

v1 scope: live videos only. Shoppable / on-demand video is not included in this release.

Requirements

  • React Native: >= 0.72
  • iOS: >= 15.6
  • Android: minSdkVersion >= 26

Installation

npm install @bambuser/react-native-commerce-sdk
# or
yarn add @bambuser/react-native-commerce-sdk

iOS

cd ios && pod install

The Bambuser Commerce SDK xcframework is vendored inside the package — no extra source/repo configuration required.

Android

The Android SDK is published to a Bambuser-hosted Maven repo. Add it to your project's android/settings.gradle:

dependencyResolutionManagement {
    repositories {
        // ... existing repos
        maven { url "https://repo.repsy.io/mvn/bambuser/bambuser-commerce-sdk" }
    }
}

The SDK uses Jetpack Compose internally. Add the Compose Kotlin plugin to your root android/build.gradle:

plugins {
    id "org.jetbrains.kotlin.plugin.compose" version "2.1.20" apply false
}

Bump minSdkVersion to 26 or higher in your android/build.gradle.

Picture-in-Picture (Android)

Android uses activity-level PiP. To enable it, add supportsPictureInPicture to your host Activity in AndroidManifest.xml:

<activity
  android:name=".MainActivity"
  android:supportsPictureInPicture="true"
  android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation|keyboard|keyboardHidden"
  ... />

When the activity enters PiP, the entire activity shrinks (not just the video). Hide non-video UI when onPiPStateChanged fires 'started' and restore it on 'stopped'.

Quick Start

import { useRef } from 'react';
import {
  BambuserVideoView,
  type BambuserVideoViewRef,
} from '@bambuser/react-native-commerce-sdk';

export function LiveVideoScreen() {
  const playerRef = useRef<BambuserVideoViewRef>(null);

  return (
    <BambuserVideoView
      ref={playerRef}
      style={{ flex: 1 }}
      id="your-video-id"
      server="US"
      configuration={{
        autoplay: true,
        currency: 'USD',
        locale: 'en-US',
      }}
      onStatus={(e) => console.log('state:', e.nativeEvent.state)}
      onError={(e) => console.error(e.nativeEvent.message)}
    />
  );
}

Component API

<BambuserVideoView />

Props

| Prop | Type | Default | Platform | Description | |---|---|---|---|---| | id | string | required | Both | Video identifier. | | server | 'US' \| 'EU' | 'US' | Both | Organization server region. | | mode | 'live' | 'live' | Both | Video mode. | | events | string[] | ['*'] | Both | Custom events to subscribe to. Changing this prop creates a new player. | | configuration | Record<string, any> | {} | Both | Player configuration. Changing this prop creates a new player — memoize it. | | ignoredSafeAreaEdges | SafeAreaEdge[] | [] | Both | Edges to ignore when the player computes safe-area padding. | | onEvent | (e) => void | — | Both | Player emitted a custom event. See Event Payloads. | | onStatus | (e) => void | — | Both | Playback state changed. | | onProgress | (e) => void | — | Both | Periodic progress updates. | | onError | (e) => void | — | Both | An error occurred. | | onPiPStateChanged | (e) => void | — | Both | Picture-in-Picture state changed. | | onThumbnailTapped | (e) => void | — | iOS only | User tapped the video thumbnail (pre-play). |

⚠️ Memoize configuration. Passing a new object literal on every render rebuilds the player. Use useMemo, a module-level constant, or a state-stable object.

Ref Methods

const playerRef = useRef<BambuserVideoViewRef>(null);

| Method | Signature | Notes | |---|---|---| | play() | () => void | Resume playback. | | pause() | () => void | Pause playback. | | mute() | () => void | Mute audio. | | unMute() | () => void | Unmute audio. | | startPiP() | () => void | Enter Picture-in-Picture. | | stopPiP() | () => void | Exit Picture-in-Picture. | | setPiPEnabled(enabled) | (boolean) => void | Enable/disable the PiP feature. Defaults to true. | | invoke(fn, args) | (string, string) => Promise<any> | Call a player-side function. See Player Bridge. | | notify(callbackKey, info) | (string, NotifyInfo) => void | Reply to a callback event surfaced via onEvent. | | getCurrentPlayerState() | () => Promise<BambuserPlayerState> | Read the last-known player state. | | getNativeTag() | () => number \| null | Underlying native view tag. Escape hatch — rarely needed. | | cleanup() | () => void | Tear down the player and release native resources. Irreversible — see Lifecycle. |

BambuserSDK

For SDK-level operations that don't require a player view (currently: analytics tracking).

import { BambuserSDK } from '@bambuser/react-native-commerce-sdk';

const sdk = new BambuserSDK({ server: 'US' });

await sdk.track('purchase', {
  orderId: 'order-123',
  total: 99.99,
  currency: 'USD',
});

| Method | Signature | Description | |---|---|---| | track | (event: string, data: Record<string, any>) => Promise<Record<string, any> \| null> | Send tracking data to Bambuser Analytics. Resolves with the response payload, or null. |

Event Payloads

All callbacks receive a synthetic event with the payload under e.nativeEvent.

onStatus

{ id: string; state: BambuserPlayerState }

state is one of:

'ready' · 'loading' · 'playing' · 'paused' · 'stopped' · 'completed' · 'error' · 'idle' · 'buffering'

onProgress

{ id: string; duration: number; currentTime: number }

Both values are in seconds.

onError

{ id: string; message: string }

onEvent

{ id: string; type: string; data: any }

Custom events emitted by the player (add-to-cart, wishlist, share, etc.). The data shape depends on the event type — see the Bambuser docs for the full event catalog.

Platform difference — callbackKey: For events that expect a response, the callback key lives at data.callbackKey on iOS and at the top level (e.nativeEvent.callbackKey) on Android. Read both:

const callbackKey = e.nativeEvent.callbackKey ?? e.nativeEvent.data?.callbackKey;

Platform difference — event data shape: Some event payloads nest data under data.event on iOS and at the top level on Android. For example, an add-to-wishlist SKU is at data.event.sku (iOS) or data.sku (Android).

onPiPStateChanged

{ id: string; state: BambuserPiPState }

state is one of:

| State | Fires when | Platform | |---|---|---| | 'willStart' | Right before PiP begins. | Both | | 'started' | PiP is active. | Both | | 'willStop' | Right before PiP ends (programmatic stop). | Both | | 'stopped' | PiP has ended. | Both | | 'restored' | User tapped "Go to full screen" from the PiP window. | iOS only |

onThumbnailTapped (iOS)

{ id: string }

Player Bridge: invoke & notify

The Bambuser player runs in a webview internally. Two primitives bridge JS ↔ player:

  • invoke(fn, args) — call a function on the player. Returns a Promise. Use this to drive UI commands (e.g. show/hide overlays).

    await playerRef.current?.invoke('hideUI', '');
    await playerRef.current?.invoke('showUI', '');
  • notify(callbackKey, info) — respond to a callback event the player surfaced via onEvent. The callbackKey comes from the event payload; info is your response.

    function onEvent(e) {
      const { type, data } = e.nativeEvent;
      const callbackKey =
        e.nativeEvent.callbackKey ?? data?.callbackKey;
    
      if (type === 'add-to-wishlist' && callbackKey) {
        const sku = Platform.OS === 'ios' ? data?.event?.sku : data?.sku;
        // ... your wishlist logic
        playerRef.current?.notify(callbackKey, { success: true, sku });
      }
    }

info accepts booleans, numbers, strings, plain objects/arrays, or null. Strings that already look like JS literals ({...}, [...], numbers, true/false/null) are passed through; anything else is JSON-encoded.

Picture-in-Picture

PiP is enabled by default. Disable per-component with setPiPEnabled(false), or trigger it manually with startPiP() / stopPiP().

playerRef.current?.startPiP();
playerRef.current?.stopPiP();
playerRef.current?.setPiPEnabled(false);

Platform behavior

  • iOS — PiP is view-level. Only the video floats; the rest of your app is unaffected. Provided by the SDK's pipController.

  • Android — PiP is activity-level. The entire host activity shrinks into the PiP window. You should hide non-video UI when entering PiP and restore it on exit:

    const [inPiP, setInPiP] = useState(false);
    
    <BambuserVideoView
      onPiPStateChanged={(e) => setInPiP(e.nativeEvent.state === 'started')}
      ...
    />
    {!inPiP && <YourAppChrome />}

State Vocabulary

For type-safe state handling:

import type {
  BambuserPlayerState,
  BambuserPiPState,
} from '@bambuser/react-native-commerce-sdk';

function describe(state: BambuserPlayerState) {
  switch (state) {
    case 'playing': return 'Now playing';
    case 'buffering': return 'Buffering…';
    case 'paused': return 'Paused';
    // ...
  }
}

Lifecycle & Cleanup

The player automatically releases native resources when the component unmounts. You normally don't need to call cleanup() yourself.

Call cleanup() explicitly when:

  • You're about to navigate away and want to free resources immediately, or
  • You want to stop the video before unmount (e.g. the user backed out via a custom gesture).

⚠️ cleanup() is irreversible. Once called, the player is gone. To play again, unmount and remount the component (or change id to force a rebuild).

useEffect(() => () => playerRef.current?.cleanup(), []);

Configuration Reference

configuration is forwarded to the underlying SDK. Common options:

configuration={{
  autoplay: true,
  currency: 'USD',
  locale: 'en-US',
  buttons: { dismiss: 'none' },
  ui: { hideShareButton: true, hideEmojiOverlay: true },
}}

For the complete list of supported keys (per-event, per-feature) consult the Bambuser Live Video docs.

Types

type SafeAreaEdge = 'all' | 'top' | 'bottom' | 'leading' | 'trailing';

type BambuserPlayerState =
  | 'ready' | 'loading' | 'playing' | 'paused' | 'stopped'
  | 'completed' | 'error' | 'idle' | 'buffering';

type BambuserPiPState =
  | 'willStart' | 'willStop' | 'started' | 'stopped' | 'restored';

type BambuserSDKOptions = { server?: 'US' | 'EU' };

type NotifyInfo =
  | boolean | number | string | null | undefined
  | Record<string, any> | Array<any>;

Troubleshooting

iOS — pod install fails to find the framework. The xcframework is vendored inside the package. If pods can't resolve it, delete ios/Pods and ios/Podfile.lock, then run pod install again.

Android — build fails with a Compose-related error. Make sure the Compose Kotlin plugin is declared in your root build.gradle and that your Kotlin version is compatible (Kotlin >= 2.1 recommended).

Android — minSdkVersion errors. The SDK requires API 26+. Bump minSdkVersion in your project's android/build.gradle.

Player rebuilds on every render. You're passing a new configuration (or events) object literal each render. Memoize it with useMemo or hoist it to module scope.

onEvent callbacks fire but callbackKey is undefined. The location differs across platforms — read both: e.nativeEvent.callbackKey ?? e.nativeEvent.data?.callbackKey.

Documentation