Latest commit

History

515 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

npm versionnpm downloadsmin Android SDK version is 21min iOS version is 13min Swift version is 5.3Contributor Covenant

Customer.io React Native SDK

The official Customer.io SDK for React Native enables you to integrate mobile messaging features—like in-app messaging and push notifications—into your app. These features can be triggered by events sent through the SDK and configured using your Customer.io campaigns.

📖 Full documentation: customer.io/docs/sdk/react-native
🧪 Example apps: see the /example directory


Installation

npm install customerio-reactnative

If a CocoaPods build reports that a generated dependency target is below the deployment range supported by Xcode, follow the deployment-target normalization guide.


SDK Initialization

Here’s a simplified example showing how to initialize the SDK to enable most mobile features:

import{CustomerIO,CioConfig,CioLogLevel,CioRegion,}from'customerio-reactnative';useEffect(()=>{constconfig: CioConfig={cdpApiKey: 'your-cdp-api-key',// RequiredmigrationSiteId: 'your-site-id',// Optional, for migrating from older SDKsregion: CioRegion.US,// Or CioRegion.EUlogLevel: CioLogLevel.debug,trackApplicationLifecycleEvents: true,inApp: {siteId: 'your-site-id',// Required for in-app messaging},push: {android: {pushClickBehavior: 'ActivityPreventRestart',// Optional},},};CustomerIO.initialize(config);},[]);

🔑 For help finding your credentials, check out the Quick Start Guide.


📲 Push Notifications

This SDK supports rich push notifications using Firebase (for Android) and either Firebase or APNs (for iOS). Follow our push setup guide to configure your project for push.

On iOS, a React Native 0.88+ UIScene host using the acknowledged handler must declare that ownership before React Native starts. This API is not used on Android or by AppDelegate-only hosts. Call this first in scene(_:willConnectTo:options:) so cold destinations wait for the JavaScript handler instead of entering the legacy Linking path:

NativeCustomerIO.configureAcknowledgedSceneDeepLinkRouting()

Register the app's Customer.io deep-link handler before calling CustomerIO.initialize. Return true after routing a URL. Return false to let the native SDK try the host AppDelegate, then pass a host-owned custom scheme to React Native Linking or open other URLs through the system. If your handler might decline an app-owned custom scheme, keep a Linking URL listener registered for that fallback. A thrown error, rejected promise, missing handler, or handler timeout follows the same native fallback. Cold URLs wait up to ten seconds for registration and are replayed once the handler is ready. After delivery, the handler has ten seconds to settle. If it takes longer, native fallback runs; a handler that later routes the URL can cause a second navigation because its acknowledgement cannot cancel the fallback.

constsubscription=CustomerIO.setDeepLinkHandler(async(url)=>{if(!canRouteInApp(url)){returnfalse;}awaitrouteInApp(url);returntrue;});CustomerIO.initialize(config);

Remove the returned subscription when the routing owner is torn down. If no replacement handler registers, later destinations wait for the readiness timeout and then use the native fallback.

The existing Linking path remains available for backward compatibility. Keep NativeCustomerIO.configureSceneDeepLinkRouting() in your SceneDelegate, then signal readiness after registering the listener:

constsubscription=Linking.addEventListener('url',({ url })=>{// Route the URL in your app.});CustomerIO.setDeepLinkRoutingReady();

Cold destinations wait up to ten seconds for this readiness signal. If it does not arrive, the SDK tries the host AppDelegate, passes a host-owned custom scheme to React Native Linking, or opens other URLs through the system.

This older path cannot acknowledge whether JavaScript handled the URL, so the SDK cannot safely fall back after it publishes a Linking event. Prefer setDeepLinkHandler for new UIScene integrations. Older React Native versions and AppDelegate-only hosts keep their existing deep-link integration.

Expo apps keep the Linking path. With config-plugin auto-initialization, register the app's Linking listener, then call CustomerIO.setDeepLinkRoutingReady() after the router is ready. The Expo plugin configures its native scene lifecycle automatically, so Expo app code does not call a native configuration method. Apps using JavaScript initialization register the router before CustomerIO.initialize; initialization marks Linking ready automatically.

This integration applies after the host has adopted React Native's UIScene lifecycle; the plugin does not replace React Native's root application lifecycle.

This release's compatibility scope is one simultaneous window scene. Multiple simultaneous React Native window scenes are not supported. React Native's URL notification is process-wide, so SDK-published destinations may reach every connected React Native instance rather than one selected window.


🔴 Live Activities

Enable the activity types you use under the liveNotifications key of your SDK config. On iOS, also add NSSupportsLiveActivities to your app's Info.plist, the liveactivities pod subspec, and a Widget Extension that renders the SDK's built-in templates. Without the Info.plist key, iOS refuses to start any activity:

<key>NSSupportsLiveActivities</key>
<true/>

One manual step is required on iOS. Forward every opened URL to the SDK from the host's active lifecycle, or taps on a Live Activity are not attributed. NativeLiveActivities comes from the wrapper pod, so import it. This only compiles once the liveactivities subspec is installed.

For an AppDelegate host:

import customerio_reactnative
import React
extensionAppDelegate{func application(_ app:UIApplication, open url:URL, options:[UIApplication.OpenURLOptionsKey:Any]=[:])->Bool{
// Reports an `opened` metric and returns the deep link to route to. A non-Customer.io URL comes
// back unchanged; `nil` means the activity carried no deep link, so there is nothing to open.
guardlet routableUrl =NativeLiveActivities.handleWidgetUrl(url)else{returntrue}returnRCTLinkingManager.application(app, open: routableUrl, options: options)}}

A React Native AppDelegate conforms to UIApplicationDelegate directly rather than subclassing, so this method is not an override, and the URL is passed on to RCTLinkingManager instead of super. See the sample app's AppDelegate.swift for this in context.

For a UIScene host, handle both lifecycle paths. At scene connection, pass the connection options through the wrapper so a cold Live Activity tap is attributed and its destination enters the acknowledged Customer.io router. Ordinary app links remain in React Native launch options for Linking:

import customerio_reactnative
reactNativeFactory?.startReactNative(
withModuleName:"YourApp",
in: window,
launchOptions:NativeLiveActivities.reactNativeLaunchOptions(from: connectionOptions))

Then replace the ordinary React Native URL-forwarding body in the existing SceneDelegate for warm opens. This reports Live Activity taps, sends Customer.io destinations through the configured Customer.io router, and forwards ordinary app links to React Native Linking:

import customerio_reactnative
func scene(_ scene:UIScene, openURLContexts URLContexts:Set<UIOpenURLContext>){forcontextin URLContexts {NativeLiveActivities.handleAndRouteWidgetUrl(context.url)}}

Do not also pass those URLs to RCTLinkingManager. The helper already routes them through the Customer.io bridge, and forwarding them again can deliver the same URL twice.

Android needs no equivalent native step.

For Expo Router, unwrap the URL once in the app's top-level app/+native-intent.tsx file:

import{CustomerIO}from'customerio-reactnative';exportasyncfunctionredirectSystemPath({ path }: {path: string}){returnCustomerIO.liveActivities.handleWidgetUrl(path);}

This reports the opened event, returns the customer's destination, preserves ordinary URLs, and returns null for a Customer.io tracking URL without a destination. Do not also call the helper from a Linking listener because processing the same tracking URL twice reports two opened events. Expo apps without Expo Router can apply the helper once in their central Linking initial-URL and subscription pipeline.


Identify Users, Track Events, and More

Customer.io helps you personalize your mobile experience:


Contributing

We welcome contributions! To get started:

  1. Review our example app to help with local development.
  2. Follow our Code of Conduct.

License

MIT

About

No description, website, or topics provided.

Resources

Code of conduct

Stars

40 stars

Watchers

22 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Latest commit

History

515 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

npm versionnpm downloadsmin Android SDK version is 21min iOS version is 13min Swift version is 5.3Contributor Covenant

Customer.io React Native SDK

The official Customer.io SDK for React Native enables you to integrate mobile messaging features—like in-app messaging and push notifications—into your app. These features can be triggered by events sent through the SDK and configured using your Customer.io campaigns.

📖 Full documentation: customer.io/docs/sdk/react-native
🧪 Example apps: see the /example directory


Installation

npm install customerio-reactnative

If a CocoaPods build reports that a generated dependency target is below the deployment range supported by Xcode, follow the deployment-target normalization guide.


SDK Initialization

Here’s a simplified example showing how to initialize the SDK to enable most mobile features:

import{CustomerIO,CioConfig,CioLogLevel,CioRegion,}from'customerio-reactnative';useEffect(()=>{constconfig: CioConfig={cdpApiKey: 'your-cdp-api-key',// RequiredmigrationSiteId: 'your-site-id',// Optional, for migrating from older SDKsregion: CioRegion.US,// Or CioRegion.EUlogLevel: CioLogLevel.debug,trackApplicationLifecycleEvents: true,inApp: {siteId: 'your-site-id',// Required for in-app messaging},push: {android: {pushClickBehavior: 'ActivityPreventRestart',// Optional},},};CustomerIO.initialize(config);},[]);

🔑 For help finding your credentials, check out the Quick Start Guide.


📲 Push Notifications

This SDK supports rich push notifications using Firebase (for Android) and either Firebase or APNs (for iOS). Follow our push setup guide to configure your project for push.

On iOS, a React Native 0.88+ UIScene host using the acknowledged handler must declare that ownership before React Native starts. This API is not used on Android or by AppDelegate-only hosts. Call this first in scene(_:willConnectTo:options:) so cold destinations wait for the JavaScript handler instead of entering the legacy Linking path:

NativeCustomerIO.configureAcknowledgedSceneDeepLinkRouting()

Register the app's Customer.io deep-link handler before calling CustomerIO.initialize. Return true after routing a URL. Return false to let the native SDK try the host AppDelegate, then pass a host-owned custom scheme to React Native Linking or open other URLs through the system. If your handler might decline an app-owned custom scheme, keep a Linking URL listener registered for that fallback. A thrown error, rejected promise, missing handler, or handler timeout follows the same native fallback. Cold URLs wait up to ten seconds for registration and are replayed once the handler is ready. After delivery, the handler has ten seconds to settle. If it takes longer, native fallback runs; a handler that later routes the URL can cause a second navigation because its acknowledgement cannot cancel the fallback.

constsubscription=CustomerIO.setDeepLinkHandler(async(url)=>{if(!canRouteInApp(url)){returnfalse;}awaitrouteInApp(url);returntrue;});CustomerIO.initialize(config);

Remove the returned subscription when the routing owner is torn down. If no replacement handler registers, later destinations wait for the readiness timeout and then use the native fallback.

The existing Linking path remains available for backward compatibility. Keep NativeCustomerIO.configureSceneDeepLinkRouting() in your SceneDelegate, then signal readiness after registering the listener:

constsubscription=Linking.addEventListener('url',({ url })=>{// Route the URL in your app.});CustomerIO.setDeepLinkRoutingReady();

Cold destinations wait up to ten seconds for this readiness signal. If it does not arrive, the SDK tries the host AppDelegate, passes a host-owned custom scheme to React Native Linking, or opens other URLs through the system.

This older path cannot acknowledge whether JavaScript handled the URL, so the SDK cannot safely fall back after it publishes a Linking event. Prefer setDeepLinkHandler for new UIScene integrations. Older React Native versions and AppDelegate-only hosts keep their existing deep-link integration.

Expo apps keep the Linking path. With config-plugin auto-initialization, register the app's Linking listener, then call CustomerIO.setDeepLinkRoutingReady() after the router is ready. The Expo plugin configures its native scene lifecycle automatically, so Expo app code does not call a native configuration method. Apps using JavaScript initialization register the router before CustomerIO.initialize; initialization marks Linking ready automatically.

This integration applies after the host has adopted React Native's UIScene lifecycle; the plugin does not replace React Native's root application lifecycle.

This release's compatibility scope is one simultaneous window scene. Multiple simultaneous React Native window scenes are not supported. React Native's URL notification is process-wide, so SDK-published destinations may reach every connected React Native instance rather than one selected window.


🔴 Live Activities

Enable the activity types you use under the liveNotifications key of your SDK config. On iOS, also add NSSupportsLiveActivities to your app's Info.plist, the liveactivities pod subspec, and a Widget Extension that renders the SDK's built-in templates. Without the Info.plist key, iOS refuses to start any activity:

<key>NSSupportsLiveActivities</key>
<true/>

One manual step is required on iOS. Forward every opened URL to the SDK from the host's active lifecycle, or taps on a Live Activity are not attributed. NativeLiveActivities comes from the wrapper pod, so import it. This only compiles once the liveactivities subspec is installed.

For an AppDelegate host:

import customerio_reactnative
import React
extensionAppDelegate{func application(_ app:UIApplication, open url:URL, options:[UIApplication.OpenURLOptionsKey:Any]=[:])->Bool{
// Reports an `opened` metric and returns the deep link to route to. A non-Customer.io URL comes
// back unchanged; `nil` means the activity carried no deep link, so there is nothing to open.
guardlet routableUrl =NativeLiveActivities.handleWidgetUrl(url)else{returntrue}returnRCTLinkingManager.application(app, open: routableUrl, options: options)}}

A React Native AppDelegate conforms to UIApplicationDelegate directly rather than subclassing, so this method is not an override, and the URL is passed on to RCTLinkingManager instead of super. See the sample app's AppDelegate.swift for this in context.

For a UIScene host, handle both lifecycle paths. At scene connection, pass the connection options through the wrapper so a cold Live Activity tap is attributed and its destination enters the acknowledged Customer.io router. Ordinary app links remain in React Native launch options for Linking:

import customerio_reactnative
reactNativeFactory?.startReactNative(
withModuleName:"YourApp",
in: window,
launchOptions:NativeLiveActivities.reactNativeLaunchOptions(from: connectionOptions))

Then replace the ordinary React Native URL-forwarding body in the existing SceneDelegate for warm opens. This reports Live Activity taps, sends Customer.io destinations through the configured Customer.io router, and forwards ordinary app links to React Native Linking:

import customerio_reactnative
func scene(_ scene:UIScene, openURLContexts URLContexts:Set<UIOpenURLContext>){forcontextin URLContexts {NativeLiveActivities.handleAndRouteWidgetUrl(context.url)}}

Do not also pass those URLs to RCTLinkingManager. The helper already routes them through the Customer.io bridge, and forwarding them again can deliver the same URL twice.

Android needs no equivalent native step.

For Expo Router, unwrap the URL once in the app's top-level app/+native-intent.tsx file:

import{CustomerIO}from'customerio-reactnative';exportasyncfunctionredirectSystemPath({ path }: {path: string}){returnCustomerIO.liveActivities.handleWidgetUrl(path);}

This reports the opened event, returns the customer's destination, preserves ordinary URLs, and returns null for a Customer.io tracking URL without a destination. Do not also call the helper from a Linking listener because processing the same tracking URL twice reports two opened events. Expo apps without Expo Router can apply the helper once in their central Linking initial-URL and subscription pipeline.


Identify Users, Track Events, and More

Customer.io helps you personalize your mobile experience:


Contributing

We welcome contributions! To get started:

  1. Review our example app to help with local development.
  2. Follow our Code of Conduct.

License

MIT

About

No description, website, or topics provided.

Resources

Code of conduct

Stars

40 stars

Watchers

22 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Latest commit

History

515 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

npm versionnpm downloadsmin Android SDK version is 21min iOS version is 13min Swift version is 5.3Contributor Covenant

Customer.io React Native SDK

The official Customer.io SDK for React Native enables you to integrate mobile messaging features—like in-app messaging and push notifications—into your app. These features can be triggered by events sent through the SDK and configured using your Customer.io campaigns.

📖 Full documentation: customer.io/docs/sdk/react-native
🧪 Example apps: see the /example directory


Installation

npm install customerio-reactnative

If a CocoaPods build reports that a generated dependency target is below the deployment range supported by Xcode, follow the deployment-target normalization guide.


SDK Initialization

Here’s a simplified example showing how to initialize the SDK to enable most mobile features:

import{CustomerIO,CioConfig,CioLogLevel,CioRegion,}from'customerio-reactnative';useEffect(()=>{constconfig: CioConfig={cdpApiKey: 'your-cdp-api-key',// RequiredmigrationSiteId: 'your-site-id',// Optional, for migrating from older SDKsregion: CioRegion.US,// Or CioRegion.EUlogLevel: CioLogLevel.debug,trackApplicationLifecycleEvents: true,inApp: {siteId: 'your-site-id',// Required for in-app messaging},push: {android: {pushClickBehavior: 'ActivityPreventRestart',// Optional},},};CustomerIO.initialize(config);},[]);

🔑 For help finding your credentials, check out the Quick Start Guide.


📲 Push Notifications

This SDK supports rich push notifications using Firebase (for Android) and either Firebase or APNs (for iOS). Follow our push setup guide to configure your project for push.

On iOS, a React Native 0.88+ UIScene host using the acknowledged handler must declare that ownership before React Native starts. This API is not used on Android or by AppDelegate-only hosts. Call this first in scene(_:willConnectTo:options:) so cold destinations wait for the JavaScript handler instead of entering the legacy Linking path:

NativeCustomerIO.configureAcknowledgedSceneDeepLinkRouting()

Register the app's Customer.io deep-link handler before calling CustomerIO.initialize. Return true after routing a URL. Return false to let the native SDK try the host AppDelegate, then pass a host-owned custom scheme to React Native Linking or open other URLs through the system. If your handler might decline an app-owned custom scheme, keep a Linking URL listener registered for that fallback. A thrown error, rejected promise, missing handler, or handler timeout follows the same native fallback. Cold URLs wait up to ten seconds for registration and are replayed once the handler is ready. After delivery, the handler has ten seconds to settle. If it takes longer, native fallback runs; a handler that later routes the URL can cause a second navigation because its acknowledgement cannot cancel the fallback.

constsubscription=CustomerIO.setDeepLinkHandler(async(url)=>{if(!canRouteInApp(url)){returnfalse;}awaitrouteInApp(url);returntrue;});CustomerIO.initialize(config);

Remove the returned subscription when the routing owner is torn down. If no replacement handler registers, later destinations wait for the readiness timeout and then use the native fallback.

The existing Linking path remains available for backward compatibility. Keep NativeCustomerIO.configureSceneDeepLinkRouting() in your SceneDelegate, then signal readiness after registering the listener:

constsubscription=Linking.addEventListener('url',({ url })=>{// Route the URL in your app.});CustomerIO.setDeepLinkRoutingReady();

Cold destinations wait up to ten seconds for this readiness signal. If it does not arrive, the SDK tries the host AppDelegate, passes a host-owned custom scheme to React Native Linking, or opens other URLs through the system.

This older path cannot acknowledge whether JavaScript handled the URL, so the SDK cannot safely fall back after it publishes a Linking event. Prefer setDeepLinkHandler for new UIScene integrations. Older React Native versions and AppDelegate-only hosts keep their existing deep-link integration.

Expo apps keep the Linking path. With config-plugin auto-initialization, register the app's Linking listener, then call CustomerIO.setDeepLinkRoutingReady() after the router is ready. The Expo plugin configures its native scene lifecycle automatically, so Expo app code does not call a native configuration method. Apps using JavaScript initialization register the router before CustomerIO.initialize; initialization marks Linking ready automatically.

This integration applies after the host has adopted React Native's UIScene lifecycle; the plugin does not replace React Native's root application lifecycle.

This release's compatibility scope is one simultaneous window scene. Multiple simultaneous React Native window scenes are not supported. React Native's URL notification is process-wide, so SDK-published destinations may reach every connected React Native instance rather than one selected window.


🔴 Live Activities

Enable the activity types you use under the liveNotifications key of your SDK config. On iOS, also add NSSupportsLiveActivities to your app's Info.plist, the liveactivities pod subspec, and a Widget Extension that renders the SDK's built-in templates. Without the Info.plist key, iOS refuses to start any activity:

<key>NSSupportsLiveActivities</key>
<true/>

One manual step is required on iOS. Forward every opened URL to the SDK from the host's active lifecycle, or taps on a Live Activity are not attributed. NativeLiveActivities comes from the wrapper pod, so import it. This only compiles once the liveactivities subspec is installed.

For an AppDelegate host:

import customerio_reactnative
import React
extensionAppDelegate{func application(_ app:UIApplication, open url:URL, options:[UIApplication.OpenURLOptionsKey:Any]=[:])->Bool{
// Reports an `opened` metric and returns the deep link to route to. A non-Customer.io URL comes
// back unchanged; `nil` means the activity carried no deep link, so there is nothing to open.
guardlet routableUrl =NativeLiveActivities.handleWidgetUrl(url)else{returntrue}returnRCTLinkingManager.application(app, open: routableUrl, options: options)}}

A React Native AppDelegate conforms to UIApplicationDelegate directly rather than subclassing, so this method is not an override, and the URL is passed on to RCTLinkingManager instead of super. See the sample app's AppDelegate.swift for this in context.

For a UIScene host, handle both lifecycle paths. At scene connection, pass the connection options through the wrapper so a cold Live Activity tap is attributed and its destination enters the acknowledged Customer.io router. Ordinary app links remain in React Native launch options for Linking:

import customerio_reactnative
reactNativeFactory?.startReactNative(
withModuleName:"YourApp",
in: window,
launchOptions:NativeLiveActivities.reactNativeLaunchOptions(from: connectionOptions))

Then replace the ordinary React Native URL-forwarding body in the existing SceneDelegate for warm opens. This reports Live Activity taps, sends Customer.io destinations through the configured Customer.io router, and forwards ordinary app links to React Native Linking:

import customerio_reactnative
func scene(_ scene:UIScene, openURLContexts URLContexts:Set<UIOpenURLContext>){forcontextin URLContexts {NativeLiveActivities.handleAndRouteWidgetUrl(context.url)}}

Do not also pass those URLs to RCTLinkingManager. The helper already routes them through the Customer.io bridge, and forwarding them again can deliver the same URL twice.

Android needs no equivalent native step.

For Expo Router, unwrap the URL once in the app's top-level app/+native-intent.tsx file:

import{CustomerIO}from'customerio-reactnative';exportasyncfunctionredirectSystemPath({ path }: {path: string}){returnCustomerIO.liveActivities.handleWidgetUrl(path);}

This reports the opened event, returns the customer's destination, preserves ordinary URLs, and returns null for a Customer.io tracking URL without a destination. Do not also call the helper from a Linking listener because processing the same tracking URL twice reports two opened events. Expo apps without Expo Router can apply the helper once in their central Linking initial-URL and subscription pipeline.


Identify Users, Track Events, and More

Customer.io helps you personalize your mobile experience:


Contributing

We welcome contributions! To get started:

  1. Review our example app to help with local development.
  2. Follow our Code of Conduct.

License

MIT

About

No description, website, or topics provided.

Resources

Code of conduct

Stars

40 stars

Watchers

22 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Latest commit

History

515 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

npm versionnpm downloadsmin Android SDK version is 21min iOS version is 13min Swift version is 5.3Contributor Covenant

Customer.io React Native SDK

The official Customer.io SDK for React Native enables you to integrate mobile messaging features—like in-app messaging and push notifications—into your app. These features can be triggered by events sent through the SDK and configured using your Customer.io campaigns.

📖 Full documentation: customer.io/docs/sdk/react-native
🧪 Example apps: see the /example directory


Installation

npm install customerio-reactnative

If a CocoaPods build reports that a generated dependency target is below the deployment range supported by Xcode, follow the deployment-target normalization guide.


SDK Initialization

Here’s a simplified example showing how to initialize the SDK to enable most mobile features:

import{CustomerIO,CioConfig,CioLogLevel,CioRegion,}from'customerio-reactnative';useEffect(()=>{constconfig: CioConfig={cdpApiKey: 'your-cdp-api-key',// RequiredmigrationSiteId: 'your-site-id',// Optional, for migrating from older SDKsregion: CioRegion.US,// Or CioRegion.EUlogLevel: CioLogLevel.debug,trackApplicationLifecycleEvents: true,inApp: {siteId: 'your-site-id',// Required for in-app messaging},push: {android: {pushClickBehavior: 'ActivityPreventRestart',// Optional},},};CustomerIO.initialize(config);},[]);

🔑 For help finding your credentials, check out the Quick Start Guide.


📲 Push Notifications

This SDK supports rich push notifications using Firebase (for Android) and either Firebase or APNs (for iOS). Follow our push setup guide to configure your project for push.

On iOS, a React Native 0.88+ UIScene host using the acknowledged handler must declare that ownership before React Native starts. This API is not used on Android or by AppDelegate-only hosts. Call this first in scene(_:willConnectTo:options:) so cold destinations wait for the JavaScript handler instead of entering the legacy Linking path:

NativeCustomerIO.configureAcknowledgedSceneDeepLinkRouting()

Register the app's Customer.io deep-link handler before calling CustomerIO.initialize. Return true after routing a URL. Return false to let the native SDK try the host AppDelegate, then pass a host-owned custom scheme to React Native Linking or open other URLs through the system. If your handler might decline an app-owned custom scheme, keep a Linking URL listener registered for that fallback. A thrown error, rejected promise, missing handler, or handler timeout follows the same native fallback. Cold URLs wait up to ten seconds for registration and are replayed once the handler is ready. After delivery, the handler has ten seconds to settle. If it takes longer, native fallback runs; a handler that later routes the URL can cause a second navigation because its acknowledgement cannot cancel the fallback.

constsubscription=CustomerIO.setDeepLinkHandler(async(url)=>{if(!canRouteInApp(url)){returnfalse;}awaitrouteInApp(url);returntrue;});CustomerIO.initialize(config);

Remove the returned subscription when the routing owner is torn down. If no replacement handler registers, later destinations wait for the readiness timeout and then use the native fallback.

The existing Linking path remains available for backward compatibility. Keep NativeCustomerIO.configureSceneDeepLinkRouting() in your SceneDelegate, then signal readiness after registering the listener:

constsubscription=Linking.addEventListener('url',({ url })=>{// Route the URL in your app.});CustomerIO.setDeepLinkRoutingReady();

Cold destinations wait up to ten seconds for this readiness signal. If it does not arrive, the SDK tries the host AppDelegate, passes a host-owned custom scheme to React Native Linking, or opens other URLs through the system.

This older path cannot acknowledge whether JavaScript handled the URL, so the SDK cannot safely fall back after it publishes a Linking event. Prefer setDeepLinkHandler for new UIScene integrations. Older React Native versions and AppDelegate-only hosts keep their existing deep-link integration.

Expo apps keep the Linking path. With config-plugin auto-initialization, register the app's Linking listener, then call CustomerIO.setDeepLinkRoutingReady() after the router is ready. The Expo plugin configures its native scene lifecycle automatically, so Expo app code does not call a native configuration method. Apps using JavaScript initialization register the router before CustomerIO.initialize; initialization marks Linking ready automatically.

This integration applies after the host has adopted React Native's UIScene lifecycle; the plugin does not replace React Native's root application lifecycle.

This release's compatibility scope is one simultaneous window scene. Multiple simultaneous React Native window scenes are not supported. React Native's URL notification is process-wide, so SDK-published destinations may reach every connected React Native instance rather than one selected window.


🔴 Live Activities

Enable the activity types you use under the liveNotifications key of your SDK config. On iOS, also add NSSupportsLiveActivities to your app's Info.plist, the liveactivities pod subspec, and a Widget Extension that renders the SDK's built-in templates. Without the Info.plist key, iOS refuses to start any activity:

<key>NSSupportsLiveActivities</key>
<true/>

One manual step is required on iOS. Forward every opened URL to the SDK from the host's active lifecycle, or taps on a Live Activity are not attributed. NativeLiveActivities comes from the wrapper pod, so import it. This only compiles once the liveactivities subspec is installed.

For an AppDelegate host:

import customerio_reactnative
import React
extensionAppDelegate{func application(_ app:UIApplication, open url:URL, options:[UIApplication.OpenURLOptionsKey:Any]=[:])->Bool{
// Reports an `opened` metric and returns the deep link to route to. A non-Customer.io URL comes
// back unchanged; `nil` means the activity carried no deep link, so there is nothing to open.
guardlet routableUrl =NativeLiveActivities.handleWidgetUrl(url)else{returntrue}returnRCTLinkingManager.application(app, open: routableUrl, options: options)}}

A React Native AppDelegate conforms to UIApplicationDelegate directly rather than subclassing, so this method is not an override, and the URL is passed on to RCTLinkingManager instead of super. See the sample app's AppDelegate.swift for this in context.

For a UIScene host, handle both lifecycle paths. At scene connection, pass the connection options through the wrapper so a cold Live Activity tap is attributed and its destination enters the acknowledged Customer.io router. Ordinary app links remain in React Native launch options for Linking:

import customerio_reactnative
reactNativeFactory?.startReactNative(
withModuleName:"YourApp",
in: window,
launchOptions:NativeLiveActivities.reactNativeLaunchOptions(from: connectionOptions))

Then replace the ordinary React Native URL-forwarding body in the existing SceneDelegate for warm opens. This reports Live Activity taps, sends Customer.io destinations through the configured Customer.io router, and forwards ordinary app links to React Native Linking:

import customerio_reactnative
func scene(_ scene:UIScene, openURLContexts URLContexts:Set<UIOpenURLContext>){forcontextin URLContexts {NativeLiveActivities.handleAndRouteWidgetUrl(context.url)}}

Do not also pass those URLs to RCTLinkingManager. The helper already routes them through the Customer.io bridge, and forwarding them again can deliver the same URL twice.

Android needs no equivalent native step.

For Expo Router, unwrap the URL once in the app's top-level app/+native-intent.tsx file:

import{CustomerIO}from'customerio-reactnative';exportasyncfunctionredirectSystemPath({ path }: {path: string}){returnCustomerIO.liveActivities.handleWidgetUrl(path);}

This reports the opened event, returns the customer's destination, preserves ordinary URLs, and returns null for a Customer.io tracking URL without a destination. Do not also call the helper from a Linking listener because processing the same tracking URL twice reports two opened events. Expo apps without Expo Router can apply the helper once in their central Linking initial-URL and subscription pipeline.


Identify Users, Track Events, and More

Customer.io helps you personalize your mobile experience:


Contributing

We welcome contributions! To get started:

  1. Review our example app to help with local development.
  2. Follow our Code of Conduct.

License

MIT

About

No description, website, or topics provided.

Resources

Code of conduct

Stars

40 stars

Watchers

22 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Latest commit

History

515 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

npm versionnpm downloadsmin Android SDK version is 21min iOS version is 13min Swift version is 5.3Contributor Covenant

Customer.io React Native SDK

The official Customer.io SDK for React Native enables you to integrate mobile messaging features—like in-app messaging and push notifications—into your app. These features can be triggered by events sent through the SDK and configured using your Customer.io campaigns.

📖 Full documentation: customer.io/docs/sdk/react-native
🧪 Example apps: see the /example directory


Installation

npm install customerio-reactnative

If a CocoaPods build reports that a generated dependency target is below the deployment range supported by Xcode, follow the deployment-target normalization guide.


SDK Initialization

Here’s a simplified example showing how to initialize the SDK to enable most mobile features:

import{CustomerIO,CioConfig,CioLogLevel,CioRegion,}from'customerio-reactnative';useEffect(()=>{constconfig: CioConfig={cdpApiKey: 'your-cdp-api-key',// RequiredmigrationSiteId: 'your-site-id',// Optional, for migrating from older SDKsregion: CioRegion.US,// Or CioRegion.EUlogLevel: CioLogLevel.debug,trackApplicationLifecycleEvents: true,inApp: {siteId: 'your-site-id',// Required for in-app messaging},push: {android: {pushClickBehavior: 'ActivityPreventRestart',// Optional},},};CustomerIO.initialize(config);},[]);

🔑 For help finding your credentials, check out the Quick Start Guide.


📲 Push Notifications

This SDK supports rich push notifications using Firebase (for Android) and either Firebase or APNs (for iOS). Follow our push setup guide to configure your project for push.

On iOS, a React Native 0.88+ UIScene host using the acknowledged handler must declare that ownership before React Native starts. This API is not used on Android or by AppDelegate-only hosts. Call this first in scene(_:willConnectTo:options:) so cold destinations wait for the JavaScript handler instead of entering the legacy Linking path:

NativeCustomerIO.configureAcknowledgedSceneDeepLinkRouting()

Register the app's Customer.io deep-link handler before calling CustomerIO.initialize. Return true after routing a URL. Return false to let the native SDK try the host AppDelegate, then pass a host-owned custom scheme to React Native Linking or open other URLs through the system. If your handler might decline an app-owned custom scheme, keep a Linking URL listener registered for that fallback. A thrown error, rejected promise, missing handler, or handler timeout follows the same native fallback. Cold URLs wait up to ten seconds for registration and are replayed once the handler is ready. After delivery, the handler has ten seconds to settle. If it takes longer, native fallback runs; a handler that later routes the URL can cause a second navigation because its acknowledgement cannot cancel the fallback.

constsubscription=CustomerIO.setDeepLinkHandler(async(url)=>{if(!canRouteInApp(url)){returnfalse;}awaitrouteInApp(url);returntrue;});CustomerIO.initialize(config);

Remove the returned subscription when the routing owner is torn down. If no replacement handler registers, later destinations wait for the readiness timeout and then use the native fallback.

The existing Linking path remains available for backward compatibility. Keep NativeCustomerIO.configureSceneDeepLinkRouting() in your SceneDelegate, then signal readiness after registering the listener:

constsubscription=Linking.addEventListener('url',({ url })=>{// Route the URL in your app.});CustomerIO.setDeepLinkRoutingReady();

Cold destinations wait up to ten seconds for this readiness signal. If it does not arrive, the SDK tries the host AppDelegate, passes a host-owned custom scheme to React Native Linking, or opens other URLs through the system.

This older path cannot acknowledge whether JavaScript handled the URL, so the SDK cannot safely fall back after it publishes a Linking event. Prefer setDeepLinkHandler for new UIScene integrations. Older React Native versions and AppDelegate-only hosts keep their existing deep-link integration.

Expo apps keep the Linking path. With config-plugin auto-initialization, register the app's Linking listener, then call CustomerIO.setDeepLinkRoutingReady() after the router is ready. The Expo plugin configures its native scene lifecycle automatically, so Expo app code does not call a native configuration method. Apps using JavaScript initialization register the router before CustomerIO.initialize; initialization marks Linking ready automatically.

This integration applies after the host has adopted React Native's UIScene lifecycle; the plugin does not replace React Native's root application lifecycle.

This release's compatibility scope is one simultaneous window scene. Multiple simultaneous React Native window scenes are not supported. React Native's URL notification is process-wide, so SDK-published destinations may reach every connected React Native instance rather than one selected window.


🔴 Live Activities

Enable the activity types you use under the liveNotifications key of your SDK config. On iOS, also add NSSupportsLiveActivities to your app's Info.plist, the liveactivities pod subspec, and a Widget Extension that renders the SDK's built-in templates. Without the Info.plist key, iOS refuses to start any activity:

<key>NSSupportsLiveActivities</key>
<true/>

One manual step is required on iOS. Forward every opened URL to the SDK from the host's active lifecycle, or taps on a Live Activity are not attributed. NativeLiveActivities comes from the wrapper pod, so import it. This only compiles once the liveactivities subspec is installed.

For an AppDelegate host:

import customerio_reactnative
import React
extensionAppDelegate{func application(_ app:UIApplication, open url:URL, options:[UIApplication.OpenURLOptionsKey:Any]=[:])->Bool{
// Reports an `opened` metric and returns the deep link to route to. A non-Customer.io URL comes
// back unchanged; `nil` means the activity carried no deep link, so there is nothing to open.
guardlet routableUrl =NativeLiveActivities.handleWidgetUrl(url)else{returntrue}returnRCTLinkingManager.application(app, open: routableUrl, options: options)}}

A React Native AppDelegate conforms to UIApplicationDelegate directly rather than subclassing, so this method is not an override, and the URL is passed on to RCTLinkingManager instead of super. See the sample app's AppDelegate.swift for this in context.

For a UIScene host, handle both lifecycle paths. At scene connection, pass the connection options through the wrapper so a cold Live Activity tap is attributed and its destination enters the acknowledged Customer.io router. Ordinary app links remain in React Native launch options for Linking:

import customerio_reactnative
reactNativeFactory?.startReactNative(
withModuleName:"YourApp",
in: window,
launchOptions:NativeLiveActivities.reactNativeLaunchOptions(from: connectionOptions))

Then replace the ordinary React Native URL-forwarding body in the existing SceneDelegate for warm opens. This reports Live Activity taps, sends Customer.io destinations through the configured Customer.io router, and forwards ordinary app links to React Native Linking:

import customerio_reactnative
func scene(_ scene:UIScene, openURLContexts URLContexts:Set<UIOpenURLContext>){forcontextin URLContexts {NativeLiveActivities.handleAndRouteWidgetUrl(context.url)}}

Do not also pass those URLs to RCTLinkingManager. The helper already routes them through the Customer.io bridge, and forwarding them again can deliver the same URL twice.

Android needs no equivalent native step.

For Expo Router, unwrap the URL once in the app's top-level app/+native-intent.tsx file:

import{CustomerIO}from'customerio-reactnative';exportasyncfunctionredirectSystemPath({ path }: {path: string}){returnCustomerIO.liveActivities.handleWidgetUrl(path);}

This reports the opened event, returns the customer's destination, preserves ordinary URLs, and returns null for a Customer.io tracking URL without a destination. Do not also call the helper from a Linking listener because processing the same tracking URL twice reports two opened events. Expo apps without Expo Router can apply the helper once in their central Linking initial-URL and subscription pipeline.


Identify Users, Track Events, and More

Customer.io helps you personalize your mobile experience:


Contributing

We welcome contributions! To get started:

  1. Review our example app to help with local development.
  2. Follow our Code of Conduct.

License

MIT

About

No description, website, or topics provided.

Resources

Code of conduct

Stars

40 stars

Watchers

22 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Latest commit

History

515 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

npm versionnpm downloadsmin Android SDK version is 21min iOS version is 13min Swift version is 5.3Contributor Covenant

Customer.io React Native SDK

The official Customer.io SDK for React Native enables you to integrate mobile messaging features—like in-app messaging and push notifications—into your app. These features can be triggered by events sent through the SDK and configured using your Customer.io campaigns.

📖 Full documentation: customer.io/docs/sdk/react-native
🧪 Example apps: see the /example directory


Installation

npm install customerio-reactnative

If a CocoaPods build reports that a generated dependency target is below the deployment range supported by Xcode, follow the deployment-target normalization guide.


SDK Initialization

Here’s a simplified example showing how to initialize the SDK to enable most mobile features:

import{CustomerIO,CioConfig,CioLogLevel,CioRegion,}from'customerio-reactnative';useEffect(()=>{constconfig: CioConfig={cdpApiKey: 'your-cdp-api-key',// RequiredmigrationSiteId: 'your-site-id',// Optional, for migrating from older SDKsregion: CioRegion.US,// Or CioRegion.EUlogLevel: CioLogLevel.debug,trackApplicationLifecycleEvents: true,inApp: {siteId: 'your-site-id',// Required for in-app messaging},push: {android: {pushClickBehavior: 'ActivityPreventRestart',// Optional},},};CustomerIO.initialize(config);},[]);

🔑 For help finding your credentials, check out the Quick Start Guide.


📲 Push Notifications

This SDK supports rich push notifications using Firebase (for Android) and either Firebase or APNs (for iOS). Follow our push setup guide to configure your project for push.

On iOS, a React Native 0.88+ UIScene host using the acknowledged handler must declare that ownership before React Native starts. This API is not used on Android or by AppDelegate-only hosts. Call this first in scene(_:willConnectTo:options:) so cold destinations wait for the JavaScript handler instead of entering the legacy Linking path:

NativeCustomerIO.configureAcknowledgedSceneDeepLinkRouting()

Register the app's Customer.io deep-link handler before calling CustomerIO.initialize. Return true after routing a URL. Return false to let the native SDK try the host AppDelegate, then pass a host-owned custom scheme to React Native Linking or open other URLs through the system. If your handler might decline an app-owned custom scheme, keep a Linking URL listener registered for that fallback. A thrown error, rejected promise, missing handler, or handler timeout follows the same native fallback. Cold URLs wait up to ten seconds for registration and are replayed once the handler is ready. After delivery, the handler has ten seconds to settle. If it takes longer, native fallback runs; a handler that later routes the URL can cause a second navigation because its acknowledgement cannot cancel the fallback.

constsubscription=CustomerIO.setDeepLinkHandler(async(url)=>{if(!canRouteInApp(url)){returnfalse;}awaitrouteInApp(url);returntrue;});CustomerIO.initialize(config);

Remove the returned subscription when the routing owner is torn down. If no replacement handler registers, later destinations wait for the readiness timeout and then use the native fallback.

The existing Linking path remains available for backward compatibility. Keep NativeCustomerIO.configureSceneDeepLinkRouting() in your SceneDelegate, then signal readiness after registering the listener:

constsubscription=Linking.addEventListener('url',({ url })=>{// Route the URL in your app.});CustomerIO.setDeepLinkRoutingReady();

Cold destinations wait up to ten seconds for this readiness signal. If it does not arrive, the SDK tries the host AppDelegate, passes a host-owned custom scheme to React Native Linking, or opens other URLs through the system.

This older path cannot acknowledge whether JavaScript handled the URL, so the SDK cannot safely fall back after it publishes a Linking event. Prefer setDeepLinkHandler for new UIScene integrations. Older React Native versions and AppDelegate-only hosts keep their existing deep-link integration.

Expo apps keep the Linking path. With config-plugin auto-initialization, register the app's Linking listener, then call CustomerIO.setDeepLinkRoutingReady() after the router is ready. The Expo plugin configures its native scene lifecycle automatically, so Expo app code does not call a native configuration method. Apps using JavaScript initialization register the router before CustomerIO.initialize; initialization marks Linking ready automatically.

This integration applies after the host has adopted React Native's UIScene lifecycle; the plugin does not replace React Native's root application lifecycle.

This release's compatibility scope is one simultaneous window scene. Multiple simultaneous React Native window scenes are not supported. React Native's URL notification is process-wide, so SDK-published destinations may reach every connected React Native instance rather than one selected window.


🔴 Live Activities

Enable the activity types you use under the liveNotifications key of your SDK config. On iOS, also add NSSupportsLiveActivities to your app's Info.plist, the liveactivities pod subspec, and a Widget Extension that renders the SDK's built-in templates. Without the Info.plist key, iOS refuses to start any activity:

<key>NSSupportsLiveActivities</key>
<true/>

One manual step is required on iOS. Forward every opened URL to the SDK from the host's active lifecycle, or taps on a Live Activity are not attributed. NativeLiveActivities comes from the wrapper pod, so import it. This only compiles once the liveactivities subspec is installed.

For an AppDelegate host:

import customerio_reactnative
import React
extensionAppDelegate{func application(_ app:UIApplication, open url:URL, options:[UIApplication.OpenURLOptionsKey:Any]=[:])->Bool{
// Reports an `opened` metric and returns the deep link to route to. A non-Customer.io URL comes
// back unchanged; `nil` means the activity carried no deep link, so there is nothing to open.
guardlet routableUrl =NativeLiveActivities.handleWidgetUrl(url)else{returntrue}returnRCTLinkingManager.application(app, open: routableUrl, options: options)}}

A React Native AppDelegate conforms to UIApplicationDelegate directly rather than subclassing, so this method is not an override, and the URL is passed on to RCTLinkingManager instead of super. See the sample app's AppDelegate.swift for this in context.

For a UIScene host, handle both lifecycle paths. At scene connection, pass the connection options through the wrapper so a cold Live Activity tap is attributed and its destination enters the acknowledged Customer.io router. Ordinary app links remain in React Native launch options for Linking:

import customerio_reactnative
reactNativeFactory?.startReactNative(
withModuleName:"YourApp",
in: window,
launchOptions:NativeLiveActivities.reactNativeLaunchOptions(from: connectionOptions))

Then replace the ordinary React Native URL-forwarding body in the existing SceneDelegate for warm opens. This reports Live Activity taps, sends Customer.io destinations through the configured Customer.io router, and forwards ordinary app links to React Native Linking:

import customerio_reactnative
func scene(_ scene:UIScene, openURLContexts URLContexts:Set<UIOpenURLContext>){forcontextin URLContexts {NativeLiveActivities.handleAndRouteWidgetUrl(context.url)}}

Do not also pass those URLs to RCTLinkingManager. The helper already routes them through the Customer.io bridge, and forwarding them again can deliver the same URL twice.

Android needs no equivalent native step.

For Expo Router, unwrap the URL once in the app's top-level app/+native-intent.tsx file:

import{CustomerIO}from'customerio-reactnative';exportasyncfunctionredirectSystemPath({ path }: {path: string}){returnCustomerIO.liveActivities.handleWidgetUrl(path);}

This reports the opened event, returns the customer's destination, preserves ordinary URLs, and returns null for a Customer.io tracking URL without a destination. Do not also call the helper from a Linking listener because processing the same tracking URL twice reports two opened events. Expo apps without Expo Router can apply the helper once in their central Linking initial-URL and subscription pipeline.


Identify Users, Track Events, and More

Customer.io helps you personalize your mobile experience:


Contributing

We welcome contributions! To get started:

  1. Review our example app to help with local development.
  2. Follow our Code of Conduct.

License

MIT

About

No description, website, or topics provided.

Resources

Code of conduct

Stars

40 stars

Watchers

22 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Latest commit

History

515 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

npm versionnpm downloadsmin Android SDK version is 21min iOS version is 13min Swift version is 5.3Contributor Covenant

Customer.io React Native SDK

The official Customer.io SDK for React Native enables you to integrate mobile messaging features—like in-app messaging and push notifications—into your app. These features can be triggered by events sent through the SDK and configured using your Customer.io campaigns.

📖 Full documentation: customer.io/docs/sdk/react-native
🧪 Example apps: see the /example directory


Installation

npm install customerio-reactnative

If a CocoaPods build reports that a generated dependency target is below the deployment range supported by Xcode, follow the deployment-target normalization guide.


SDK Initialization

Here’s a simplified example showing how to initialize the SDK to enable most mobile features:

import{CustomerIO,CioConfig,CioLogLevel,CioRegion,}from'customerio-reactnative';useEffect(()=>{constconfig: CioConfig={cdpApiKey: 'your-cdp-api-key',// RequiredmigrationSiteId: 'your-site-id',// Optional, for migrating from older SDKsregion: CioRegion.US,// Or CioRegion.EUlogLevel: CioLogLevel.debug,trackApplicationLifecycleEvents: true,inApp: {siteId: 'your-site-id',// Required for in-app messaging},push: {android: {pushClickBehavior: 'ActivityPreventRestart',// Optional},},};CustomerIO.initialize(config);},[]);

🔑 For help finding your credentials, check out the Quick Start Guide.


📲 Push Notifications

This SDK supports rich push notifications using Firebase (for Android) and either Firebase or APNs (for iOS). Follow our push setup guide to configure your project for push.

On iOS, a React Native 0.88+ UIScene host using the acknowledged handler must declare that ownership before React Native starts. This API is not used on Android or by AppDelegate-only hosts. Call this first in scene(_:willConnectTo:options:) so cold destinations wait for the JavaScript handler instead of entering the legacy Linking path:

NativeCustomerIO.configureAcknowledgedSceneDeepLinkRouting()

Register the app's Customer.io deep-link handler before calling CustomerIO.initialize. Return true after routing a URL. Return false to let the native SDK try the host AppDelegate, then pass a host-owned custom scheme to React Native Linking or open other URLs through the system. If your handler might decline an app-owned custom scheme, keep a Linking URL listener registered for that fallback. A thrown error, rejected promise, missing handler, or handler timeout follows the same native fallback. Cold URLs wait up to ten seconds for registration and are replayed once the handler is ready. After delivery, the handler has ten seconds to settle. If it takes longer, native fallback runs; a handler that later routes the URL can cause a second navigation because its acknowledgement cannot cancel the fallback.

constsubscription=CustomerIO.setDeepLinkHandler(async(url)=>{if(!canRouteInApp(url)){returnfalse;}awaitrouteInApp(url);returntrue;});CustomerIO.initialize(config);

Remove the returned subscription when the routing owner is torn down. If no replacement handler registers, later destinations wait for the readiness timeout and then use the native fallback.

The existing Linking path remains available for backward compatibility. Keep NativeCustomerIO.configureSceneDeepLinkRouting() in your SceneDelegate, then signal readiness after registering the listener:

constsubscription=Linking.addEventListener('url',({ url })=>{// Route the URL in your app.});CustomerIO.setDeepLinkRoutingReady();

Cold destinations wait up to ten seconds for this readiness signal. If it does not arrive, the SDK tries the host AppDelegate, passes a host-owned custom scheme to React Native Linking, or opens other URLs through the system.

This older path cannot acknowledge whether JavaScript handled the URL, so the SDK cannot safely fall back after it publishes a Linking event. Prefer setDeepLinkHandler for new UIScene integrations. Older React Native versions and AppDelegate-only hosts keep their existing deep-link integration.

Expo apps keep the Linking path. With config-plugin auto-initialization, register the app's Linking listener, then call CustomerIO.setDeepLinkRoutingReady() after the router is ready. The Expo plugin configures its native scene lifecycle automatically, so Expo app code does not call a native configuration method. Apps using JavaScript initialization register the router before CustomerIO.initialize; initialization marks Linking ready automatically.

This integration applies after the host has adopted React Native's UIScene lifecycle; the plugin does not replace React Native's root application lifecycle.

This release's compatibility scope is one simultaneous window scene. Multiple simultaneous React Native window scenes are not supported. React Native's URL notification is process-wide, so SDK-published destinations may reach every connected React Native instance rather than one selected window.


🔴 Live Activities

Enable the activity types you use under the liveNotifications key of your SDK config. On iOS, also add NSSupportsLiveActivities to your app's Info.plist, the liveactivities pod subspec, and a Widget Extension that renders the SDK's built-in templates. Without the Info.plist key, iOS refuses to start any activity:

<key>NSSupportsLiveActivities</key>
<true/>

One manual step is required on iOS. Forward every opened URL to the SDK from the host's active lifecycle, or taps on a Live Activity are not attributed. NativeLiveActivities comes from the wrapper pod, so import it. This only compiles once the liveactivities subspec is installed.

For an AppDelegate host:

import customerio_reactnative
import React
extensionAppDelegate{func application(_ app:UIApplication, open url:URL, options:[UIApplication.OpenURLOptionsKey:Any]=[:])->Bool{
// Reports an `opened` metric and returns the deep link to route to. A non-Customer.io URL comes
// back unchanged; `nil` means the activity carried no deep link, so there is nothing to open.
guardlet routableUrl =NativeLiveActivities.handleWidgetUrl(url)else{returntrue}returnRCTLinkingManager.application(app, open: routableUrl, options: options)}}

A React Native AppDelegate conforms to UIApplicationDelegate directly rather than subclassing, so this method is not an override, and the URL is passed on to RCTLinkingManager instead of super. See the sample app's AppDelegate.swift for this in context.

For a UIScene host, handle both lifecycle paths. At scene connection, pass the connection options through the wrapper so a cold Live Activity tap is attributed and its destination enters the acknowledged Customer.io router. Ordinary app links remain in React Native launch options for Linking:

import customerio_reactnative
reactNativeFactory?.startReactNative(
withModuleName:"YourApp",
in: window,
launchOptions:NativeLiveActivities.reactNativeLaunchOptions(from: connectionOptions))

Then replace the ordinary React Native URL-forwarding body in the existing SceneDelegate for warm opens. This reports Live Activity taps, sends Customer.io destinations through the configured Customer.io router, and forwards ordinary app links to React Native Linking:

import customerio_reactnative
func scene(_ scene:UIScene, openURLContexts URLContexts:Set<UIOpenURLContext>){forcontextin URLContexts {NativeLiveActivities.handleAndRouteWidgetUrl(context.url)}}

Do not also pass those URLs to RCTLinkingManager. The helper already routes them through the Customer.io bridge, and forwarding them again can deliver the same URL twice.

Android needs no equivalent native step.

For Expo Router, unwrap the URL once in the app's top-level app/+native-intent.tsx file:

import{CustomerIO}from'customerio-reactnative';exportasyncfunctionredirectSystemPath({ path }: {path: string}){returnCustomerIO.liveActivities.handleWidgetUrl(path);}

This reports the opened event, returns the customer's destination, preserves ordinary URLs, and returns null for a Customer.io tracking URL without a destination. Do not also call the helper from a Linking listener because processing the same tracking URL twice reports two opened events. Expo apps without Expo Router can apply the helper once in their central Linking initial-URL and subscription pipeline.


Identify Users, Track Events, and More

Customer.io helps you personalize your mobile experience:


Contributing

We welcome contributions! To get started:

  1. Review our example app to help with local development.
  2. Follow our Code of Conduct.

License

MIT

About

No description, website, or topics provided.

Resources

Code of conduct

Stars

40 stars

Watchers

22 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Latest commit

History

515 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

npm versionnpm downloadsmin Android SDK version is 21min iOS version is 13min Swift version is 5.3Contributor Covenant

Customer.io React Native SDK

The official Customer.io SDK for React Native enables you to integrate mobile messaging features—like in-app messaging and push notifications—into your app. These features can be triggered by events sent through the SDK and configured using your Customer.io campaigns.

📖 Full documentation: customer.io/docs/sdk/react-native
🧪 Example apps: see the /example directory


Installation

npm install customerio-reactnative

If a CocoaPods build reports that a generated dependency target is below the deployment range supported by Xcode, follow the deployment-target normalization guide.


SDK Initialization

Here’s a simplified example showing how to initialize the SDK to enable most mobile features:

import{CustomerIO,CioConfig,CioLogLevel,CioRegion,}from'customerio-reactnative';useEffect(()=>{constconfig: CioConfig={cdpApiKey: 'your-cdp-api-key',// RequiredmigrationSiteId: 'your-site-id',// Optional, for migrating from older SDKsregion: CioRegion.US,// Or CioRegion.EUlogLevel: CioLogLevel.debug,trackApplicationLifecycleEvents: true,inApp: {siteId: 'your-site-id',// Required for in-app messaging},push: {android: {pushClickBehavior: 'ActivityPreventRestart',// Optional},},};CustomerIO.initialize(config);},[]);

🔑 For help finding your credentials, check out the Quick Start Guide.


📲 Push Notifications

This SDK supports rich push notifications using Firebase (for Android) and either Firebase or APNs (for iOS). Follow our push setup guide to configure your project for push.

On iOS, a React Native 0.88+ UIScene host using the acknowledged handler must declare that ownership before React Native starts. This API is not used on Android or by AppDelegate-only hosts. Call this first in scene(_:willConnectTo:options:) so cold destinations wait for the JavaScript handler instead of entering the legacy Linking path:

NativeCustomerIO.configureAcknowledgedSceneDeepLinkRouting()

Register the app's Customer.io deep-link handler before calling CustomerIO.initialize. Return true after routing a URL. Return false to let the native SDK try the host AppDelegate, then pass a host-owned custom scheme to React Native Linking or open other URLs through the system. If your handler might decline an app-owned custom scheme, keep a Linking URL listener registered for that fallback. A thrown error, rejected promise, missing handler, or handler timeout follows the same native fallback. Cold URLs wait up to ten seconds for registration and are replayed once the handler is ready. After delivery, the handler has ten seconds to settle. If it takes longer, native fallback runs; a handler that later routes the URL can cause a second navigation because its acknowledgement cannot cancel the fallback.

constsubscription=CustomerIO.setDeepLinkHandler(async(url)=>{if(!canRouteInApp(url)){returnfalse;}awaitrouteInApp(url);returntrue;});CustomerIO.initialize(config);

Remove the returned subscription when the routing owner is torn down. If no replacement handler registers, later destinations wait for the readiness timeout and then use the native fallback.

The existing Linking path remains available for backward compatibility. Keep NativeCustomerIO.configureSceneDeepLinkRouting() in your SceneDelegate, then signal readiness after registering the listener:

constsubscription=Linking.addEventListener('url',({ url })=>{// Route the URL in your app.});CustomerIO.setDeepLinkRoutingReady();

Cold destinations wait up to ten seconds for this readiness signal. If it does not arrive, the SDK tries the host AppDelegate, passes a host-owned custom scheme to React Native Linking, or opens other URLs through the system.

This older path cannot acknowledge whether JavaScript handled the URL, so the SDK cannot safely fall back after it publishes a Linking event. Prefer setDeepLinkHandler for new UIScene integrations. Older React Native versions and AppDelegate-only hosts keep their existing deep-link integration.

Expo apps keep the Linking path. With config-plugin auto-initialization, register the app's Linking listener, then call CustomerIO.setDeepLinkRoutingReady() after the router is ready. The Expo plugin configures its native scene lifecycle automatically, so Expo app code does not call a native configuration method. Apps using JavaScript initialization register the router before CustomerIO.initialize; initialization marks Linking ready automatically.

This integration applies after the host has adopted React Native's UIScene lifecycle; the plugin does not replace React Native's root application lifecycle.

This release's compatibility scope is one simultaneous window scene. Multiple simultaneous React Native window scenes are not supported. React Native's URL notification is process-wide, so SDK-published destinations may reach every connected React Native instance rather than one selected window.


🔴 Live Activities

Enable the activity types you use under the liveNotifications key of your SDK config. On iOS, also add NSSupportsLiveActivities to your app's Info.plist, the liveactivities pod subspec, and a Widget Extension that renders the SDK's built-in templates. Without the Info.plist key, iOS refuses to start any activity:

<key>NSSupportsLiveActivities</key>
<true/>

One manual step is required on iOS. Forward every opened URL to the SDK from the host's active lifecycle, or taps on a Live Activity are not attributed. NativeLiveActivities comes from the wrapper pod, so import it. This only compiles once the liveactivities subspec is installed.

For an AppDelegate host:

import customerio_reactnative
import React
extensionAppDelegate{func application(_ app:UIApplication, open url:URL, options:[UIApplication.OpenURLOptionsKey:Any]=[:])->Bool{
// Reports an `opened` metric and returns the deep link to route to. A non-Customer.io URL comes
// back unchanged; `nil` means the activity carried no deep link, so there is nothing to open.
guardlet routableUrl =NativeLiveActivities.handleWidgetUrl(url)else{returntrue}returnRCTLinkingManager.application(app, open: routableUrl, options: options)}}

A React Native AppDelegate conforms to UIApplicationDelegate directly rather than subclassing, so this method is not an override, and the URL is passed on to RCTLinkingManager instead of super. See the sample app's AppDelegate.swift for this in context.

For a UIScene host, handle both lifecycle paths. At scene connection, pass the connection options through the wrapper so a cold Live Activity tap is attributed and its destination enters the acknowledged Customer.io router. Ordinary app links remain in React Native launch options for Linking:

import customerio_reactnative
reactNativeFactory?.startReactNative(
withModuleName:"YourApp",
in: window,
launchOptions:NativeLiveActivities.reactNativeLaunchOptions(from: connectionOptions))

Then replace the ordinary React Native URL-forwarding body in the existing SceneDelegate for warm opens. This reports Live Activity taps, sends Customer.io destinations through the configured Customer.io router, and forwards ordinary app links to React Native Linking:

import customerio_reactnative
func scene(_ scene:UIScene, openURLContexts URLContexts:Set<UIOpenURLContext>){forcontextin URLContexts {NativeLiveActivities.handleAndRouteWidgetUrl(context.url)}}

Do not also pass those URLs to RCTLinkingManager. The helper already routes them through the Customer.io bridge, and forwarding them again can deliver the same URL twice.

Android needs no equivalent native step.

For Expo Router, unwrap the URL once in the app's top-level app/+native-intent.tsx file:

import{CustomerIO}from'customerio-reactnative';exportasyncfunctionredirectSystemPath({ path }: {path: string}){returnCustomerIO.liveActivities.handleWidgetUrl(path);}

This reports the opened event, returns the customer's destination, preserves ordinary URLs, and returns null for a Customer.io tracking URL without a destination. Do not also call the helper from a Linking listener because processing the same tracking URL twice reports two opened events. Expo apps without Expo Router can apply the helper once in their central Linking initial-URL and subscription pipeline.


Identify Users, Track Events, and More

Customer.io helps you personalize your mobile experience:


Contributing

We welcome contributions! To get started:

  1. Review our example app to help with local development.
  2. Follow our Code of Conduct.

License

MIT

About

No description, website, or topics provided.

Resources

Code of conduct

Stars

40 stars

Watchers

22 watching

Forks

Releases

Packages

Used by

Contributors

Languages