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

@openmobile/sdk-web

v0.1.0-alpha

Published

OpenMobile Web SDK - React components for server-driven UI

Readme

OpenMobile Web SDK

MIT License npm version TypeScript React Build Status

Native in-app experiences for React applications - update your UI instantly without app releases.

Features

  • 🎯 Native rendering - React components from JSON definitions
  • 📊 Surveys - NPS, CSAT, and custom feedback forms
  • 🚀 Onboarding flows - Interactive user tours and walkthroughs
  • 📢 Feature announcements - In-app messages and changelogs
  • 🎨 Visual editor - Design screens without coding
  • Instant updates - No app store deployment required
  • 🔒 Type-safe - Full TypeScript support
  • 🧪 Well-tested - Comprehensive test coverage

📦 Installation

npm

npm install @openmobile/sdk-web

yarn

yarn add @openmobile/sdk-web

pnpm

pnpm add @openmobile/sdk-web

Requirements:

  • React 18.0+
  • TypeScript 5.0+ (recommended)

🚀 Quick Start

1. Initialize SDK

In your app entry point (main.tsx or App.tsx):

import { OpenMobileSDK } from '@openmobile/sdk-web';

// Initialize once at app startup
OpenMobileSDK.initialize({
  apiKey: 'your-api-key-here',
  baseUrl: 'https://api.openmobile.io' // optional
});

2. Use OpenMobileScreen Component

import React from 'react';
import { OpenMobileScreen, EventListener } from '@openmobile/sdk-web';

const json = `
{
  "version": "1.0",
  "id": "home_screen",
  "root": {
    "type": "column",
    "properties": {
      "id": "main",
      "spacing": 16,
      "padding": 20
    },
    "children": [
      {
        "type": "text",
        "properties": {
          "id": "title",
          "text": "Welcome to OpenMobile!",
          "fontSize": 24,
          "fontWeight": "bold"
        }
      },
      {
        "type": "button",
        "properties": {
          "id": "cta",
          "text": "Get Started"
        },
        "events": {
          "onTap": {
            "type": "navigate",
            "route": "/getting-started"
          }
        }
      }
    ]
  }
}
`;

function HomeScreen() {
  return (
    <OpenMobileScreen
      json={json}
      eventListener={new MyEventListener()}
    />
  );
}

3. Handle Events

import { EventListener } from '@openmobile/sdk-web';
import { useNavigate } from 'react-router-dom'; // or your routing library

class MyEventListener implements EventListener {
  constructor(private navigate?: any) {}

  onNavigate(route: string, params?: Record<string, any>) {
    console.log('Navigate to:', route, params);
    // Use your routing library
    this.navigate?.(route);
  }

  onOpenUrl(url: string, external: boolean) {
    console.log('Open URL:', url, 'external:', external);
    if (external) {
      window.open(url, '_blank');
    } else {
      window.location.href = url;
    }
  }

  onCustomEvent(action: string, payload?: Record<string, any>) {
    switch (action) {
      case 'add_to_cart':
        console.log('Add to cart:', payload?.product_id);
        // Handle add to cart
        break;
      case 'share':
        console.log('Share:', payload);
        // Handle share
        break;
    }
  }

  onSubmitForm(endpoint: string, method: string, fields?: Record<string, any>) {
    console.log('Submit form to:', endpoint);
    // Make API call
    fetch(endpoint, {
      method,
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(fields)
    });
  }

  onTrackAnalytics(eventName: string, properties?: Record<string, any>) {
    console.log('Track:', eventName, properties);
    // Send to your analytics service (GA, Mixpanel, etc.)
  }
}

// Usage in component
function MyComponent() {
  const navigate = useNavigate();
  const eventListener = new MyEventListener(navigate);

  return <OpenMobileScreen json={json} eventListener={eventListener} />;
}

📖 Components

Column

Vertical layout container (flex column).

{
  "type": "column",
  "properties": {
    "id": "main_column",
    "spacing": 16,
    "padding": 20,
    "scrollable": true,
    "horizontalAlignment": "center",
    "backgroundColor": "#F5F5F5"
  },
  "children": [...]
}

Text

Display text content.

{
  "type": "text",
  "properties": {
    "id": "title",
    "text": "Hello World",
    "fontSize": 24,
    "fontWeight": "bold",
    "color": "#000000",
    "textAlign": "center",
    "maxLines": 2
  }
}

Button

Interactive button.

{
  "type": "button",
  "properties": {
    "id": "submit_btn",
    "text": "Submit",
    "variant": "primary",
    "size": "large",
    "fullWidth": true,
    "backgroundColor": "#FF6B35",
    "textColor": "#FFFFFF"
  },
  "events": {
    "onTap": {
      "type": "navigate",
      "route": "/success"
    }
  }
}

Button Variants:

  • primary: Filled button with background color
  • secondary: Filled button with secondary color
  • outline: Outlined button with transparent background
  • text: Text-only button (no background, no border)

Button Sizes:

  • small: Compact button
  • medium: Default size
  • large: Large button

Image

Display images from URL.

{
  "type": "image",
  "properties": {
    "id": "hero_image",
    "url": "https://example.com/image.jpg",
    "aspectRatio": "16:9",
    "resizeMode": "cover",
    "alt": "Hero banner",
    "borderRadius": 12
  }
}

Resize Modes:

  • cover: Fills container, may crop image
  • contain: Fits entirely, may have empty space
  • fill: Stretches to fill container
  • center: Original size, centered

Card

Container with elevation (shadow).

{
  "type": "card",
  "properties": {
    "id": "promo_card",
    "elevation": 4,
    "cornerRadius": 16,
    "padding": 20,
    "clickable": true
  },
  "children": [...]
}

⚡ Events

