diff --git a/.eslintrc.js b/.eslintrc.js
index e0f808c23..18e154b5a 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -1,5 +1,6 @@
module.exports = {
root: true,
+ ignorePatterns: ['coverage/**/*'],
extends: [
'@react-native',
'plugin:react/recommended',
diff --git a/README.md b/README.md
index dd3918d21..240239f1d 100644
--- a/README.md
+++ b/README.md
@@ -17,6 +17,11 @@ Iterable. It supports JavaScript and TypeScript.
- [Iterable's React Native SDK](#iterables-react-native-sdk)
- [Requirements](#requirements)
+ - [React Native](#react-native)
+ - [UI Components require additional peer dependencies](#ui-components-require-additional-peer-dependencies)
+ - [Optional peer dependencies for enhanced UI](#optional-peer-dependencies-for-enhanced-ui)
+ - [iOS](#ios)
+ - [Android](#android)
- [Architecture Support](#architecture-support)
- [Installation](#installation)
- [Features](#features)
@@ -34,22 +39,24 @@ Iterable. It supports JavaScript and TypeScript.
Iterable's React Native SDK relies on:
-- **React Native**
- - [React Native 0.75+](https://github.com/facebook/react-native)
- - [React 18.1+](https://github.com/facebook/react)
+### React Native
+ - [React Native 0.75+](https://github.com/facebook/react-native)
+ - [React 18.1+](https://github.com/facebook/react)
- _UI Components require additional peer dependencies_
- - [React Navigation 6+](https://github.com/react-navigation/react-navigation)
- - [React Native Safe Area Context 4+](https://github.com/th3rdwave/react-native-safe-area-context)
- - [React Native Vector Icons 10+](https://github.com/oblador/react-native-vector-icons)
- - [React Native WebView 13+](https://github.com/react-native-webview/react-native-webview)
+#### UI Components require additional peer dependencies
+ - [React Navigation 6+](https://github.com/react-navigation/react-navigation)
-- **iOS**
+#### Optional peer dependencies for enhanced UI
+ - [React Native WebView 13+](https://github.com/react-native-webview/react-native-webview) - Required only for inbox message display functionality. If not installed, the SDK will show a fallback message.
+ - [React Native Safe Area Context 4+](https://github.com/th3rdwave/react-native-safe-area-context) - Provides proper safe area handling for the inbox component. If not installed, the SDK will use fallback View components.
+ - [React Native Vector Icons 10+](https://github.com/oblador/react-native-vector-icons) - Provides enhanced icons for the inbox component. If not installed, the SDK will use fallback Unicode symbols.
+
+### iOS
- Xcode 12+
- [Deployment target 13.4+](https://help.apple.com/xcode/mac/current/#/deve69552ee5)
- [Iterable's iOS SDK](https://github.com/Iterable/iterable-swift-sdk)
-
-- **Android**
+ - Swift 5
+### Android
- [`minSdkVersion` 21+, `compileSdkVersion` 31+](https://medium.com/androiddevelopers/picking-your-compilesdkversion-minsdkversion-targetsdkversion-a098a0341ebd)
- [Iterable's Android SDK](https://github.com/Iterable/iterable-android-sdk)
diff --git a/package.json b/package.json
index ef832a4ad..98fd2b463 100644
--- a/package.json
+++ b/package.json
@@ -93,9 +93,9 @@
"react-native": "0.79.3",
"react-native-builder-bob": "^0.40.4",
"react-native-gesture-handler": "^2.26.0",
- "react-native-safe-area-context": "^5.4.0",
+ "react-native-safe-area-context": "^5.6.1",
"react-native-screens": "^4.10.0",
- "react-native-vector-icons": "^10.2.0",
+ "react-native-vector-icons": "^10.3.0",
"react-native-webview": "^13.14.1",
"react-test-renderer": "19.0.0",
"release-it": "^17.10.0",
@@ -111,14 +111,20 @@
"peerDependencies": {
"@react-navigation/native": "*",
"react": "*",
- "react-native": "*",
- "react-native-safe-area-context": "*",
- "react-native-vector-icons": "*",
- "react-native-webview": "*"
+ "react-native": "*"
},
"peerDependenciesMeta": {
"expo": {
"optional": true
+ },
+ "react-native-safe-area-context": {
+ "optional": true
+ },
+ "react-native-vector-icons": {
+ "optional": true
+ },
+ "react-native-webview": {
+ "optional": true
}
},
"sideEffects": false,
diff --git a/src/core/index.ts b/src/core/index.ts
index 250860473..041c5d7af 100644
--- a/src/core/index.ts
+++ b/src/core/index.ts
@@ -2,3 +2,4 @@ export * from './classes';
export * from './enums';
export * from './hooks';
export * from './types';
+export * from './utils/SafeAreaContext';
diff --git a/src/core/utils/SafeAreaContext.tsx b/src/core/utils/SafeAreaContext.tsx
new file mode 100644
index 000000000..a1a7ebc7f
--- /dev/null
+++ b/src/core/utils/SafeAreaContext.tsx
@@ -0,0 +1,127 @@
+/* eslint-disable @typescript-eslint/no-var-requires, @typescript-eslint/no-require-imports */
+import React from 'react';
+import { View, type ViewStyle } from 'react-native';
+
+/**
+ * Error thrown when react-native-safe-area-context is required but not available
+ */
+export class SafeAreaContextNotAvailableError extends Error {
+ constructor(componentName: string) {
+ super(
+ `react-native-safe-area-context is required for ${componentName} but is not installed. ` +
+ 'Please install it by running: npm install react-native-safe-area-context ' +
+ 'or yarn add react-native-safe-area-context'
+ );
+ this.name = 'SafeAreaContextNotAvailableError';
+ }
+}
+
+/**
+ * Conditionally imports and returns SafeAreaView from react-native-safe-area-context
+ * @throws \{SafeAreaContextNotAvailableError\} When the library is not available
+ */
+export const getSafeAreaView = () => {
+ try {
+ const { SafeAreaView } = require('react-native-safe-area-context');
+ return SafeAreaView;
+ } catch {
+ throw new SafeAreaContextNotAvailableError('SafeAreaView');
+ }
+};
+
+/**
+ * Conditionally imports and returns SafeAreaProvider from react-native-safe-area-context
+ * @throws \{SafeAreaContextNotAvailableError\} When the library is not available
+ */
+export const getSafeAreaProvider = () => {
+ try {
+ const { SafeAreaProvider } = require('react-native-safe-area-context');
+ return SafeAreaProvider;
+ } catch {
+ throw new SafeAreaContextNotAvailableError('SafeAreaProvider');
+ }
+};
+
+/**
+ * Conditionally imports and returns useSafeAreaInsets from react-native-safe-area-context
+ * @throws \{SafeAreaContextNotAvailableError\} When the library is not available
+ */
+export const getUseSafeAreaInsets = () => {
+ try {
+ const { useSafeAreaInsets } = require('react-native-safe-area-context');
+ return useSafeAreaInsets;
+ } catch {
+ throw new SafeAreaContextNotAvailableError('useSafeAreaInsets');
+ }
+};
+
+/**
+ * Conditionally imports and returns useSafeAreaFrame from react-native-safe-area-context
+ * @throws \{SafeAreaContextNotAvailableError\} When the library is not available
+ */
+export const getUseSafeAreaFrame = () => {
+ try {
+ const { useSafeAreaFrame } = require('react-native-safe-area-context');
+ return useSafeAreaFrame;
+ } catch {
+ throw new SafeAreaContextNotAvailableError('useSafeAreaFrame');
+ }
+};
+
+/**
+ * A conditional SafeAreaView component that only loads react-native-safe-area-context when needed
+ */
+export interface ConditionalSafeAreaViewProps {
+ style?: ViewStyle;
+ children: React.ReactNode;
+ edges?: string[];
+ mode?: 'padding' | 'margin';
+}
+
+export const ConditionalSafeAreaView: React.FC<
+ ConditionalSafeAreaViewProps
+> = ({ style, children, edges, mode }) => {
+ try {
+ const SafeAreaView = getSafeAreaView();
+ return (
+
+ {children}
+
+ );
+ } catch {
+ // Fallback to regular View if SafeAreaView is not available
+ console.warn(
+ 'SafeAreaView is not available. Falling back to regular View. ' +
+ 'Install react-native-safe-area-context for proper safe area handling.'
+ );
+ return {children};
+ }
+};
+
+/**
+ * A conditional SafeAreaProvider component that only loads react-native-safe-area-context when needed
+ */
+export interface ConditionalSafeAreaProviderProps {
+ children: React.ReactNode;
+ initialMetrics?: unknown;
+}
+
+export const ConditionalSafeAreaProvider: React.FC<
+ ConditionalSafeAreaProviderProps
+> = ({ children, initialMetrics }) => {
+ try {
+ const SafeAreaProvider = getSafeAreaProvider();
+ return (
+
+ {children}
+
+ );
+ } catch {
+ // Fallback to Fragment if SafeAreaProvider is not available
+ console.warn(
+ 'SafeAreaProvider is not available. Falling back to Fragment. ' +
+ 'Install react-native-safe-area-context for proper safe area handling.'
+ );
+ return <>{children}>;
+ }
+};
diff --git a/src/inbox/components/IterableInbox.tsx b/src/inbox/components/IterableInbox.tsx
index 545403e03..5752f8e1f 100644
--- a/src/inbox/components/IterableInbox.tsx
+++ b/src/inbox/components/IterableInbox.tsx
@@ -9,9 +9,8 @@ import {
Text,
View,
} from 'react-native';
-import { SafeAreaView } from 'react-native-safe-area-context';
-
import { useAppStateListener, useDeviceOrientation } from '../../core';
+import { ConditionalSafeAreaView } from '../../core/utils/SafeAreaContext';
// expo throws an error if this is not imported directly due to circular
// dependencies
// See: https://github.com/expo/expo/issues/35100
@@ -500,7 +499,7 @@ export const IterableInbox = ({
);
return safeAreaMode ? (
- {inboxAnimatedView}
+ {inboxAnimatedView}
) : (
{inboxAnimatedView}
);
diff --git a/src/inbox/components/IterableInboxIcon.tsx b/src/inbox/components/IterableInboxIcon.tsx
new file mode 100644
index 000000000..fcb944043
--- /dev/null
+++ b/src/inbox/components/IterableInboxIcon.tsx
@@ -0,0 +1,41 @@
+import { Text, StyleSheet, type TextStyle } from 'react-native';
+
+/**
+ * Props for the IterableInboxIcon component.
+ */
+export interface IterableInboxIconProps {
+ /**
+ * The name of the icon to display.
+ */
+ name: string;
+ /**
+ * The style to apply to the icon.
+ */
+ style?: TextStyle;
+}
+
+/**
+ * A fallback icon component that uses Unicode symbols instead of vector icons.
+ * This allows the inbox to work without requiring react-native-vector-icons.
+ */
+export const IterableInboxIcon = ({ name, style }: IterableInboxIconProps) => {
+ // Map of common icon names to Unicode symbols
+ const iconMap: Record = {
+ 'chevron-back-outline': '‹',
+ 'chevron-back': '‹',
+ 'arrow-back': '←',
+ 'back': '←',
+ };
+
+ const iconSymbol = iconMap[name] || '?';
+
+ return {iconSymbol};
+};
+
+const styles = StyleSheet.create({
+ icon: {
+ fontSize: 24,
+ fontWeight: 'bold',
+ textAlign: 'center',
+ },
+});
diff --git a/src/inbox/components/IterableInboxIconUtils.ts b/src/inbox/components/IterableInboxIconUtils.ts
new file mode 100644
index 000000000..12f25a108
--- /dev/null
+++ b/src/inbox/components/IterableInboxIconUtils.ts
@@ -0,0 +1,20 @@
+/* eslint-disable @typescript-eslint/no-require-imports, @typescript-eslint/no-var-requires */
+import { type TextStyle } from 'react-native';
+
+// Type for the vector icon component
+type VectorIconComponent = React.ComponentType<{
+ name: string;
+ style?: TextStyle;
+}>;
+
+/**
+ * Attempts to load the react-native-vector-icons module.
+ * Returns null if the module is not available.
+ */
+export function tryLoadVectorIcons(): VectorIconComponent | null {
+ try {
+ return require('react-native-vector-icons/Ionicons').default;
+ } catch {
+ return null;
+ }
+}
diff --git a/src/inbox/components/IterableInboxMessageDisplay.tsx b/src/inbox/components/IterableInboxMessageDisplay.tsx
index 7e6798c73..f70e5ffc6 100644
--- a/src/inbox/components/IterableInboxMessageDisplay.tsx
+++ b/src/inbox/components/IterableInboxMessageDisplay.tsx
@@ -7,8 +7,12 @@ import {
TouchableWithoutFeedback,
View,
} from 'react-native';
-import Icon from 'react-native-vector-icons/Ionicons';
-import { WebView, type WebViewMessageEvent } from 'react-native-webview';
+import { IterableInboxSmartIcon } from './IterableInboxSmartIcon';
+import {
+ loadWebView,
+ FallbackWebView,
+ WebViewNotAvailableError,
+} from '../../utils/WebViewLoader';
import {
IterableAction,
@@ -78,6 +82,13 @@ export const IterableInboxMessageDisplay = ({
const messageTitle = rowViewModel.inAppMessage.inboxMetadata?.title;
const [inAppContent, setInAppContent] =
useState(null);
+ const [WebViewComponent, setWebViewComponent] = useState void;
+ injectedJavaScript?: string;
+ }> | null>(null);
const styles = StyleSheet.create({
contentContainer: {
@@ -172,7 +183,21 @@ export const IterableInboxMessageDisplay = ({
};
});
- function handleInAppLinkAction(event: WebViewMessageEvent) {
+ // Load WebView component dynamically
+ useEffect(() => {
+ try {
+ const WebView = loadWebView();
+ setWebViewComponent(() => WebView);
+ } catch (error) {
+ if (error instanceof WebViewNotAvailableError) {
+ setWebViewComponent(() => FallbackWebView);
+ } else {
+ setWebViewComponent(() => FallbackWebView);
+ }
+ }
+ }, []);
+
+ function handleInAppLinkAction(event: { nativeEvent: { data: string } }) {
const URL = event.nativeEvent.data;
const action = new IterableAction('openUrl', URL, '');
@@ -233,7 +258,7 @@ export const IterableInboxMessageDisplay = ({
}}
>
-
@@ -253,13 +278,13 @@ export const IterableInboxMessageDisplay = ({
- {inAppContent && (
+ {inAppContent && WebViewComponent && (
- handleInAppLinkAction(event)}
+ onMessage={handleInAppLinkAction}
injectedJavaScript={JS}
/>
diff --git a/src/inbox/components/IterableInboxSmartIcon.tsx b/src/inbox/components/IterableInboxSmartIcon.tsx
new file mode 100644
index 000000000..f7f695c23
--- /dev/null
+++ b/src/inbox/components/IterableInboxSmartIcon.tsx
@@ -0,0 +1,36 @@
+import { type TextStyle } from 'react-native';
+
+import { IterableInboxIcon } from './IterableInboxIcon';
+import { tryLoadVectorIcons } from './IterableInboxIconUtils';
+
+/**
+ * Props for the IterableInboxSmartIcon component.
+ */
+export interface IterableInboxSmartIconProps {
+ /**
+ * The name of the icon to display.
+ */
+ name: string;
+ /**
+ * The style to apply to the icon.
+ */
+ style?: TextStyle;
+}
+
+/**
+ * A smart icon component that attempts to use react-native-vector-icons if available,
+ * otherwise falls back to Unicode symbols.
+ */
+export const IterableInboxSmartIcon = ({
+ name,
+ style,
+}: IterableInboxSmartIconProps) => {
+ const VectorIcon = tryLoadVectorIcons();
+
+ if (VectorIcon) {
+ return ;
+ }
+
+ // Fallback to Unicode symbols
+ return ;
+};
diff --git a/src/inbox/components/index.ts b/src/inbox/components/index.ts
index f00e88241..b294f06fc 100644
--- a/src/inbox/components/index.ts
+++ b/src/inbox/components/index.ts
@@ -1,5 +1,7 @@
export * from './IterableInbox';
export * from './IterableInboxEmptyState';
+export * from './IterableInboxIcon';
+export * from './IterableInboxSmartIcon';
export * from './IterableInboxMessageCell';
export * from './IterableInboxMessageDisplay';
export * from './IterableInboxMessageList';
diff --git a/src/index.tsx b/src/index.tsx
index 885cd74bd..122c1718d 100644
--- a/src/index.tsx
+++ b/src/index.tsx
@@ -44,11 +44,15 @@ export {
IterableInbox,
IterableInboxDataModel,
IterableInboxEmptyState,
+ IterableInboxIcon,
IterableInboxMessageCell,
+ IterableInboxSmartIcon,
type IterableInboxCustomizations,
type IterableInboxEmptyStateProps,
+ type IterableInboxIconProps,
type IterableInboxImpressionRowInfo,
type IterableInboxMessageCellProps,
type IterableInboxProps,
type IterableInboxRowViewModel,
+ type IterableInboxSmartIconProps,
} from './inbox';
diff --git a/src/utils/WebViewLoader.tsx b/src/utils/WebViewLoader.tsx
new file mode 100644
index 000000000..56e054533
--- /dev/null
+++ b/src/utils/WebViewLoader.tsx
@@ -0,0 +1,131 @@
+/* eslint-disable @typescript-eslint/no-var-requires,
+@typescript-eslint/no-require-imports, react-native/no-inline-styles, react-native/no-color-literals */
+/**
+ * Utility for dynamically loading react-native-webview
+ * This allows the SDK to work without requiring react-native-webview as a dependency
+ * when WebView functionality is not needed.
+ */
+
+import { View, Text } from 'react-native';
+
+/**
+ * Error thrown when react-native-webview is not available but is required
+ */
+export class WebViewNotAvailableError extends Error {
+ constructor() {
+ super(
+ 'react-native-webview is required but not installed. Please install it using: npm install react-native-webview or yarn add react-native-webview'
+ );
+ this.name = 'WebViewNotAvailableError';
+ }
+}
+
+/**
+ * Dynamically loads the WebView component from react-native-webview
+ * @returns The WebView component
+ * @throws \{WebViewNotAvailableError\} When react-native-webview is not installed
+ */
+export function loadWebView(): React.ComponentType<{
+ originWhiteList?: string[];
+ source?: { html: string };
+ style?: object;
+ onMessage?: (event: { nativeEvent: { data: string } }) => void;
+ injectedJavaScript?: string;
+}> {
+ try {
+ // Try to require react-native-webview dynamically
+ const { WebView } = require('react-native-webview');
+ return WebView;
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
+ } catch (error) {
+ throw new WebViewNotAvailableError();
+ }
+}
+
+/**
+ * Dynamically loads the WebViewMessageEvent type from react-native-webview
+ * @returns The WebViewMessageEvent type
+ * @throws \{WebViewNotAvailableError\} When react-native-webview is not installed
+ */
+export function loadWebViewMessageEventType(): {
+ nativeEvent: { data: string };
+} {
+ try {
+ // Try to require the type from react-native-webview
+ const { WebViewMessageEvent } = require('react-native-webview');
+ return WebViewMessageEvent;
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
+ } catch (error) {
+ throw new WebViewNotAvailableError();
+ }
+}
+
+/**
+ * Checks if react-native-webview is available without throwing an error
+ * @returns true if react-native-webview is available, false otherwise
+ */
+export function isWebViewAvailable(): boolean {
+ try {
+ require('react-native-webview');
+ return true;
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
+ } catch (error) {
+ return false;
+ }
+}
+
+/**
+ * Fallback WebView component that shows an error message
+ * Used when react-native-webview is not available
+ */
+interface FallbackWebViewProps {
+ style?: object;
+}
+
+export const FallbackWebView = ({ style }: FallbackWebViewProps) => {
+ return (
+
+
+
+ WebView Not Available
+
+
+ react-native-webview is required to display this content.
+
+
+ Please install: npm install react-native-webview
+
+
+
+ );
+};
diff --git a/src/utils/__tests__/WebViewLoader.test.ts b/src/utils/__tests__/WebViewLoader.test.ts
new file mode 100644
index 000000000..221d9d900
--- /dev/null
+++ b/src/utils/__tests__/WebViewLoader.test.ts
@@ -0,0 +1,79 @@
+import {
+ loadWebView,
+ loadWebViewMessageEventType,
+ isWebViewAvailable,
+} from '../WebViewLoader';
+
+// Mock react-native-webview
+jest.mock('react-native-webview', () => ({
+ WebView: 'MockWebView',
+ WebViewMessageEvent: 'MockWebViewMessageEvent',
+}));
+
+describe('WebViewLoader', () => {
+ beforeEach(() => {
+ jest.clearAllMocks();
+ });
+
+ describe('isWebViewAvailable', () => {
+ it('should return true when react-native-webview is available', () => {
+ expect(isWebViewAvailable()).toBe(true);
+ });
+ });
+
+ describe('loadWebView', () => {
+ it('should load WebView component when available', () => {
+ const WebView = loadWebView();
+ expect(WebView).toBe('MockWebView');
+ });
+ });
+
+ describe('loadWebViewMessageEventType', () => {
+ it('should load WebViewMessageEvent type when available', () => {
+ const WebViewMessageEvent = loadWebViewMessageEventType();
+ expect(WebViewMessageEvent).toBe('MockWebViewMessageEvent');
+ });
+ });
+});
+
+describe('WebViewLoader without react-native-webview', () => {
+ beforeEach(() => {
+ // Clear the module cache and mock
+ jest.resetModules();
+ jest.doMock('react-native-webview', () => {
+ throw new Error('Module not found');
+ });
+ });
+
+ afterEach(() => {
+ jest.resetModules();
+ });
+
+ describe('isWebViewAvailable', () => {
+ it('should return false when react-native-webview is not available', async () => {
+ const { isWebViewAvailable: isAvailable } = await import(
+ '../WebViewLoader'
+ );
+ expect(isAvailable()).toBe(false);
+ });
+ });
+
+ describe('loadWebView', () => {
+ it('should throw WebViewNotAvailableError when react-native-webview is not available', async () => {
+ const { loadWebView: loadWebViewFn } = await import('../WebViewLoader');
+ expect(() => loadWebViewFn()).toThrow(
+ 'react-native-webview is required but not installed'
+ );
+ });
+ });
+
+ describe('loadWebViewMessageEventType', () => {
+ it('should throw WebViewNotAvailableError when react-native-webview is not available', async () => {
+ const { loadWebViewMessageEventType: loadWebViewMessageEventTypeFn } =
+ await import('../WebViewLoader');
+ expect(() => loadWebViewMessageEventTypeFn()).toThrow(
+ 'react-native-webview is required but not installed'
+ );
+ });
+ });
+});
diff --git a/yarn.lock b/yarn.lock
index d9961ac53..28f288534 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -3146,9 +3146,9 @@ __metadata:
react-native: 0.79.3
react-native-builder-bob: ^0.40.4
react-native-gesture-handler: ^2.26.0
- react-native-safe-area-context: ^5.4.0
+ react-native-safe-area-context: ^5.6.1
react-native-screens: ^4.10.0
- react-native-vector-icons: ^10.2.0
+ react-native-vector-icons: ^10.3.0
react-native-webview: ^13.14.1
react-test-renderer: 19.0.0
release-it: ^17.10.0
@@ -3161,12 +3161,15 @@ __metadata:
"@react-navigation/native": "*"
react: "*"
react-native: "*"
- react-native-safe-area-context: "*"
- react-native-vector-icons: "*"
- react-native-webview: "*"
peerDependenciesMeta:
expo:
optional: true
+ react-native-safe-area-context:
+ optional: true
+ react-native-vector-icons:
+ optional: true
+ react-native-webview:
+ optional: true
languageName: unknown
linkType: soft
@@ -12544,7 +12547,7 @@ __metadata:
languageName: node
linkType: hard
-"react-native-safe-area-context@npm:^5.4.0":
+"react-native-safe-area-context@npm:^5.6.1":
version: 5.6.1
resolution: "react-native-safe-area-context@npm:5.6.1"
peerDependencies:
@@ -12597,6 +12600,21 @@ __metadata:
languageName: node
linkType: hard
+"react-native-vector-icons@npm:^10.3.0":
+ version: 10.3.0
+ resolution: "react-native-vector-icons@npm:10.3.0"
+ dependencies:
+ prop-types: ^15.7.2
+ yargs: ^16.1.1
+ bin:
+ fa-upgrade.sh: bin/fa-upgrade.sh
+ fa5-upgrade: bin/fa5-upgrade.sh
+ fa6-upgrade: bin/fa6-upgrade.sh
+ generate-icon: bin/generate-icon.js
+ checksum: 5c431fd9a8e6efd355e34ed28ca7fa7eed30e89362280cbd1e474e6d16148c6c37f5c950a525ec0b428c79dc74b9fb7a61171fc509b6ab253e111456f3e49b71
+ languageName: node
+ linkType: hard
+
"react-native-webview@npm:^13.13.1":
version: 13.14.1
resolution: "react-native-webview@npm:13.14.1"