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 🙏

© 2025 – Pkg Stats / Ryan Hefner

react-native-sdk-pianoio

v0.5.1

Published

Piano io sdk integration

Readme

React Native SDK for Piano.io

Version License Platform

A comprehensive React Native SDK for integrating Piano.io's digital monetization platform into your mobile applications. This SDK provides seamless, cross-platform integration with Piano's paywall, subscription, metering, and content monetization features.

✨ Key Features

🚀 Complete Piano Integration

  • Piano Composer: Execute experiences and handle multiple simultaneous events
  • Piano ID Authentication: Complete user authentication and token management
  • Advanced Metering: Track user engagement and enforce content limits
  • User Segmentation: Handle complex audience targeting and personalization

🔄 Multiple Events Support

  • Simultaneous Event Handling: Capture all Piano events in a single execution
  • Event Collection System: 2-second collection window ensures no events are missed
  • Rich Event Data: User segments, meters, templates, and experience execution data

📱 Cross-Platform Excellence

  • iOS & Android Parity: Identical functionality across both platforms
  • Type-Safe API: Full TypeScript support with comprehensive type definitions
  • Robust Error Handling: Comprehensive validation and defensive programming
  • Production Ready: Extensively tested with real Piano configurations

📦 Installation

npm install react-native-sdk-pianoio
# or
yarn add react-native-sdk-pianoio

Platform Setup

iOS Requirements

1. Deployment Target & Build Configuration

// In your app.json (Expo) or iOS deployment target
{
  "expo": {
    "plugins": [
      [
        "expo-build-properties",
        {
          "ios": {
            "deploymentTarget": "15.1",
            "useFrameworks": "static"
          }
        }
      ]
    ]
  }
}

2. OAuth URL Scheme Configuration (Required for Piano ID Authentication)

Add a custom URL scheme to your app's Info.plist:

<!-- In ios/YourApp/Info.plist -->
<key>CFBundleURLTypes</key>
<array>
  <dict>
    <key>CFBundleTypeRole</key>
    <string>Editor</string>
    <key>CFBundleURLSchemes</key>
    <array>
      <!-- Replace YOUR_PIANO_AID with your actual Piano Application ID (lowercase) -->
      <string>io.piano.id.YOUR_PIANO_AID</string>
    </array>
  </dict>
</array>

Example:

<string>io.piano.id.youraid123</string>

3. AppDelegate OAuth Callback Handling

Add the following to your AppDelegate:

Swift (AppDelegate.swift):

import PianoOAuth

func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey: Any] = [:]) -> Bool {
    return PianoOAuth.PianoIDApplicationDelegate.shared.application(app, open: url, options: options)
}

Objective-C (AppDelegate.m):

#import <PianoOAuth/PianoOAuth-Swift.h>

- (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary<UIApplicationOpenURLOptionsKey,id> *)options {
    return [PianoIDApplicationDelegate.shared application:app openURL:url options:options];
}

⚠️ Important: The URL scheme must be lowercase and follow the format io.piano.id.{YOUR_AID_LOWERCASE}. This is required for Piano ID OAuth to redirect back to your app after authentication.

Android Requirements

1. Build Configuration

// In your app.json (Expo)
{
  "expo": {
    "plugins": [
      [
        "expo-build-properties",
        {
          "android": {
            "minSdkVersion": 24,
            "compileSdkVersion": 34,
            "targetSdkVersion": 34,
            "buildToolsVersion": "35.0.0"
          }
        }
      ]
    ]
  }
}

2. OAuth Redirect URI Configuration (Required for Piano ID Authentication)

Add the following to your AndroidManifest.xml to enable Piano ID sign-in:

<!-- In android/app/src/main/AndroidManifest.xml -->
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
  <application>
    <activity
      android:name=".MainActivity"
      android:launchMode="singleTask">

      <!-- Add this intent-filter for Piano ID OAuth -->
      <intent-filter android:autoVerify="true">
        <action android:name="android.intent.action.VIEW" />
        <category android:name="android.intent.category.DEFAULT" />
        <category android:name="android.intent.category.BROWSABLE" />

        <!-- Replace YOUR_PIANO_AID with your actual Piano Application ID -->
        <data
          android:scheme="piano.id.oauth.YOUR_PIANO_AID"
          android:host="success" />
      </intent-filter>

    </activity>
  </application>
