flowboard-react 0.6.2 → 0.6.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (88) hide show
  1. package/README.md +185 -52
  2. package/app.plugin.js +341 -0
  3. package/bin/setup.js +428 -0
  4. package/lib/module/FlowboardProvider.js +1 -1
  5. package/lib/module/FlowboardProvider.js.map +1 -1
  6. package/lib/module/components/FlowboardFlow.js +6 -6
  7. package/lib/module/components/FlowboardFlow.js.map +1 -1
  8. package/lib/module/components/FlowboardRenderer.js +6 -6
  9. package/lib/module/components/FlowboardRenderer.js.map +1 -1
  10. package/lib/module/core/clientContext.js +10 -29
  11. package/lib/module/core/clientContext.js.map +1 -1
  12. package/lib/module/core/fontAwesome.js +2 -9
  13. package/lib/module/core/fontAwesome.js.map +1 -1
  14. package/lib/module/core/onboardingRepository.js +13 -13
  15. package/lib/module/core/onboardingRepository.js.map +1 -1
  16. package/lib/module/native/asyncStorage.js +55 -0
  17. package/lib/module/native/asyncStorage.js.map +1 -0
  18. package/lib/module/native/deviceInfo.js +46 -0
  19. package/lib/module/native/deviceInfo.js.map +1 -0
  20. package/lib/module/native/inAppReview.js +17 -0
  21. package/lib/module/native/inAppReview.js.map +1 -0
  22. package/lib/module/native/linearGradient.js +29 -0
  23. package/lib/module/native/linearGradient.js.map +1 -0
  24. package/lib/module/native/lottie.js +20 -0
  25. package/lib/module/native/lottie.js.map +1 -0
  26. package/lib/module/native/maskedView.js +26 -0
  27. package/lib/module/native/maskedView.js.map +1 -0
  28. package/lib/module/native/pagerView.js +79 -0
  29. package/lib/module/native/pagerView.js.map +1 -0
  30. package/lib/module/native/permissions.js +30 -0
  31. package/lib/module/native/permissions.js.map +1 -0
  32. package/lib/module/native/runtime.js +81 -0
  33. package/lib/module/native/runtime.js.map +1 -0
  34. package/lib/module/native/safeAreaContext.js +43 -0
  35. package/lib/module/native/safeAreaContext.js.map +1 -0
  36. package/lib/module/native/svg.js +83 -0
  37. package/lib/module/native/svg.js.map +1 -0
  38. package/lib/module/native/vectorIcons.js +41 -0
  39. package/lib/module/native/vectorIcons.js.map +1 -0
  40. package/lib/typescript/src/components/FlowboardFlow.d.ts.map +1 -1
  41. package/lib/typescript/src/components/FlowboardRenderer.d.ts.map +1 -1
  42. package/lib/typescript/src/core/clientContext.d.ts.map +1 -1
  43. package/lib/typescript/src/core/fontAwesome.d.ts +1 -1
  44. package/lib/typescript/src/core/fontAwesome.d.ts.map +1 -1
  45. package/lib/typescript/src/core/onboardingRepository.d.ts.map +1 -1
  46. package/lib/typescript/src/native/asyncStorage.d.ts +16 -0
  47. package/lib/typescript/src/native/asyncStorage.d.ts.map +1 -0
  48. package/lib/typescript/src/native/deviceInfo.d.ts +11 -0
  49. package/lib/typescript/src/native/deviceInfo.d.ts.map +1 -0
  50. package/lib/typescript/src/native/inAppReview.d.ts +4 -0
  51. package/lib/typescript/src/native/inAppReview.d.ts.map +1 -0
  52. package/lib/typescript/src/native/linearGradient.d.ts +3 -0
  53. package/lib/typescript/src/native/linearGradient.d.ts.map +1 -0
  54. package/lib/typescript/src/native/lottie.d.ts +3 -0
  55. package/lib/typescript/src/native/lottie.d.ts.map +1 -0
  56. package/lib/typescript/src/native/maskedView.d.ts +3 -0
  57. package/lib/typescript/src/native/maskedView.d.ts.map +1 -0
  58. package/lib/typescript/src/native/pagerView.d.ts +14 -0
  59. package/lib/typescript/src/native/pagerView.d.ts.map +1 -0
  60. package/lib/typescript/src/native/permissions.d.ts +9 -0
  61. package/lib/typescript/src/native/permissions.d.ts.map +1 -0
  62. package/lib/typescript/src/native/runtime.d.ts +13 -0
  63. package/lib/typescript/src/native/runtime.d.ts.map +1 -0
  64. package/lib/typescript/src/native/safeAreaContext.d.ts +15 -0
  65. package/lib/typescript/src/native/safeAreaContext.d.ts.map +1 -0
  66. package/lib/typescript/src/native/svg.d.ts +8 -0
  67. package/lib/typescript/src/native/svg.d.ts.map +1 -0
  68. package/lib/typescript/src/native/vectorIcons.d.ts +4 -0
  69. package/lib/typescript/src/native/vectorIcons.d.ts.map +1 -0
  70. package/package.json +21 -14
  71. package/src/FlowboardProvider.tsx +1 -1
  72. package/src/components/FlowboardFlow.tsx +10 -7
  73. package/src/components/FlowboardRenderer.tsx +8 -13
  74. package/src/core/clientContext.ts +10 -32
  75. package/src/core/fontAwesome.ts +2 -9
  76. package/src/core/onboardingRepository.ts +18 -13
  77. package/src/native/asyncStorage.ts +99 -0
  78. package/src/native/deviceInfo.ts +88 -0
  79. package/src/native/inAppReview.ts +34 -0
  80. package/src/native/linearGradient.tsx +24 -0
  81. package/src/native/lottie.tsx +17 -0
  82. package/src/native/maskedView.tsx +19 -0
  83. package/src/native/pagerView.tsx +95 -0
  84. package/src/native/permissions.ts +59 -0
  85. package/src/native/runtime.ts +110 -0
  86. package/src/native/safeAreaContext.tsx +44 -0
  87. package/src/native/svg.tsx +82 -0
  88. package/src/native/vectorIcons.tsx +50 -0
