react-native-sdk-pianoio
v0.5.1
Published
Piano io sdk integration
Maintainers
Readme
React Native SDK for Piano.io
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-pianoioPlatform 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_AIDwith your actual Piano Application ID. The scheme must bepiano.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:
showRecommendationsrequires 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 wrapperAndroid 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 androidSign-In Not Working
Android:
- Verify OAuth redirect URI is configured in AndroidManifest.xml
- Ensure
android:launchMode="singleTask"is set on MainActivity - Confirm your Piano AID matches between initialization and AndroidManifest.xml
- Check that the redirect URI is registered in Piano Console (see note below)
- Check Android logs:
adb logcat | grep 'ComposerPianoImpl\|PianoId'
iOS:
- Verify URL scheme is added to Info.plist (
io.piano.id.{YOUR_AID_LOWERCASE}) - Ensure AppDelegate has the OAuth callback method implemented
- Confirm your Piano AID matches between initialization and Info.plist
- Check that the redirect URI is registered in Piano Console (see note below)
- 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
- 📧 Email: [email protected]
- 📖 Piano Documentation: Piano.io Docs
- 🎹 Piano Developer Portal: Console
Made with ❤️ by HexagonSwiss