</manifest>

Example:

<data
  android:scheme="piano.id.oauth.yourAID123"
  android:host="success" />

⚠️ Important: Replace YOUR_PIANO_AID with your actual Piano Application ID. The scheme must be piano.id.oauth.{YOUR_AID} for authentication to work.

🚀 Quick Start

1. Initialize the SDK

import { PianoComposer, PianoEndpoint } from 'react-native-sdk-pianoio';

// Initialize with your Piano Application ID and endpoint
// Default: Production US
const composer = await PianoComposer.create('YOUR_PIANO_AID');

// Or specify a regional endpoint
const composer = await PianoComposer.create('YOUR_PIANO_AID', PianoEndpoint.PRODUCTION_EUROPE);

// Or use sandbox for testing
const composer = await PianoComposer.create('YOUR_PIANO_AID', PianoEndpoint.SANDBOX);

2. Configure Content & Context

// Essential configuration for Piano experience execution
await composer.setUrl('https://www.your-site.com/premium-article');
await composer.setReferrer('https://www.your-site.com/');

// Content categorization
await composer.addTag('premium-content');
await composer.addTags(['mobile', 'subscription', 'paywall']);

// Custom tracking variables
await composer.setCustomVariable('content_id', 'article-12345');
await composer.setCustomVariable('article_type', 'premium');
await composer.setCustomVariable('user_type', 'free');

3. Execute Piano Experience

try {
  const result = await composer.executeExperience();

  // Handle different response types
  switch (result.eventType) {
    case 'multipleEvents':
      console.log(`Received ${result.events.length} events:`, result.events);

      // Process each event
      result.events.forEach(event => {
        switch (event.eventType) {
          case 'meter':
            console.log(`Meter: ${event.views}/${event.maxViews} views used`);
            break;
          case 'userSegment':
            console.log(`User segment active: ${event.state}`);
            break;
          case 'showTemplate':
            console.log(`Show template: ${event.templateId}`);
            break;
          case 'experienceExecute':
            console.log('Experience executed with access list:', event.accessList);
            break;
        }
      });
      break;

    case 'meter':
      console.log(`Single meter event: ${result.views}/${result.maxViews}`);
      break;

    case 'noEvents':
      console.log('No Piano events triggered for this configuration');
      break;
  }
} catch (error) {
  console.error('Piano execution failed:', error);
}

4. Handle User Authentication

// Sign in user (opens Piano ID interface)
try {
  const user = await composer.signIn();
  console.log('User signed in:', user.uid, user.email);
} catch (error) {
  console.error('Sign in failed:', error);
}

// Check authentication status
const isAuth = await composer.isAuthenticated();

// Get current user details
const currentUser = await composer.getCurrentUser();
if (currentUser?.authenticated) {
  console.log('Current user:', currentUser.uid, currentUser.email);
}

// Sign out
await composer.signOut();

5. Monitor SDK Status

// Check if SDK is initialized (static method)
const isInit = await PianoComposer.isInitialized();
console.log('SDK initialized:', isInit);

// Get comprehensive SDK status
const status = await composer.getStatus();
console.log('SDK Status:', {
  initialized: status.initialized,
  aid: status.aid,
  tags: status.tags,
  url: status.url,
  customVariables: status.customVariables
});

📋 Complete API Reference

Core Methods

| Method | Description | Returns | |--------|-------------|---------| | PianoComposer.create(aid, endpoint?) | Create and initialize SDK instance | Promise<PianoComposer> | | PianoComposer.isInitialized() | Check if SDK has been initialized | Promise<boolean> | | executeExperience() | Execute Piano experience with current config | Promise<PianoEvent> | | getStatus() | Get current SDK configuration status | Promise<PianoStatus> |

Configuration Methods