package/README.md CHANGED
@@ -1,42 +1,154 @@
1
1
  # flowboard-react
2
2
 
3
- Flowboard React Native SDK. JSON-driven onboarding flows with custom screens and full SDUI rendering parity with the Flutter package.
3
+ Flowboard React Native SDK for SDUI onboarding flows.
4
4
 
5
- ## Installation
5
+ It supports:
6
+
7
+ - Expo development builds and prebuild/EAS workflows
8
+ - React Native CLI apps
9
+ - JSON-driven onboarding with custom screens and full Flowboard renderer support
10
+
11
+ ## Quick Start
12
+
13
+ Install the SDK:
6
14
 
7
15
  ```sh
8
16
  npm install flowboard-react
9
17
  ```
10
18
 
11
- Install compatible versions of the peer/native dependencies for your app that satisfy these minimums:
19
+ Install the native peer dependencies in one step:
12
20
 
13
21
  ```sh
14
- npm install \
15
- @react-native-async-storage/async-storage \
16
- @react-native-masked-view/masked-view \
17
- lottie-react-native \
18
- react-native-device-info \
19
- react-native-get-random-values \
20
- react-native-in-app-review \
21
- react-native-linear-gradient \
22
- react-native-mask-input \
23
- react-native-pager-view \
24
- react-native-permissions \
25
- react-native-safe-area-context \
26
- react-native-svg \
27
- react-native-vector-icons
22
+ npx --package flowboard-react flowboard-setup
28
23
  ```
29
24
 
30
- `flowboard-react` ships `expr-eval` and `uuid` itself, so host apps do not need to install them separately.
31
-
32
- iOS:
25
+ Preview the commands first:
33
26
 
