@zencemarketing/zence-react-native-sdk
v0.1.0
Published
Reusable React Native SDK to wrap any web app URL in a native WebView shell.
Readme
@zencemarketing/zence-react-native-sdk
React Native library that loads your web app inside a native WebView with a small native ↔ web bridge (JWT bootstrap, postMessage, deep links, optional push token handoff).
Platforms: Android and iOS (JavaScript + react-native-webview; no custom native module is required from this package).
Requirements
| Requirement | Notes |
|-------------|--------|
| React | >= 18 (peer) |
| React Native | >= 0.72 (peer) |
| react-native-webview | >= 13 (peer) |
| Node | >= 18 recommended for local development / publishing |
Installation
In your host React Native app (not inside this repo unless you are developing the SDK):
npm install @zencemarketing/zence-react-native-sdk react-native-webview @react-native-async-storage/async-storage @react-native-community/netinfoyarn add @zencemarketing/zence-react-native-sdk react-native-webview @react-native-async-storage/async-storage @react-native-community/netinfopnpm add @zencemarketing/zence-react-native-sdk react-native-webview @react-native-async-storage/async-storage @react-native-community/netinfoThen install native dependencies.
iOS
From your app root:
cd ios && pod install && cd ..- HTTPS / ATS: Loading arbitrary HTTPS URLs is usually fine. For HTTP or non-standard TLS, configure App Transport Security in your app’s
Info.plist. - Geolocation in WebView: If you use
allowLocationAccess, add usage strings (e.g.NSLocationWhenInUseUsageDescription) inInfo.plist. - Camera / mic in WebView: Add the corresponding privacy usage keys if your web app uses them.
Android
- Ensure
android.permission.INTERNETis present in your appAndroidManifest.xml(default templates include it). - For cleartext HTTP, set
android:usesCleartextTraffic="true"or a network security config as required by your policy.
Expo
Use a dev client or bare workflow so native modules apply, then align versions with Expo’s installer where applicable:
npx expo install react-native-webview @react-native-async-storage/async-storage @react-native-community/netinfoAdd @zencemarketing/zence-react-native-sdk with your package manager as above.
Quick start
import React from "react";
import { ZenceWebSDK } from "@zencemarketing/zence-react-native-sdk";
export default function App() {
return (
<ZenceWebSDK
url="https://myreactapp.com"
token="jwt-token"
enableBackButton
enablePullToRefresh
onMessage={(data) => console.log("Web message:", data)}
onError={(error) => console.log("WebView error:", error)}
/>
);
}Features
- WebView with configurable URL, headers, and user agent
- Optional JWT bootstrap into web
localStorageandsessionStorage - Native ↔ web bridge (
postMessage+injectJavaScript) - Android hardware back navigates WebView history when
enableBackButtonis true - Pull-to-refresh toggle
- Deep link forwarding into the web layer
- Offline fallback HTML when connectivity is lost (
offline+@react-native-community/netinfo) - Loader overlay and analytics-style callbacks
- Optional push token request/response from web to native
Web → native
From JavaScript inside the page (standard WebView pattern):
window.ReactNativeWebView.postMessage(
JSON.stringify({
type: "LOGIN_SUCCESS",
payload: { id: 10, name: "John" },
}),
);The SDK parses JSON messages with a type field and passes them to onMessage.
Built-in web → native message type
| type | Behavior |
|--------|-----------|
| REQUEST_PUSH_TOKEN | If onPushTokenRequested is set, native resolves a token and injects a response into the page (see below). |
Native → web
Use a ref and postMessageToWeb to push structured messages into the WebView:
import React, { useRef } from "react";
import { Button, View } from "react-native";
import { ZenceWebSDK, type NativeCommands } from "@zencemarketing/zence-react-native-sdk";
export default function App() {
const sdkRef = useRef<NativeCommands>(null);
return (
<View style={{ flex: 1 }}>
<Button
title="Set dark theme"
onPress={() =>
sdkRef.current?.postMessageToWeb({
type: "SET_THEME",
payload: { theme: "dark" },
})
}
/>
<ZenceWebSDK ref={sdkRef} url="https://myreactapp.com" />
</View>
);
}NativeCommands (ref handle)
| Method | Description |
|--------|-------------|
| postMessageToWeb(message) | Delivers message to window.ZenceBridge.onNativeMessage and dispatches zence-native-message on window. |
| reload() | Reloads the WebView. |
| goBack() | WebView back navigation. |
Events and payloads the web app can observe
After load, the SDK injects window.ZenceBridge and dispatches:
zence-native-ready—event.detail.tokenmirrors thetokenprop (ornull).zence-native-message—event.detailis the bridge message object.
Native-initiated messages (including deep links) also call:
window.ZenceBridge = window.ZenceBridge || {};
window.ZenceBridge.onNativeMessage = (message) => {
console.log("From native:", message);
};Deep links received via React Native Linking are forwarded as a bridge message with type: "DEEPLINK_RECEIVED" and payload.url, and the same injection path runs.
Push token flow from web:
// Web requests a token; native responds via onPushTokenRequested
window.ReactNativeWebView.postMessage(JSON.stringify({ type: "REQUEST_PUSH_TOKEN" }));Native responds by injecting { type: "PUSH_TOKEN_RESPONSE", payload: { token: string } } into the page.
Props (ZenceWebSDKProps)
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| url | string | required | Initial WebView URL. |
| token | string | — | Written to localStorage / sessionStorage as "token" when present. |
| headers | Record<string, string> | — | Request headers for the main document load. |
| userAgent | string | — | Custom user agent string. |
| customInjectedJavaScript | string | — | Appended to the bootstrap script run at load. |
| enableBackButton | boolean | true | Android hardware back steps WebView history when possible. |
| enablePullToRefresh | boolean | true | Enables pull-to-refresh on the WebView. |
| allowFileAccess | boolean | true | Maps to WebView read-access behavior where supported. |
| allowCameraAccess | boolean | true | Reserved / policy flag surface (WebView permissions still depend on OS + manifest). |
| allowLocationAccess | boolean | true | Enables geolocation in WebView when the OS allows it. |
| allowBiometrics | boolean | true | When true, emits an analytics event once (see source). |
| enableCookies | boolean | true | sharedCookiesEnabled on WebView. |
| enableThirdPartyCookies | boolean | true | thirdPartyCookiesEnabled (where supported). |
| offline | { enabled?: boolean; fallbackHtml?: string } | — | When enabled, shows offline HTML while the device reports no connectivity. |
| loader | { color?; backgroundColor?; testID? } | — | Overlay loader styling during loads. |
| style | StyleProp<ViewStyle> | — | Wrapper View style. |
| onMessage | (message: BridgeMessage) => void | — | Parsed web messages. |
| onError | (error: string) => void | — | Load / HTTP / push token errors. |
| onLoadStart / onLoadEnd | (url: string) => void | — | Navigation lifecycle. |
| onNavigationStateChange | (state: WebViewNavigation) => void | — | Forwarded from WebView. |
| onAnalyticsEvent | (event: { name: string; payload?: Record<string, unknown> }) => void | — | Hook for your analytics pipeline. |
| onDeepLink | (url: string) => void | — | Called when app receives a deep link URL. |
| onPushTokenRequested | () => Promise<string \| undefined> | — | Supplies token to web when REQUEST_PUSH_TOKEN is received. |
Exported types: ZenceWebSDKProps, BridgeMessage, OfflineConfig, LoaderConfig, AnalyticsEvent, AnalyticsHandler, DeepLinkHandler, NativeCommands.
Advanced example
<ZenceWebSDK
url="https://myreactapp.com/?{jwt}"
headers={{ "x-tenant-id": "tenant_001" }}
offline={{ enabled: true }}
loader={{ color: "#0F62FE", backgroundColor: "rgba(0,0,0,0.12)" }}
onPushTokenRequested={async () => {
// Return FCM / APNs token from your push setup
return "push-token";
}}
onAnalyticsEvent={(event) => {
console.log(event.name, event.payload);
}}
/>Troubleshooting
| Issue | What to check |
|-------|----------------|
| Blank WebView | URL, ATS / cleartext, device network, onError logs. |
| Messages not reaching native | Must use window.ReactNativeWebView.postMessage inside the WebView page; body must be a string (JSON.stringify). |
| iOS pod errors | Run pod repo update and align react-native-webview with your RN version. |
| Types not found | Ensure typescript in your app resolves dist/index.d.ts (ships with the package). |
Developing this SDK (clone of repo)
npm install
npm run build
npm run typecheckPublishing to npm (maintainers)
Account: Create an account on https://www.npmjs.com/ and enable two-factor authentication (npm requires 2FA for publishing as of policy updates).
Login:
npm login(ornpm adduser) on the machine that will publish; verify withnpm whoami.Package name:
nameinpackage.jsonmust be available on the registry. Scoped packages (@your-org/name) need"publishConfig": { "access": "public" }if you want them public.Metadata: Add a real
repositoryandbugsURL inpackage.jsonwhen you have a Git host—improves discoverability and issue links on the npm page.Version: Bump semver in
package.json(or usenpm version patch|minor|major).Dry run: From the package root (with a clean git state recommended):
npm run build npm pack --dry-runConfirm the tarball lists
dist/,README.md,LICENSE, and placeholderandroid/iosfolders as intended.Publish:
npm publishUse
npm publish --tag betaand a prerelease version for pre-releases.Provenance (optional): On supported CI, you can use npm provenance for supply-chain transparency.
Do not publish node_modules, example-app, or .gradle-home; the files field in package.json already limits what is packed. The prepack script runs npm run build so dist/ is up to date when packing or publishing.
License
MIT — see LICENSE.