| Method | Description | Returns | |--------|-------------|---------| | setUrl(url) | Set content URL for experience | Promise<boolean> | | setReferrer(referrer) | Set referrer URL | Promise<boolean> | | setZoneId(zoneId) | Set Piano zone identifier | Promise<boolean> | | addTag(tag) | Add content tag | Promise<boolean> | | addTags(tags[]) | Add multiple content tags | Promise<boolean> | | setCustomVariable(key, value) | Set custom tracking variable | Promise<boolean> | | setCustomVariables(variables) | Set multiple custom variables | Promise<boolean> | | setUserToken(token) | Set user authentication token | Promise<boolean> |

Authentication Methods

| Method | Description | Returns | |--------|-------------|---------| | signIn() | Initiate Piano ID sign-in flow | Promise<PianoUser> | | signOut() | Sign out current user | Promise<boolean> | | getCurrentUser() | Get current authenticated user | Promise<PianoUser \| null> | | isAuthenticated() | Check if user is authenticated | Promise<boolean> |

PianoUser Object:

interface PianoUser {
  success?: boolean;        // Sign-in success status
  authenticated?: boolean;  // Authentication state
  uid?: string;             // User ID
  email?: string;           // User email address
  accessToken?: string;     // OAuth access token
  [key: string]: any;       // Additional fields from Piano SDK
}

Cleanup & Management

| Method | Description | Returns | |--------|-------------|---------| | clearConfiguration() | Clear all tags and custom variables | Promise<boolean> | | removeTag(tag) | Remove specific tag | Promise<boolean> | | removeCustomVariable(key) | Remove specific custom variable | Promise<boolean> | | clearTags() | Clear all tags | Promise<boolean> | | clearCustomVariables() | Clear all custom variables | Promise<boolean> |

🎯 Event Types & Data Structures

PianoEvent Response Types

// Single event responses
type PianoEventData =
  | UserSegmentEvent    // User segmentation result
  | MeterEvent         // Content metering data
  | ShowTemplateEvent  // Paywall template display
  | ShowLoginEvent     // Authentication required
  | ShowFormEvent      // Data collection form display
  | ShowRecommendationsEvent // Content recommendations display
  | SetResponseVariableEvent // Server response variable updates
  | ExperienceExecuteEvent // Experience execution result
  | NoEventsResponse   // No events triggered
  | NonSiteEvent      // Non-site specific event

// Multiple events response (most common)
interface MultipleEventsResponse {
  eventType: 'multipleEvents';
  events: PianoEventData[];
}

Event Data Examples

// Meter Event - Content usage tracking
{
  eventType: 'meter',
  meterName: 'DefaultMeter',
  views: 2,
  viewsLeft: 3,
  maxViews: 5,
  totalViews: 2,
  incremented: true,
  state: 'ACTIVE' // or 'EXPIRED'
}

// Show Login Event - Authentication required
{
  eventType: 'showLogin',
  userProvider: 'piano_id'
}

// Show Template Event - Display paywall
{
  eventType: 'showTemplate',
  templateId: 'OTABCDEFG123',
  templateVariantId: 'variant-abc',
  displayMode: 'MODAL',
  containerSelector: '#piano-container',
  showCloseButton: false,
  delayBy: { ... },
  templateUrl: 'https://example.piano.io/checkout/template/show?...',
  endpointUrl: 'https://api.piano.io',
  trackingId: 'tracking-xyz'
}

// Show Form Event - Data collection form
{
  eventType: 'showForm',
  formName: 'Newsletter Signup',
  hideCompletedFields: true,
  templateId: 'FORM123',
  templateVariantId: 'variant-form',
  containerSelector: '#form-container',
  displayMode: 'INLINE',
  showCloseButton: true,
  aid: 'yourAID123',
  pageViewId: 'pv-xyz',
  endpointUrl: 'https://api.piano.io',
  trackingId: 'tracking-form'
}

// Show Recommendations Event - Content recommendations (requires C1X/Cxense integration)
{
  eventType: 'showRecommendations',
  widgetId: 'widget-123',
  placeholder: 'recommendation-placeholder',
  siteId: 'cx-site-id',
  templateId: 'REC123',
  templateVariantId: 'variant-rec',
  containerSelector: '#recommendations',
  displayMode: 'INLINE',
  showCloseButton: false,
  endpointUrl: 'https://api.piano.io',
  trackingId: 'tracking-rec'
}