34
27
  ```sh
35
- cd ios && pod install
28
+ npx --package flowboard-react flowboard-setup --dry-run
29
+ ```
30
+
31
+ `flowboard-react` keeps heavy native modules as peer dependencies to avoid duplicate binaries and version conflicts in host apps. The setup command installs the required peer set for you.
32
+
33
+ ## Expo Support
34
+
35
+ Expo Go is not supported. This SDK depends on custom native modules, so Expo users need a development build, `expo prebuild`, or EAS Build.
36
+
37
+ After running `flowboard-setup`, add the plugin to your app config:
38
+
39
+ ```json
40
+ {
41
+ "expo": {
42
+ "plugins": ["flowboard-react"]
43
+ }
44
+ }
45
+ ```
46
+
47
+ If your onboarding flow uses `request_permission` actions, configure the plugin with permission options:
48
+
49
+ ```json
50
+ {
51
+ "expo": {
52
+ "plugins": [
53
+ [
54
+ "flowboard-react",
55
+ {
56
+ "permissions": ["camera", "photos", "microphone"],
57
+ "permissionMessages": {
58
+ "camera": "Allow $(PRODUCT_NAME) to access the camera during onboarding.",
59
+ "photos": "Allow $(PRODUCT_NAME) to access your photo library during onboarding.",
60
+ "microphone": "Allow $(PRODUCT_NAME) to access the microphone during onboarding."
61
+ }
62
+ }
63
+ ]
64
+ ]
65
+ }
66
+ }
67
+ ```
68
+
69
+ Supported plugin options:
70
+
71
+ - `permissions`: `notifications`, `contacts`, `location`, `camera`, `microphone`, `photos`
72
+ - `permissionMessages`: override default iOS usage descriptions for supported Flowboard permissions
73
+ - `iosPermissions`: append advanced `react-native-permissions` iOS entries directly
74
+ - `androidPermissions`: append raw Android permissions directly
75
+ - `includeVectorIconFonts`: defaults to `false`
76
+ - `iconFonts`: override the bundled FontAwesome6 font file list
77
+
78
+ The plugin does three things:
79
+
80
+ - Composes `react-native-permissions` for iOS permission Podfile setup when needed
81
+ - Adds Android manifest permissions for Flowboard permission actions
82
+ - Optionally bundles FontAwesome 6 fonts through `expo-font` when `includeVectorIconFonts` is enabled
83
+
84
+ If your Expo app needs Flowboard's bundled icon fonts, enable them explicitly:
85
+
86
+ ```json
87
+ {
88
+ "expo": {
89
+ "plugins": [
90
+ [
91
+ "flowboard-react",
92
+ {
93
+ "includeVectorIconFonts": true
94
+ }
95
+ ]
96
+ ]
97
+ }
98
+ }
36
99
  ```
37
100
 
101
+ ## React Native CLI Support
102
+
103
+ `flowboard-setup` installs the validated peer set for React Native CLI apps too, but some native configuration still belongs to the host app.
104
+
105
+ After installation:
106
+
107
+ 1. Run `cd ios && pod install`.
108
+ 2. Configure `react-native-permissions` in your `Podfile`, `Info.plist`, and `AndroidManifest.xml` if your flow uses permission actions.
109
+ 3. Configure `react-native-vector-icons` font bundling if your host app does not already do it.
110
+
111
+ The SDK does not edit your app config files automatically in this phase. It installs the packages and prints the exact next steps.
112
+
113
+ ## What `flowboard-setup` Installs
114
+
115
+ | Package | Why it stays a peer |
116
+ | ------------------------------------------- | ------------------------------ |
117
+ | `@react-native-async-storage/async-storage` | Native persistence backend |
118
+ | `@react-native-masked-view/masked-view` | Native masked rendering |
119
+ | `lottie-react-native` | Native animation runtime |
120
+ | `react-native-device-info` | Native device/app metadata |
121
+ | `react-native-get-random-values` | Native crypto randomness bridge |
122
+ | `react-native-in-app-review` | Native review prompt support |
123
+ | `react-native-linear-gradient` | Native gradient rendering |
124
+ | `react-native-pager-view` | Native paged screen containers |
125
+ | `react-native-permissions` | Native permissions bridge |
126
+ | `react-native-safe-area-context` | Native safe-area insets |
127
+ | `react-native-svg` | Native vector rendering |
128
+ | `react-native-vector-icons` | Native font/icon bundling |
129
+
130
+ Expo apps also get `expo-font` so the plugin can bundle the FontAwesome 6 fonts used by Flowboard icons when `includeVectorIconFonts` is enabled.
131
+
132
+ These JS-only helpers are bundled by the SDK itself and do not need to be installed by host apps:
133
+
134
+ - `react-native-mask-input`
135
+ - `expr-eval`
136
+ - `uuid`
137
+
138
+ ## Installation Flags
139
+
140
+ The setup command supports:
141
+
142
+ - `--dry-run`: print commands without installing anything
143
+ - `--yes`: skip the confirmation prompt
144
+ - `--upgrade`: force reinstall of the validated package set
145
+ - `--verbose`: print each command before it runs
146
+ - `--project-root <path>`: target a different host app directory
147
+
38
148
  ## Usage
