flowboard-react 0.6.1 → 0.6.3
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.
- package/README.md +168 -52
- package/app.plugin.js +341 -0
- package/bin/setup.js +427 -0
- package/lib/module/Flowboard.js +1 -2
- package/lib/module/Flowboard.js.map +1 -1
- package/lib/module/FlowboardProvider.js +1 -1
- package/lib/module/FlowboardProvider.js.map +1 -1
- package/lib/module/components/FlowboardFlow.js +31 -9
- package/lib/module/components/FlowboardFlow.js.map +1 -1
- package/lib/module/components/FlowboardRenderer.js +419 -85
- package/lib/module/components/FlowboardRenderer.js.map +1 -1
- package/lib/module/core/clientContext.js +10 -29
- package/lib/module/core/clientContext.js.map +1 -1
- package/lib/module/core/fontAwesome.js +2 -9
- package/lib/module/core/fontAwesome.js.map +1 -1
- package/lib/module/core/onboardingRepository.js +13 -13
- package/lib/module/core/onboardingRepository.js.map +1 -1
- package/lib/module/native/asyncStorage.js +55 -0
- package/lib/module/native/asyncStorage.js.map +1 -0
- package/lib/module/native/deviceInfo.js +46 -0
- package/lib/module/native/deviceInfo.js.map +1 -0
- package/lib/module/native/inAppReview.js +17 -0
- package/lib/module/native/inAppReview.js.map +1 -0
- package/lib/module/native/linearGradient.js +29 -0
- package/lib/module/native/linearGradient.js.map +1 -0
- package/lib/module/native/lottie.js +20 -0
- package/lib/module/native/lottie.js.map +1 -0
- package/lib/module/native/maskedView.js +26 -0
- package/lib/module/native/maskedView.js.map +1 -0
- package/lib/module/native/pagerView.js +79 -0
- package/lib/module/native/pagerView.js.map +1 -0
- package/lib/module/native/permissions.js +30 -0
- package/lib/module/native/permissions.js.map +1 -0
- package/lib/module/native/runtime.js +81 -0
- package/lib/module/native/runtime.js.map +1 -0
- package/lib/module/native/safeAreaContext.js +43 -0
- package/lib/module/native/safeAreaContext.js.map +1 -0
- package/lib/module/native/svg.js +83 -0
- package/lib/module/native/svg.js.map +1 -0
- package/lib/module/native/vectorIcons.js +41 -0
- package/lib/module/native/vectorIcons.js.map +1 -0
- package/lib/module/utils/flowboardUtils.js +4 -1
- package/lib/module/utils/flowboardUtils.js.map +1 -1
- package/lib/typescript/src/Flowboard.d.ts.map +1 -1
- package/lib/typescript/src/components/FlowboardFlow.d.ts.map +1 -1
- package/lib/typescript/src/components/FlowboardRenderer.d.ts +33 -0
- package/lib/typescript/src/components/FlowboardRenderer.d.ts.map +1 -1
- package/lib/typescript/src/core/clientContext.d.ts.map +1 -1
- package/lib/typescript/src/core/fontAwesome.d.ts +1 -1
- package/lib/typescript/src/core/fontAwesome.d.ts.map +1 -1
- package/lib/typescript/src/core/onboardingRepository.d.ts.map +1 -1
- package/lib/typescript/src/native/asyncStorage.d.ts +16 -0
- package/lib/typescript/src/native/asyncStorage.d.ts.map +1 -0
- package/lib/typescript/src/native/deviceInfo.d.ts +11 -0
- package/lib/typescript/src/native/deviceInfo.d.ts.map +1 -0
- package/lib/typescript/src/native/inAppReview.d.ts +4 -0
- package/lib/typescript/src/native/inAppReview.d.ts.map +1 -0
- package/lib/typescript/src/native/linearGradient.d.ts +3 -0
- package/lib/typescript/src/native/linearGradient.d.ts.map +1 -0
- package/lib/typescript/src/native/lottie.d.ts +3 -0
- package/lib/typescript/src/native/lottie.d.ts.map +1 -0
- package/lib/typescript/src/native/maskedView.d.ts +3 -0
- package/lib/typescript/src/native/maskedView.d.ts.map +1 -0
- package/lib/typescript/src/native/pagerView.d.ts +14 -0
- package/lib/typescript/src/native/pagerView.d.ts.map +1 -0
- package/lib/typescript/src/native/permissions.d.ts +9 -0
- package/lib/typescript/src/native/permissions.d.ts.map +1 -0
- package/lib/typescript/src/native/runtime.d.ts +13 -0
- package/lib/typescript/src/native/runtime.d.ts.map +1 -0
- package/lib/typescript/src/native/safeAreaContext.d.ts +15 -0
- package/lib/typescript/src/native/safeAreaContext.d.ts.map +1 -0
- package/lib/typescript/src/native/svg.d.ts +8 -0
- package/lib/typescript/src/native/svg.d.ts.map +1 -0
- package/lib/typescript/src/native/vectorIcons.d.ts +4 -0
- package/lib/typescript/src/native/vectorIcons.d.ts.map +1 -0
- package/lib/typescript/src/utils/flowboardUtils.d.ts.map +1 -1
- package/package.json +24 -19
- package/src/Flowboard.ts +1 -2
- package/src/FlowboardProvider.tsx +1 -1
- package/src/components/FlowboardFlow.tsx +47 -9
- package/src/components/FlowboardRenderer.tsx +689 -113
- package/src/core/clientContext.ts +10 -32
- package/src/core/fontAwesome.ts +2 -9
- package/src/core/onboardingRepository.ts +18 -13
- package/src/native/asyncStorage.ts +99 -0
- package/src/native/deviceInfo.ts +88 -0
- package/src/native/inAppReview.ts +34 -0
- package/src/native/linearGradient.tsx +24 -0
- package/src/native/lottie.tsx +17 -0
- package/src/native/maskedView.tsx +19 -0
- package/src/native/pagerView.tsx +95 -0
- package/src/native/permissions.ts +59 -0
- package/src/native/runtime.ts +110 -0
- package/src/native/safeAreaContext.tsx +44 -0
- package/src/native/svg.tsx +82 -0
- package/src/native/vectorIcons.tsx +50 -0
- package/src/utils/flowboardUtils.ts +9 -1
package/README.md
CHANGED
|
@@ -1,42 +1,137 @@
|
|
|
1
1
|
# flowboard-react
|
|
2
2
|
|
|
3
|
-
Flowboard React Native SDK
|
|
3
|
+
Flowboard React Native SDK for SDUI onboarding flows.
|
|
4
4
|
|
|
5
|
-
|
|
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
|
|
19
|
+
Install the native peer dependencies in one step:
|
|
12
20
|
|
|
13
21
|
```sh
|
|
14
|
-
|
|
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-gesture-handler \
|
|
20
|
-
react-native-get-random-values \
|
|
21
|
-
react-native-in-app-review \
|
|
22
|
-
react-native-linear-gradient \
|
|
23
|
-
react-native-mask-input \
|
|
24
|
-
react-native-pager-view \
|
|
25
|
-
react-native-permissions \
|
|
26
|
-
react-native-safe-area-context@^5 \
|
|
27
|
-
react-native-svg \
|
|
28
|
-
react-native-vector-icons \
|
|
29
|
-
uuid
|
|
22
|
+
npx --package flowboard-react flowboard-setup
|
|
30
23
|
```
|
|
31
24
|
|
|
32
|
-
|
|
25
|
+
Preview the commands first:
|
|
33
26
|
|
|
34
27
|
```sh
|
|
35
|
-
|
|
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
|
+
}
|
|
36
45
|
```
|
|
37
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 `true`
|
|
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
|
+
- Bundles FontAwesome 6 fonts through `expo-font` so icons work in Expo builds
|
|
83
|
+
|
|
84
|
+
## React Native CLI Support
|
|
85
|
+
|
|
86
|
+
`flowboard-setup` installs the validated peer set for React Native CLI apps too, but some native configuration still belongs to the host app.
|
|
87
|
+
|
|
88
|
+
After installation:
|
|
89
|
+
|
|
90
|
+
1. Run `cd ios && pod install`.
|
|
91
|
+
2. Configure `react-native-permissions` in your `Podfile`, `Info.plist`, and `AndroidManifest.xml` if your flow uses permission actions.
|
|
92
|
+
3. Configure `react-native-vector-icons` font bundling if your host app does not already do it.
|
|
93
|
+
|
|
94
|
+
The SDK does not edit your app config files automatically in this phase. It installs the packages and prints the exact next steps.
|
|
95
|
+
|
|
96
|
+
## What `flowboard-setup` Installs
|
|
97
|
+
|
|
98
|
+
| Package | Why it stays a peer |
|
|
99
|
+
| ------------------------------------------- | ------------------------------ |
|
|
100
|
+
| `@react-native-async-storage/async-storage` | Native persistence backend |
|
|
101
|
+
| `@react-native-masked-view/masked-view` | Native masked rendering |
|
|
102
|
+
| `lottie-react-native` | Native animation runtime |
|
|
103
|
+
| `react-native-device-info` | Native device/app metadata |
|
|
104
|
+
| `react-native-in-app-review` | Native review prompt support |
|
|
105
|
+
| `react-native-linear-gradient` | Native gradient rendering |
|
|
106
|
+
| `react-native-pager-view` | Native paged screen containers |
|
|
107
|
+
| `react-native-permissions` | Native permissions bridge |
|
|
108
|
+
| `react-native-safe-area-context` | Native safe-area insets |
|
|
109
|
+
| `react-native-svg` | Native vector rendering |
|
|
110
|
+
| `react-native-vector-icons` | Native font/icon bundling |
|
|
111
|
+
|
|
112
|
+
Expo apps also get `expo-font` so the plugin can bundle the FontAwesome 6 fonts used by Flowboard icons.
|
|
113
|
+
|
|
114
|
+
These JS-only helpers are bundled by the SDK itself and do not need to be installed by host apps:
|
|
115
|
+
|
|
116
|
+
- `react-native-get-random-values`
|
|
117
|
+
- `react-native-mask-input`
|
|
118
|
+
- `expr-eval`
|
|
119
|
+
- `uuid`
|
|
120
|
+
|
|
121
|
+
## Installation Flags
|
|
122
|
+
|
|
123
|
+
The setup command supports:
|
|
124
|
+
|
|
125
|
+
- `--dry-run`: print commands without installing anything
|
|
126
|
+
- `--yes`: skip the confirmation prompt
|
|
127
|
+
- `--upgrade`: force reinstall of the validated package set
|
|
128
|
+
- `--verbose`: print each command before it runs
|
|
129
|
+
- `--project-root <path>`: target a different host app directory
|
|
130
|
+
|
|
38
131
|
## Usage
|
|
39
132
|
|
|
133
|
+
Initialize Flowboard once near app startup:
|
|
134
|
+
|
|
40
135
|
```tsx
|
|
41
136
|
import React from 'react';
|
|
42
137
|
import { Flowboard, FlowboardProvider } from 'flowboard-react';
|
|
@@ -53,13 +148,18 @@ export default function App() {
|
|
|
53
148
|
}
|
|
54
149
|
```
|
|
55
150
|
|
|
56
|
-
Launch onboarding:
|
|
151
|
+
Launch the default onboarding:
|
|
57
152
|
|
|
58
153
|
```tsx
|
|
59
154
|
await Flowboard.launchOnboarding({
|
|
60
155
|
customScreenBuilder: (ctx) => {
|
|
61
156
|
if (ctx.screenData.id === 'paywall') {
|
|
62
|
-
return
|
|
157
|
+
return (
|
|
158
|
+
<MyPaywall
|
|
159
|
+
onClose={ctx.onNext}
|
|
160
|
+
onSignup={() => ctx.onJumpTo('signup')}
|
|
161
|
+
/>
|
|
162
|
+
);
|
|
63
163
|
}
|
|
64
164
|
return null;
|
|
65
165
|
},
|
|
@@ -69,7 +169,7 @@ await Flowboard.launchOnboarding({
|
|
|
69
169
|
});
|
|
70
170
|
```
|
|
71
171
|
|
|
72
|
-
Launch a specific onboarding by ID
|
|
172
|
+
Launch a specific onboarding by ID:
|
|
73
173
|
|
|
74
174
|
```tsx
|
|
75
175
|
await Flowboard.launchOnboardingById('YOUR_ONBOARDING_ID', {
|
|
@@ -81,7 +181,7 @@ await Flowboard.launchOnboardingById('YOUR_ONBOARDING_ID', {
|
|
|
81
181
|
});
|
|
82
182
|
```
|
|
83
183
|
|
|
84
|
-
Re-initialize:
|
|
184
|
+
Re-initialize if you rotate credentials or environment:
|
|
85
185
|
|
|
86
186
|
```tsx
|
|
87
187
|
await Flowboard.reinit();
|
|
@@ -93,11 +193,11 @@ await Flowboard.reinit();
|
|
|
93
193
|
- `Flowboard.launchOnboarding({ customScreenBuilder, customActionBuilder, onOnboardEnd, onStepChange, enableAnalytics, alwaysRestart, resumeProgress })`
|
|
94
194
|
- `Flowboard.launchOnboardingById(onboardingId, { locale, version, customScreenBuilder, customActionBuilder, onOnboardEnd, onStepChange, enableAnalytics, alwaysRestart, resumeProgress })`
|
|
95
195
|
- `Flowboard.reinit()`
|
|
96
|
-
- `FlowboardProvider`
|
|
196
|
+
- `FlowboardProvider`
|
|
97
197
|
|
|
98
|
-
## Jump To
|
|
198
|
+
## Jump To Actions
|
|
99
199
|
|
|
100
|
-
|
|
200
|
+
Use `jump_to` in Flowboard JSON:
|
|
101
201
|
|
|
102
202
|
```json
|
|
103
203
|
{
|
|
@@ -117,25 +217,53 @@ Inside custom screens, use `ctx.onJumpTo(screenId)`:
|
|
|
117
217
|
```tsx
|
|
118
218
|
customScreenBuilder: (ctx) => {
|
|
119
219
|
if (ctx.screenData.id === 'paywall') {
|
|
120
|
-
return
|
|
220
|
+
return (
|
|
221
|
+
<Button title="Jump to Sign Up" onPress={() => ctx.onJumpTo('signup')} />
|
|
222
|
+
);
|
|
121
223
|
}
|
|
122
224
|
return null;
|
|
123
225
|
};
|
|
124
226
|
```
|
|
125
227
|
|
|
126
|
-
##
|
|
228
|
+
## Runtime Resilience
|
|
229
|
+
|
|
230
|
+
Flowboard wraps its native peers behind defensive adapters.
|
|
231
|
+
|
|
232
|
+
If a native module is installed but not linked or configured correctly:
|
|
233
|
+
|
|
234
|
+
- the SDK logs a development warning once
|
|
235
|
+
- storage falls back to memory
|
|
236
|
+
- permissions return `unavailable`
|
|
237
|
+
- in-app review is skipped
|
|
238
|
+
- gradients, masks, paging, SVG, and Lottie fall back to simplified rendering where possible
|
|
127
239
|
|
|
128
|
-
|
|
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
|
|
240
|
+
This does not replace installation. Metro still requires the peer packages to exist in the host app dependency graph.
|
|
137
241
|
|
|
138
|
-
##
|
|
242
|
+
## Troubleshooting
|
|
243
|
+
|
|
244
|
+
`Plugin "flowboard-react" was not found`
|
|
245
|
+
|
|
246
|
+
- Make sure `flowboard-react` is installed in the host app.
|
|
247
|
+
- Re-run `npx --package flowboard-react flowboard-setup`.
|
|
248
|
+
|
|
249
|
+
Icons render as empty boxes
|
|
250
|
+
|
|
251
|
+
- Expo: confirm `plugins: ["flowboard-react"]` is present and rebuild the development client.
|
|
252
|
+
- React Native CLI: confirm `react-native-vector-icons` fonts are bundled in the native app.
|
|
253
|
+
|
|
254
|
+
Permission requests do nothing on iOS
|
|
255
|
+
|
|
256
|
+
- Make sure `react-native-permissions` is configured in the host app Podfile.
|
|
257
|
+
- Confirm the matching `Info.plist` usage descriptions exist.
|
|
258
|
+
- Re-run `pod install`.
|
|
259
|
+
|
|
260
|
+
Warnings about unavailable native modules
|
|
261
|
+
|
|
262
|
+
- The package is present, but native setup is incomplete.
|
|
263
|
+
- Rebuild the app after installing peers.
|
|
264
|
+
- Verify `pod install`, Android permissions, and Expo plugin setup for the affected module.
|
|
265
|
+
|
|
266
|
+
## Development
|
|
139
267
|
|
|
140
268
|
```sh
|
|
141
269
|
yarn typecheck
|
|
@@ -143,18 +271,6 @@ yarn test
|
|
|
143
271
|
yarn lint
|
|
144
272
|
```
|
|
145
273
|
|
|
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
274
|
## License
|
|
159
275
|
|
|
160
276
|
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 === false) 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
|
+
};
|