// User Segment Event - Audience targeting
{
  eventType: 'userSegment',
  state: true
}

// Experience Execute Event - Access control
{
  eventType: 'experienceExecute',
  user: null,
  accessList: []
}

🔧 Advanced Usage

Regional Endpoints

Piano supports multiple regional endpoints for data residency and performance optimization:

import { PianoComposer, PianoEndpoint } from 'react-native-sdk-pianoio';

// US Production (default)
const composer = await PianoComposer.create('YOUR_AID', PianoEndpoint.PRODUCTION);

// Europe Production
const composer = await PianoComposer.create('YOUR_AID', PianoEndpoint.PRODUCTION_EUROPE);

// Australia Production
const composer = await PianoComposer.create('YOUR_AID', PianoEndpoint.PRODUCTION_AUSTRALIA);

// Asia-Pacific Production
const composer = await PianoComposer.create('YOUR_AID', PianoEndpoint.PRODUCTION_ASIA_PACIFIC);

// Sandbox (for testing)
const composer = await PianoComposer.create('YOUR_AID', PianoEndpoint.SANDBOX);

// You can also use string values directly
const composer = await PianoComposer.create('YOUR_AID', 'production-europe');

Handling ShowLogin Events

When Piano requires authentication (e.g., for paywall access), it triggers a showLogin event:

const result = await composer.executeExperience();

if (result.eventType === 'showLogin') {
  // Piano is requesting user authentication
  console.log('Authentication required, provider:', result.userProvider);

  // Trigger sign-in flow
  try {
    const user = await composer.signIn();
    console.log('User authenticated:', user.uid, user.email);

    // Re-execute experience after authentication
    const newResult = await composer.executeExperience();
    // Handle the new result (likely with access granted)
  } catch (error) {
    console.error('Sign-in cancelled or failed:', error);
  }
} else if (result.eventType === 'multipleEvents') {
  // Check if any event is a showLogin
  const loginEvent = result.events.find(e => e.eventType === 'showLogin');
  if (loginEvent) {
    // Handle authentication requirement
  }
}

Handling ShowTemplate Events (Paywalls)

Piano provides a URL for templates, which you must display in a WebView:

import { Modal, WebView } from 'react-native';

const result = await composer.executeExperience();

if (result.eventType === 'multipleEvents') {
  const template = result.events.find(e => e.eventType === 'showTemplate');

  if (template) {
    const isModal = template.displayMode === 'MODAL';

    return (
      <Modal visible={true} presentationStyle={isModal ? 'pageSheet' : 'fullScreen'}>
        <WebView
          source={{ uri: template.templateUrl }}
          onNavigationStateChange={(navState) => {
            // Handle navigation/close events
            if (navState.url.includes('checkout/close')) {
              setShowTemplate(false);
            }
          }}
        />
      </Modal>
    );
  }
}

Note: Piano does not provide native mobile UI components. Templates are web-based and must be displayed in a WebView.

Handling ShowRecommendations Events (C1X/Cxense)

The showRecommendations event requires additional C1X/Cxense SDK integration:

const result = await composer.executeExperience();

if (result.eventType === 'multipleEvents') {
  const recommendations = result.events.find(e => e.eventType === 'showRecommendations');

  if (recommendations && recommendations.type === 'CXENSE') {
    // This event provides metadata for loading recommendations
    console.log('Widget ID:', recommendations.widgetId);

    // You must:
    // 1. Integrate Cxense SDK separately
    // 2. Use the widgetId to load actual recommendations
    // 3. Display recommendations in your UI
    // 4. Track clicks/displays back to Piano

    // Note: This SDK captures the event but doesn't provide Cxense integration
  }
}

⚠️ Important: showRecommendations requires the separate Piano C1X/Cxense SDK. This React Native SDK captures the event but doesn't include Cxense functionality.

Error Handling Best Practices