39
149
 
150
+ Initialize Flowboard once near app startup:
151
+
40
152
  ```tsx
41
153
  import React from 'react';
42
154
  import { Flowboard, FlowboardProvider } from 'flowboard-react';
@@ -53,13 +165,18 @@ export default function App() {
53
165
  }
54
166
  ```
55
167
 
56
- Launch onboarding:
168
+ Launch the default onboarding:
57
169
 
58
170
  ```tsx
59
171
  await Flowboard.launchOnboarding({
60
172
  customScreenBuilder: (ctx) => {
61
173
  if (ctx.screenData.id === 'paywall') {
62
- return <MyPaywall onClose={ctx.onNext} onSignup={() => ctx.onJumpTo('signup')} />;
174
+ return (
175
+ <MyPaywall
176
+ onClose={ctx.onNext}
177
+ onSignup={() => ctx.onJumpTo('signup')}
178
+ />
179
+ );
63
180
  }
64
181
  return null;
65
182
  },
@@ -69,7 +186,7 @@ await Flowboard.launchOnboarding({
69
186
  });
70
187
  ```
71
188
 
72
- Launch a specific onboarding by ID (without replacing default cached onboarding used by `launchOnboarding`):
189
+ Launch a specific onboarding by ID:
73
190
 
74
191
  ```tsx
75
192
  await Flowboard.launchOnboardingById('YOUR_ONBOARDING_ID', {
@@ -81,7 +198,7 @@ await Flowboard.launchOnboardingById('YOUR_ONBOARDING_ID', {
81
198
  });
82
199
  ```
83
200
 
84
- Re-initialize:
201
+ Re-initialize if you rotate credentials or environment:
85
202
 
86
203
  ```tsx
87
204
  await Flowboard.reinit();
@@ -93,11 +210,11 @@ await Flowboard.reinit();
93
210
  - `Flowboard.launchOnboarding({ customScreenBuilder, customActionBuilder, onOnboardEnd, onStepChange, enableAnalytics, alwaysRestart, resumeProgress })`
94
211
  - `Flowboard.launchOnboardingById(onboardingId, { locale, version, customScreenBuilder, customActionBuilder, onOnboardEnd, onStepChange, enableAnalytics, alwaysRestart, resumeProgress })`
95
212
  - `Flowboard.reinit()`
96
- - `FlowboardProvider` (required to host modal flow)
213
+ - `FlowboardProvider`
97
214
 
98
- ## Jump To Action
215
+ ## Jump To Actions
99
216
 
100
- Configure direct screen navigation in JSON with `action: "jump_to"` and an `actionData.screenId` target:
217
+ Use `jump_to` in Flowboard JSON:
101
218
 
102
219
  ```json