Navigate

Navigate to another screen/route.

{
  "type": "navigate",
  "route": "/product/detail",
  "params": {
    "product_id": "PROD-123"
  }
}

Open URL

Open external URL or webview.

{
  "type": "open_url",
  "url": "https://example.com",
  "external": true
}

Custom

Trigger custom actions in your app.

{
  "type": "custom",
  "action": "add_to_cart",
  "payload": {
    "product_id": "PROD-123",
    "quantity": 1
  }
}

Submit Form

Submit data to API.

{
  "type": "submit_form",
  "endpoint": "https://api.example.com/contact",
  "method": "POST",
  "fields": {
    "name": "John Doe",
    "email": "[email protected]"
  }
}

Track Analytics

Send analytics events.

{
  "type": "track_analytics",
  "eventName": "button_clicked",
  "properties": {
    "button_id": "cta_primary",
    "screen": "home"
  }
}

🎨 Styling

Colors

Supports hex colors and named colors:

"color": "#FF6B35"
"backgroundColor": "#FFFFFF"
"textColor": "blue"

Named colors: black, white, red, green, blue, yellow, orange, purple, pink, gray, brown, cyan, indigo, mint, teal

Spacing

All spacing values in pixels:

"padding": 16,
"margin": 8,
"spacing": 12

Typography

"fontSize": 18,
"fontWeight": "bold",
"lineHeight": 1.5,
"textAlign": "center"

Font weights: thin (100), ultralight (200), light (300), regular (400), medium (500), semibold (600), bold (700), heavy (800), black (900)


🔍 Examples

Full Example: Product Card

import React, { useState, useEffect } from 'react';
import { OpenMobileScreen, EventListener } from '@openmobile/sdk-web';

function ProductPage({ productId }: { productId: string }) {
  const [json, setJson] = useState<string | null>(null);
  const [isLoading, setIsLoading] = useState(true);

  useEffect(() => {
    // Fetch JSON from your API
    fetch(`https://api.example.com/products/${productId}/screen`)
      .then(res => res.json())
      .then(data => {
        setJson(JSON.stringify(data));
        setIsLoading(false);
      })
      .catch(error => {
        console.error('Error loading product:', error);
        setIsLoading(false);
      });
  }, [productId]);

  if (isLoading) return <div>Loading...</div>;
  if (!json) return <div>Error loading product</div>;

  return (
    <OpenMobileScreen
      json={json}
      eventListener={new ProductEventListener()}
    />
  );
}

class ProductEventListener implements EventListener {
  onCustomEvent(action: string, payload?: Record<string, any>) {
    if (action === 'add_to_cart') {
      const productId = payload?.product_id;
      const price = payload?.price;
      console.log(`Adding product ${productId} ($${price}) to cart`);
      // Add to cart logic
    }
  }
}

Custom Loading & Error Components

import { OpenMobileScreen } from '@openmobile/sdk-web';

function MyScreen() {
  return (
    <OpenMobileScreen
      json={json}
      loadingComponent={
        <div className="custom-loading">
          <Spinner />
          <p>Loading your content...</p>
        </div>
      }
      errorComponent={(error) => (
        <div className="custom-error">
          <h2>Oops!</h2>
          <p>{error}</p>
          <button onClick={() => window.location.reload()}>
            Try Again
          </button>
        </div>
      )}
      onSuccess={(screen) => {
        console.log('Screen loaded:', screen.id);
      }}
      onError={(error) => {
        console.error('Failed to load screen:', error);
        // Report to error tracking service
      }}
    />
  );
}

TypeScript Usage

import { Screen, Component, ComponentType } from '@openmobile/sdk-web';

// Type-safe screen creation
const screen: Screen = {
  version: '1.0',
  id: 'my_screen',
  root: {
    type: ComponentType.COLUMN,
    properties: {
      id: 'root',
      spacing: 16,
      padding: 20,
    },
    children: [
      {
        type: ComponentType.TEXT,
        properties: {
          id: 'title',
          text: 'Hello TypeScript!',
          fontSize: 24,
        },
      },
    ],
  },
};

// Use with OpenMobileScreen
<OpenMobileScreen screen={screen} />

🧪 Testing

Unit Tests with Jest

import { render, screen } from '@testing-library/react';
import { OpenMobileScreen } from '@openmobile/sdk-web';

describe('OpenMobileScreen', () => {
  it('renders text component', () => {
    const json = `{
      "version": "1.0",
      "id": "test",
      "root": {
        "type": "text",
        "properties": {
          "id": "test_text",
          "text": "Hello Test"
        }
      }
    }`;

    render(<OpenMobileScreen json={json} />);
    expect(screen.getByText('Hello Test')).toBeInTheDocument();
  });

  it('handles invalid JSON', () => {
    const onError = jest.fn();
    render(<OpenMobileScreen json="invalid json" onError={onError} />);
    expect(onError).toHaveBeenCalled();
  });
});

📚 Documentation


🐛 Troubleshooting

Error: "SDK not initialized"

Solution: Call OpenMobileSDK.initialize() before using any SDK features.

Error: "Invalid JSON format"

Solution: Validate your JSON against the schema. Check JSON_SCHEMA.md for correct format.

Images not loading

Solution:

  • Ensure URLs are HTTPS
  • Check CORS headers on image server
  • Verify image URLs are accessible

TypeScript errors

Solution:

  • Ensure @openmobile/sdk-core is installed
  • Check tsconfig.json has "moduleResolution": "node"
  • Run npm install to ensure all dependencies are installed

🤝 Contributing

We welcome contributions! Please see CONTRIBUTING.md for guidelines.

📄 License

MIT License - see LICENSE file for details.

📞 Support


Built with ❤️ by OpenMobile Team