try {
  const result = await composer.executeExperience();
  // Handle success
} catch (error) {
  switch (error.code) {
    case 'NOT_INITIALIZED':
      console.error('SDK not initialized');
      break;
    case 'INVALID_PARAMETER':
      console.error('Invalid parameter:', error.message);
      break;
    case 'EXECUTION_ERROR':
      console.error('Piano execution failed:', error.message);
      break;
    default:
      console.error('Unknown error:', error);
  }
}

Memory Management

// The SDK automatically manages memory and promises
// No manual cleanup required - all promises resolve within 5 seconds
// Event collection timeout: 2 seconds
// Fallback timeout: 5 seconds

📊 Testing & Debugging

Debug Logging

The SDK provides comprehensive logging for development:

// All SDK methods include detailed console logging
// Look for these prefixes in your logs:
// [SdkPianoio] - Native module operations
// [ComposerPianoImpl] - Core implementation
// [MyComposerDelegate] - iOS event handling
// [PianoComposer] - JavaScript wrapper

Android Debugging

# View Piano-related logs
adb logcat | grep 'ComposerPianoImpl\|PianoId'

# Clear logs and start fresh
adb logcat -c && adb logcat | grep 'ComposerPianoImpl\|PianoId'

Common Issues

OAuth Redirect URI Mismatch Error

Error: "The redirect URI in the request does not match the ones authorized"

Solution: Ensure your AndroidManifest.xml includes the OAuth intent-filter (see Android Requirements section above). The scheme must match your Piano AID:

<data
  android:scheme="piano.id.oauth.YOUR_PIANO_AID"
  android:host="success" />

After adding the intent-filter, rebuild your app:

cd android && ./gradlew clean && cd .. && npm run android

Sign-In Not Working

Android:

  1. Verify OAuth redirect URI is configured in AndroidManifest.xml
  2. Ensure android:launchMode="singleTask" is set on MainActivity
  3. Confirm your Piano AID matches between initialization and AndroidManifest.xml
  4. Check that the redirect URI is registered in Piano Console (see note below)
  5. Check Android logs: adb logcat | grep 'ComposerPianoImpl\|PianoId'

iOS:

  1. Verify URL scheme is added to Info.plist (io.piano.id.{YOUR_AID_LOWERCASE})
  2. Ensure AppDelegate has the OAuth callback method implemented
  3. Confirm your Piano AID matches between initialization and Info.plist
  4. Check that the redirect URI is registered in Piano Console (see note below)
  5. Check Xcode console for Piano logs

Piano Console Configuration: The redirect URIs must be registered in your Piano application's OAuth settings:

  • Android: piano.id.oauth.{YOUR_AID}://success
  • iOS: io.piano.id.{your_aid_lowercase}://success

You'll need to locate the OAuth or redirect URI configuration section in the Piano Console for your application and add these URIs. If you cannot find this setting, contact Piano support at [email protected] for assistance.

Test Configuration

// Example test configuration
const testConfig = {
  aid: 'YOUR_SANDBOX_AID',
  url: 'https://example.com/test-article',
  tags: ['test', 'premium-content'],
  customVariables: {
    'content_id': 'test-123',
    'article_type': 'premium',
    'user_type': 'free'
  }
};

Project Structure

├── android/          # Android native implementation (Kotlin)
│   ├── src/main/java/com/sdkpianoio/
│   │   ├── SdkPianoioModule.kt      # React Native bridge
│   │   ├── ComposerPianoImpl.kt     # Core Piano SDK integration
│   │   └── TokenService.kt          # Authentication service
├── ios/              # iOS native implementation (Swift)
│   ├── SdkPianoio.swift            # React Native bridge
│   ├── ComposerPianoImpl.swift     # Core Piano SDK integration
│   ├── MyComposerDelegate.swift    # Piano event delegate
│   └── TokenService.swift          # Authentication service
├── src/              # TypeScript source
│   ├── index.tsx                   # Public API exports
│   ├── PianoComposer.tsx          # Main wrapper class
│   └── NativeSdkPianoio.ts        # Type definitions & native bridge

📄 License

MIT License - see the LICENSE file for details.

🆘 Support & Resources


Made with ❤️ by HexagonSwiss