103
220
  {
@@ -117,25 +234,53 @@ Inside custom screens, use `ctx.onJumpTo(screenId)`:
117
234
  ```tsx
118
235
  customScreenBuilder: (ctx) => {
119
236
  if (ctx.screenData.id === 'paywall') {
120
- return <Button title="Jump to Sign Up" onPress={() => ctx.onJumpTo('signup')} />;
237
+ return (
238
+ <Button title="Jump to Sign Up" onPress={() => ctx.onJumpTo('signup')} />
239
+ );
121
240
  }
122
241
  return null;
123
242
  };
124
243
  ```
125
244
 
126
- ## Parity Checklist
245
+ ## Runtime Resilience
246
+
247
+ Flowboard wraps its native peers behind defensive adapters.
127
248
 
128
- - [x] Flowboard init/launch/reinit
129
- - [x] Client context capture + resolver payload
130
- - [x] Analytics events (onboard_loaded, onboard_started, screen_view, onboard_ended)
131
- - [x] Cache + progress persistence
132
- - [x] SDUI components (layout, visual, inputs, advanced)
133
- - [x] Custom screens/actions
134
- - [x] Deeplink/weblink, rate_app, request_permission, jump_to
135
- - [x] Feature showcase example app
136
- - [x] Jest unit tests for critical logic
249
+ If a native module is installed but not linked or configured correctly:
137
250
 
138
- ## Testing
251
+ - the SDK logs a development warning once
252
+ - storage falls back to memory
253
+ - permissions return `unavailable`
254
+ - in-app review is skipped
255
+ - gradients, masks, paging, SVG, and Lottie fall back to simplified rendering where possible
256
+
257
+ This does not replace installation. Metro still requires the peer packages to exist in the host app dependency graph.
258
+
259
+ ## Troubleshooting
260
+
261
+ `Plugin "flowboard-react" was not found`
262
+
263
+ - Make sure `flowboard-react` is installed in the host app.
264
+ - Re-run `npx --package flowboard-react flowboard-setup`.
265
+
266
+ Icons render as empty boxes
267
+
268
+ - Expo: confirm `plugins: ["flowboard-react"]` is present and rebuild the development client.
269
+ - React Native CLI: confirm `react-native-vector-icons` fonts are bundled in the native app.
270
+
271
+ Permission requests do nothing on iOS
272
+
273
+ - Make sure `react-native-permissions` is configured in the host app Podfile.
274
+ - Confirm the matching `Info.plist` usage descriptions exist.
275
+ - Re-run `pod install`.
276
+
277
+ Warnings about unavailable native modules
278
+
279
+ - The package is present, but native setup is incomplete.
280
+ - Rebuild the app after installing peers.
281
+ - Verify `pod install`, Android permissions, and Expo plugin setup for the affected module.
282
+
283
+ ## Development
139
284
 
140
285
  ```sh
141
286
  yarn typecheck
@@ -143,18 +288,6 @@ yarn test
143
288
  yarn lint
144
289
  ```
145
290
 
146
- ## Notes
147
-
148
- - Custom fonts must be available in the host app when using `fontFamily`.
149
- - Asset images/Lottie ("source": "asset") should be provided as `uri` or through a custom loader in the host app.
150
- - Radar charts support optional `autoFitLabels` (default `true`) and `labelOffset` (default `max(20, fontSize * 1.5)`) to keep axis labels inside bounds.
151
-
152
- ## Contributing
153
-
154
- - [Development workflow](CONTRIBUTING.md#development-workflow)
155
- - [Sending a pull request](CONTRIBUTING.md#sending-a-pull-request)
156
- - [Code of conduct](CODE_OF_CONDUCT.md)
157
-
158
291
  ## License
159
292
 
160
293
  MIT
package/app.plugin.js ADDED
@@ -0,0 +1,341 @@
1
+ const fs = require('node:fs');
2
+ const path = require('node:path');
3
+
4
+ const DEFAULT_ICON_FONTS = [
5
+ 'FontAwesome6_Brands.ttf',
6
+ 'FontAwesome6_Regular.ttf',
7
+ 'FontAwesome6_Solid.ttf',
8
+ ];
9
+
10
+ const FLOWBOARD_PERMISSION_MAP = {
11
+ camera: {
12
+ android: [{ name: 'android.permission.CAMERA' }],
13
+ infoPlistKey: 'NSCameraUsageDescription',
14
+ infoPlistMessage:
15
+ 'Allow $(PRODUCT_NAME) to access the camera during onboarding.',
16
+ ios: 'Camera',
17
+ },
18
+ contacts: {
19
+ android: [{ name: 'android.permission.READ_CONTACTS' }],
20
+ infoPlistKey: 'NSContactsUsageDescription',
21
+ infoPlistMessage:
22
+ 'Allow $(PRODUCT_NAME) to access your contacts during onboarding.',
23
+ ios: 'Contacts',
24
+ },
25
+ location: {
26
+ android: [{ name: 'android.permission.ACCESS_FINE_LOCATION' }],
27
+ infoPlistKey: 'NSLocationWhenInUseUsageDescription',
28
+ infoPlistMessage:
29
+ 'Allow $(PRODUCT_NAME) to access your location during onboarding.',
30
+ ios: 'LocationWhenInUse',
31
+ },
32
+ microphone: {
33
+ android: [{ name: 'android.permission.RECORD_AUDIO' }],
34
+ infoPlistKey: 'NSMicrophoneUsageDescription',
35
+ infoPlistMessage:
36
+ 'Allow $(PRODUCT_NAME) to access the microphone during onboarding.',
37
+ ios: 'Microphone',
38
+ },
39
+ notifications: {
40
+ android: [{ name: 'android.permission.POST_NOTIFICATIONS' }],
41
+ ios: 'Notifications',
42
+ },
43
+ photos: {
44
+ android: [
45
+ { name: 'android.permission.READ_MEDIA_IMAGES' },
46
+ {
47
+ attributes: { 'android:maxSdkVersion': '32' },
48
+ name: 'android.permission.READ_EXTERNAL_STORAGE',
49
+ },
50
+ ],
51
+ infoPlistKey: 'NSPhotoLibraryUsageDescription',
52
+ infoPlistMessage:
53
+ 'Allow $(PRODUCT_NAME) to access your photo library during onboarding.',
54
+ ios: 'PhotoLibrary',
55
+ },
56
+ };
57
+
58
+ function unique(values) {
59
+ return [...new Set(values.filter(Boolean))];
60
+ }
61
+
62
+ function getProjectRoot(config) {
63
+ return config?._internal?.projectRoot || process.cwd();
64
+ }
65
+
66
+ function warn(message) {
67
+ console.warn(`[flowboard-react/plugin] ${message}`);
68
+ }
69
+
70
+ function normalizeFlowboardPermissions(permissions) {
71
+ if (!Array.isArray(permissions)) return [];
72
+ return unique(
73
+ permissions
74
+ .map((permission) =>
75
+ String(permission || '')
76
+ .trim()
77
+ .toLowerCase()
78
+ )
79
+ .filter((permission) => FLOWBOARD_PERMISSION_MAP[permission])
80
+ );
81
+ }
82
+
83
+ function resolveIosPermissions(options) {
84
+ const inferred = normalizeFlowboardPermissions(options.permissions).map(
85
+ (permission) => FLOWBOARD_PERMISSION_MAP[permission].ios
86
+ );
87
+ const extras = Array.isArray(options.iosPermissions)
88
+ ? options.iosPermissions.map((permission) =>
89
+ String(permission || '').trim()
90
+ )
91
+ : [];
92
+ return unique([...inferred, ...extras]);
93
+ }
94
+
95
+ function resolveAndroidPermissions(options) {
96
+ const inferred = normalizeFlowboardPermissions(options.permissions).flatMap(
97
+ (permission) => FLOWBOARD_PERMISSION_MAP[permission].android || []
98
+ );
99
+ const extras = Array.isArray(options.androidPermissions)
100
+ ? options.androidPermissions
101
+ .map((permission) => String(permission || '').trim())
102
+ .filter(Boolean)
103
+ .map((name) => ({ name }))
104
+ : [];
105
+
106
+ return [...inferred, ...extras].reduce((acc, entry) => {
107
+ const name = entry.name;
108
+ const attributes = entry.attributes || {};
109
+ const duplicate = acc.some(
110
+ (candidate) =>
111
+ candidate.name === name &&
112
+ JSON.stringify(candidate.attributes || {}) ===
113
+ JSON.stringify(attributes)
114
+ );
115
+ if (!duplicate) {
116
+ acc.push({ attributes, name });
117
+ }
118
+ return acc;
119
+ }, []);
120
+ }
121
+
122
+ function resolveInfoPlistEntries(options) {
123
+ const overrides = options.permissionMessages || {};
124
+ return normalizeFlowboardPermissions(options.permissions).reduce(
125
+ (acc, permission) => {
126
+ const config = FLOWBOARD_PERMISSION_MAP[permission];
127
+ if (!config.infoPlistKey) return acc;
128
+ acc[config.infoPlistKey] =
129
+ overrides[permission] || config.infoPlistMessage;
130
+ return acc;
131
+ },
132
+ {}
133
+ );
134
+ }
135
+
136
+ function resolveOptionalModule(projectRoot, requests) {
137
+ const candidates = Array.isArray(requests) ? requests : [requests];
138
+ for (const request of candidates) {
139
+ try {
140
+ const resolvedPath = require.resolve(request, { paths: [projectRoot] });
141
+ return {
142
+ module: require(resolvedPath),
143
+ resolvedPath,
144
+ };
145
+ } catch {
146
+ continue;
147
+ }
148
+ }
149
+ return null;
150
+ }
151
+
152
+ function createEnvironment(projectRoot) {
153
+ const configPlugins = resolveOptionalModule(
154
+ projectRoot,
155
+ '@expo/config-plugins'
156
+ );
157
+ return {
158
+ configPlugins: configPlugins ? configPlugins.module : null,
159
+ loadOptionalModule: (requests) =>
160
+ resolveOptionalModule(projectRoot, requests),
161
+ projectRoot,
162
+ warn,
163
+ };
164
+ }
165
+
166
+ function applyPermissionsPlugin(config, options, env) {
167
+ const iosPermissions = resolveIosPermissions(options);
168
+ if (iosPermissions.length === 0) return config;
169
+
170
+ const pluginModule = env.loadOptionalModule([
171
+ 'react-native-permissions/app.plugin.js',
172
+ 'react-native-permissions',
173
+ ]);
174
+ if (!pluginModule) {
175
+ env.warn(
176
+ 'react-native-permissions was not found in the host app. iOS Podfile setup was skipped.'
177
+ );
178
+ return config;
179
+ }
180
+
181
+ const plugin = pluginModule.module.default || pluginModule.module;
182
+ if (typeof plugin !== 'function') {
183
+ env.warn(
184
+ 'react-native-permissions did not expose a valid Expo plugin. iOS Podfile setup was skipped.'
185
+ );
186
+ return config;
187
+ }
188
+
189
+ return plugin(config, { iosPermissions });
190
+ }
191
+
192
+ function resolveVectorIconFontPaths(options, env) {
193
+ if (options.includeVectorIconFonts !== true) return [];
194
+
195
+ const iconFonts =
196
+ Array.isArray(options.iconFonts) && options.iconFonts.length > 0
197
+ ? options.iconFonts
198
+ : DEFAULT_ICON_FONTS;
199
+
200
+ const packageJson = env.loadOptionalModule(
201
+ 'react-native-vector-icons/package.json'
202
+ );
203
+ if (!packageJson) {
204
+ env.warn(
205
+ 'react-native-vector-icons was not found in the host app. Font bundling for Expo was skipped.'
206
+ );
207
+ return [];
208
+ }
209
+
210
+ const packageRoot = path.dirname(packageJson.resolvedPath);
211
+ return iconFonts
212
+ .map((fontFile) => path.join(packageRoot, 'Fonts', fontFile))
213
+ .filter((fontPath) => {
214
+ if (fs.existsSync(fontPath)) return true;
215
+ env.warn(`Missing vector icon font file: ${fontPath}`);
216
+ return false;
217
+ });
218
+ }
219
+
220
+ function applyExpoFontPlugin(config, options, env) {
221
+ const fonts = resolveVectorIconFontPaths(options, env);
222
+ if (fonts.length === 0) return config;
223
+
224
+ const pluginModule = env.loadOptionalModule([
225
+ 'expo-font/app.plugin.js',
226
+ 'expo-font',
227
+ ]);
228
+ if (!pluginModule) {
229
+ env.warn(
230
+ 'expo-font was not found in the host app. Font bundling for Expo was skipped.'
231
+ );
232
+ return config;
233
+ }
234
+
235
+ const plugin = pluginModule.module.default || pluginModule.module;
236
+ if (typeof plugin !== 'function') {
237
+ env.warn(
238
+ 'expo-font did not expose a valid Expo plugin. Font bundling was skipped.'
239
+ );
240
+ return config;
241
+ }
242
+
243
+ return plugin(config, { fonts });
244
+ }
245
+
246
+ function ensureAndroidPermission(manifest, permission) {
247
+ const usesPermissions = manifest.manifest['uses-permission'] || [];
248
+ const nextEntry = {
249
+ $: {
250
+ 'android:name': permission.name,
251
+ ...(permission.attributes || {}),
252
+ },
253
+ };
254
+
255
+ const duplicate = usesPermissions.some((entry) => {
256
+ const current = entry.$ || {};
257
+ return (
258
+ current['android:name'] === nextEntry.$['android:name'] &&
259
+ current['android:maxSdkVersion'] === nextEntry.$['android:maxSdkVersion']
260
+ );
261
+ });
262
+
263
+ if (!duplicate) {
264
+ usesPermissions.push(nextEntry);
265
+ }
266
+
267
+ manifest.manifest['uses-permission'] = usesPermissions;
268
+ return manifest;
269
+ }
270
+
271
+ function applyFlowboardPlugin(config, options = {}, env) {
272
+ const configPlugins = env.configPlugins;
273
+ if (
274
+ !configPlugins ||
275
+ typeof configPlugins.withAndroidManifest !== 'function' ||
276
+ typeof configPlugins.withInfoPlist !== 'function'
277
+ ) {
278
+ env.warn(
279
+ '@expo/config-plugins was not found in the host app. Flowboard Expo configuration was skipped.'
280
+ );
281
+ return config;
282
+ }
283
+
284
+ let nextConfig = config;
285
+ nextConfig = applyPermissionsPlugin(nextConfig, options, env);
286
+ nextConfig = applyExpoFontPlugin(nextConfig, options, env);
287
+
288
+ nextConfig = configPlugins.withInfoPlist(nextConfig, (infoConfig) => {
289
+ const entries = resolveInfoPlistEntries(options);
290
+ infoConfig.modResults = infoConfig.modResults || {};
291
+
292
+ Object.entries(entries).forEach(([key, value]) => {
293
+ if (!infoConfig.modResults[key]) {
294
+ infoConfig.modResults[key] = value;
295
+ }
296
+ });
297
+
298
+ return infoConfig;
299
+ });
300
+
301
+ nextConfig = configPlugins.withAndroidManifest(
302
+ nextConfig,
303
+ (androidConfig) => {
304
+ androidConfig.modResults = androidConfig.modResults || { manifest: {} };
305
+ androidConfig.modResults.manifest =
306
+ androidConfig.modResults.manifest || {};
307
+
308
+ resolveAndroidPermissions(options).forEach((permission) => {
309
+ ensureAndroidPermission(androidConfig.modResults, permission);
310
+ });
311
+
312
+ return androidConfig;
313
+ }
314
+ );
315
+
316
+ return nextConfig;
317
+ }
318
+
319
+ function withFlowboardReact(config, options = {}) {
320
+ const projectRoot = getProjectRoot(config);
321
+ const env = createEnvironment(projectRoot);
322
+ return applyFlowboardPlugin(config, options, env);
323
+ }
324
+
325
+ module.exports = withFlowboardReact;
326
+ module.exports._internal = {
327
+ DEFAULT_ICON_FONTS,
328
+ FLOWBOARD_PERMISSION_MAP,
329
+ applyExpoFontPlugin,
330
+ applyFlowboardPlugin,
331
+ applyPermissionsPlugin,
332
+ createEnvironment,
333
+ ensureAndroidPermission,
334
+ getProjectRoot,
335
+ normalizeFlowboardPermissions,
336
+ resolveAndroidPermissions,
337
+ resolveInfoPlistEntries,
338
+ resolveIosPermissions,
339
+ resolveOptionalModule,
340
+ resolveVectorIconFontPaths,
341
+ };